Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FileManager.copyItem() throws a permission error after having copied files (Ubuntu) #1125

Open
sebsto opened this issue Jan 20, 2025 · 2 comments
Assignees

Comments

@sebsto
Copy link

sebsto commented Jan 20, 2025

When copying an entire directory where files are owned by root, FileManager.copyItem() throws a CocoaError.fileWriteNoPermission after having copied the files.

It works on macOS and it is reproductible on Ubuntu.

Version : Swift 6.0.3 on Ubuntu 24.04

Minimal code to reproduce

import FoundationEssentials

do {
    try FileManager.default.copyItem(
        atPath: "src",
        toPath: "dst"
    )
    print("Files copied successfully.")
} catch let error as CocoaError{
    print("Cocoa Error copying files: \(error)")
} catch {
    print("Other error copying files: \(error)")
}

and this shell script

#!/bin/bash 

sudo mkdir src 
sudo touch src/toto 
ls -al src
swift main.swift
ls -al dst

This produces

total 8
drwxr-xr-x 2 root   root   4096 Jan 20 07:24 .
drwxrwxr-x 3 ubuntu ubuntu 4096 Jan 20 07:24 ..
-rw-r--r-- 1 root   root      0 Jan 20 07:24 toto

Cocoa Error copying files: CocoaError(code: FoundationEssentials.CocoaError.Code(rawValue: 513), userInfo: ["NSURL": AnyHashable(dst -- file:///home/ubuntu/swift-aws-lambda-runtime/_REPRO/), "NSFilePath": AnyHashable("dst"), "NSUnderlyingError": AnyHashable(FoundationEssentials.POSIXError(code: Glibc.POSIXErrorCode.EPERM))])

total 8
drwxr-xr-x 2 ubuntu ubuntu 4096 Jan 20 07:24 .
drwxrwxr-x 4 ubuntu ubuntu 4096 Jan 20 07:24 ..
-rw-r--r-- 1 ubuntu ubuntu    0 Jan 20 07:24 toto

Note that the source files have perm 0o644, they are readable by non-root users. Copying them must succeed and the target files must be owned by the user running the swift script. This is the case here, but FileManager.copyItem() throws an error after the copy operation.

sebsto added a commit to swift-server/swift-aws-lambda-runtime that referenced this issue Jan 21, 2025
When including resources in the package and packaging on Ubuntu,
`FileManager` throws a FilePermission error. The docker daemon runs as
root and files to be copied are owned by `root` while the archiver runs
as the current user (`ubuntu` on EC2 Ubuntu). The `FileManager` manages
to copy the files but throws an error after the copy. We suspect the
`FileManager` to perform some kind of operation after the copy and it
fails because of the `root` permission of the files.

See
#449 (comment)
for a description of the problem.

This PR contains code to reproduce the problem, a very simple
workaround, and an integration test.
The workaround consists of 
- trapping all errors
- verify if the error is the permission error (Code = 513)
- verify if the files have been copied or not 
- if the two above conditions are met, ignore the error, otherwise
re-throw it

I would rather prefer a solution that solves the root cause rather than
just ignoring the error.
We're still investigating the root cause (see [this
thread](https://forums.swift.org/t/filemanager-copyitem-on-linux-fails-after-copying-the-files/77282)
on the Swift Forum and this issue on Swift Foundation
swiftlang/swift-foundation#1125
@jmschonfeld jmschonfeld self-assigned this Jan 23, 2025
@yretenai
Copy link

yretenai commented Jan 24, 2025

copyItem eventually calls a method that invokes fchown which tries to set ownership back to root which is not permitted.

Main:

// Copy owner/group
if fchown(dstFD, statInfo.st_uid, statInfo.st_gid) != 0 {
try delegate.throwIfNecessary(errno, srcPath(), dstPath())
}

Older:

do {
let attributes = try fileManager.attributesOfItem(atPath: String(cString: fts_path))
try fileManager.setAttributes(attributes, ofItemAtPath: String(cString: buffer.baseAddress!))
} catch {
try delegate.throwIfNecessary(error, String(cString: fts_path), String(cString: buffer.baseAddress!))
}

relates to swiftlang/swift-docc#1136 and swiftlang/swift-docc-plugin#102

@jmschonfeld
Copy link
Contributor

Thanks @yretenai yeah that makes sense - the reason we don't hit this on Darwin is because on Darwin we use copyfile in order to copy the metadata over, and it looks like copyfile's behavior does not preserve the owner of the source in the destination copy, whereas the Linux implementation does attempt to preserve the source owner (at least from experimentation). We'll likely need to confirm the behavior of copyfile and update the Linux implementation to match the behavior of copyfile so we have parity across the platforms

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants