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

fs::copy fails copying '.git' dir from Linux to Windows(SMB) #131026

Closed
TobisLee opened this issue Sep 29, 2024 · 18 comments
Closed

fs::copy fails copying '.git' dir from Linux to Windows(SMB) #131026

TobisLee opened this issue Sep 29, 2024 · 18 comments
Labels
C-discussion Category: Discussion or questions that doesn't represent real issues. O-windows Operating system: Windows T-libs Relevant to the library team, which will review and decide on the PR/issue.

Comments

@TobisLee
Copy link

I tried to copy a git repository from Linux host to Windows host using yazi. And I noticed that there are some files in .git dir are copied failed. The error is permission denied. I reported to yazi (see sxyazi/yazi#1703). But it seems yazi just call the std::fs::copy to copy files. And I tried the following code:

use std::fs;

fn main() {
    fs::copy(
        "/home/xxx/.git/objects/pack/pack-d2e6xxxc2.idx",
        "/win-share/pack-d2e6xxxc2.idx").unwrap();
}

I expected to see this happen:
File pack-d2e6xxxc2.idx is copied to Windows directory.

Instead, this happened:

Error: Os { code: 13, kind: PermissionDenied, message: "Permission denied" }

Meta

rustc --version --verbose:

rustc 1.79.0 (129f3b996 2024-06-10)
binary: rustc
commit-hash: 129f3b9964af4d4a709d1383930ade12dfe7c081
commit-date: 2024-06-10
host: x86_64-unknown-linux-gnu
release: 1.79.0
LLVM version: 18.1.7
Backtrace

stack backtrace:
   0: rust_begin_unwind
             at /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/std/src/panicking.rs:652:5
   1: core::panicking::panic_fmt
             at /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/core/src/panicking.rs:72:14
   2: core::result::unwrap_failed
             at /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/core/src/result.rs:1654:5
   3: core::result::Result<T,E>::unwrap
             at /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/core/src/result.rs:1077:23
   4: fs_copy::main
             at ./src/main.rs:4:5
   5: core::ops::function::FnOnce::call_once
             at /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/core/src/ops/function.rs:250:5

@TobisLee TobisLee added the C-bug Category: This is a bug. label Sep 29, 2024
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Sep 29, 2024
@bjorn3
Copy link
Member

bjorn3 commented Sep 29, 2024

Do you actually have permission? Does copying using cp on the commandline work? If both are true can you show an strace of the failing rust program?

@TobisLee
Copy link
Author

TobisLee commented Sep 30, 2024

Do you actually have permission?

Yeah, I have the read permission for the file, and the write permission for the target folder.

Image

Does copying using cp on the commandline work?

Yeah, cp does copy the file with no error.

Image

I run the strace ./target/debug/fs-copy

execve("./target/debug/fs-copy", ["./target/debug/fs-copy"], 0x7fff3c076090 /* 66 vars */) = 0
brk(NULL)                               = 0x56c871f58000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=176339, ...}) = 0
mmap(NULL, 176339, PROT_READ, MAP_PRIVATE, 3, 0) = 0x76f1cd547000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=915712, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76f1cd545000
mmap(NULL, 184808, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x76f1cd517000
mmap(0x76f1cd51b000, 147456, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x4000) = 0x76f1cd51b000
mmap(0x76f1cd53f000, 16384, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x76f1cd53f000
mmap(0x76f1cd543000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2b000) = 0x76f1cd543000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\340_\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
fstat(3, {st_mode=S_IFREG|0755, st_size=2014520, ...}) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
mmap(NULL, 2034616, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x76f1cd326000
mmap(0x76f1cd34a000, 1511424, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x24000) = 0x76f1cd34a000
mmap(0x76f1cd4bb000, 319488, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x195000) = 0x76f1cd4bb000
mmap(0x76f1cd509000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e3000) = 0x76f1cd509000
mmap(0x76f1cd50f000, 31672, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x76f1cd50f000
close(3)                                = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76f1cd323000
arch_prctl(ARCH_SET_FS, 0x76f1cd323780) = 0
set_tid_address(0x76f1cd323a50)         = 8421
set_robust_list(0x76f1cd323a60, 24)     = 0
rseq(0x76f1cd3240a0, 0x20, 0, 0x53053053) = 0
mprotect(0x76f1cd509000, 16384, PROT_READ) = 0
mprotect(0x76f1cd543000, 4096, PROT_READ) = 0
mprotect(0x56c86b4d1000, 12288, PROT_READ) = 0
mprotect(0x76f1cd5ad000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0x76f1cd547000, 176339)          = 0
poll([{fd=0, events=0}, {fd=1, events=0}, {fd=2, events=0}], 3, 0) = 0 (Timeout)
rt_sigaction(SIGPIPE, {sa_handler=SIG_IGN, sa_mask=[PIPE], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x76f1cd3631d0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
getrandom("\xd6\x5d\xa8\x65\xa2\x1e\x5e\x63", 8, GRND_NONBLOCK) = 8
brk(NULL)                               = 0x56c871f58000
brk(0x56c871f79000)                     = 0x56c871f79000
openat(AT_FDCWD, "/proc/self/maps", O_RDONLY|O_CLOEXEC) = 3
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(3, "56c86b47f000-56c86b485000 r--p 0"..., 1024) = 1024
read(3, "p 001e3000 103:02 658867        "..., 1024) = 1024
read(3, "xp 00001000 103:02 658789       "..., 1024) = 549
close(3)                                = 0
sched_getaffinity(8421, 32, [0 1 2 3 4 5 6 7 8 9 10 11]) = 8
rt_sigaction(SIGSEGV, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction(SIGSEGV, {sa_handler=0x56c86b4a4e40, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_SIGINFO, sa_restorer=0x76f1cd3631d0}, NULL, 8) = 0
rt_sigaction(SIGBUS, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction(SIGBUS, {sa_handler=0x56c86b4a4e40, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_SIGINFO, sa_restorer=0x76f1cd3631d0}, NULL, 8) = 0
sigaltstack(NULL, {ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=0}) = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x76f1cd570000
mprotect(0x76f1cd570000, 4096, PROT_NONE) = 0
sigaltstack({ss_sp=0x76f1cd571000, ss_flags=0, ss_size=8192}, NULL) = 0
openat(AT_FDCWD, "/home/tlss/repos/my-own-swordsmam-bilibili-danmu/.git/objects/pack/pack-d2e66015bbbc4e376941d6b59325b32cd94baac2.idx", O_RDONLY|O_CLOEXEC) = 3
statx(3, "", AT_STATX_SYNC_AS_STAT|AT_EMPTY_PATH, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0444, stx_size=21176, ...}) = 0
openat(AT_FDCWD, "/home/tlss/win-share/18tb/temp/target.idx", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0100444) = -1 EACCES (Permission denied)
close(3)                                = 0
write(2, "thread '", 8thread ')                 = 8
write(2, "main", 4main)                     = 4
write(2, "' panicked at ", 14' panicked at )          = 14
write(2, "src/main.rs", 11src/main.rs)             = 11
write(2, ":", 1:)                        = 1
write(2, "6", 16)                        = 1
write(2, ":", 1:)                        = 1
write(2, "54", 254)                       = 2
write(2, ":\n", 2:
)                      = 2
write(2, "called `Result::unwrap()` on an "..., 114called `Result::unwrap()` on an `Err` value: Os { code: 13, kind: PermissionDenied, message: "Permission denied" }) = 114
write(2, "\n", 1
)                       = 1
write(2, "note: run with `RUST_BACKTRACE=1"..., 78note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
) = 78
futex(0x76f1cd544070, FUTEX_WAKE_PRIVATE, 2147483647) = 0
sigaltstack({ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=8192}, NULL) = 0
munmap(0x76f1cd570000, 12288)           = 0
exit_group(101)                         = ?
+++ exited with 101 +++

@the8472
Copy link
Member

the8472 commented Sep 30, 2024

What's the windows share mounted with? The kernel's SMB driver or a FUSE implementation? You can check via
grep win-share /proc/mounts

@the8472
Copy link
Member

the8472 commented Sep 30, 2024

openat(AT_FDCWD, "/home/tlss/win-share/18tb/temp/target.idx", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0100444)

Hrrm, the 1 there looks sus, maybe we shouldn't be passing through all the bits from st_mode.

On the other hand the openat2 manpage says:

          Whereas openat(2) ignores bits other than those in the range 07777 in its mode argument, openat2() returns an error if how.mode con‐
          tains  bits other than 07777.  Similarly, an error is returned if openat2() is called with a nonzero how.mode and how.flags does not
          contain O_CREAT or O_TMPFILE.

so the extra bits should be fine with openat.

@the8472
Copy link
Member

the8472 commented Sep 30, 2024

Or maybe windows/smb doesn't support unix semantics of open where the current FD permissions can be different from the on-disk mode that only affect subsequent opens.

@TobisLee does the copy succeed if you make the source file writable?

@TobisLee
Copy link
Author

Hey @the8472, thanks for your help. Here's some information that might be useful.

What's the windows share mounted with?

I use the kernel's SMB driver.

> grep win-share /proc/mounts
//192.168.2.5/h /home/tlss/win-share/18tb cifs rw,relatime,vers=3.1.1,cache=strict,username=tlss1,uid=1000,forceuid,gid=0,noforcegid,addr=192.168.2.5,file_mode=0755,dir_mode=0755,soft,nounix,serverino,mapposix,reparse=nfs,rsize=4194304,wsize=4194304,bsize=1048576,retrans=1,echo_interval=60,actimeo=1,closetimeo=1 0 0

does the copy succeed if you make the source file writable?

No, still got the same error.

@the8472
Copy link
Member

the8472 commented Sep 30, 2024

Then does the target file already exist with permissions that would prevent it from being overwritten?

@TobisLee
Copy link
Author

Ah, yes, there is a target file already exists, after I delete the file, and rerun the code, the target file is copied successfully.

@the8472
Copy link
Member

the8472 commented Sep 30, 2024

Then I think there's no problem here. cp also has the same behavior where it does not clobber write-protected destination files

$ touch a && touch b && chmod -w b && cp a b
cp: cannot create regular file 'b': Permission denied

@TobisLee
Copy link
Author

TobisLee commented Oct 1, 2024

Sorry, I am not sure I understand you correctly.

As you said, when the target file ('b') exists, the copy will fail. But at the first time, there is no target file in the destination directory, and I run the code, I got the error. It's more like:

$ touch a && chmod -w a && cp a b

And the second time, after I add writable permission to source file, the code run successfully.

@the8472
Copy link
Member

the8472 commented Oct 1, 2024

I'm not sure if I understand the order of events correctly.
You previously said it copied successfully after deleting the file.
Now you outline a few steps but also say "the second time", so there are more steps?

Can you provide complete procedures of what does and what doesn't work?

@TobisLee
Copy link
Author

TobisLee commented Oct 1, 2024

If the file have write permission, then the code will run successfully, the file will be copied. Otherwise, the code will fail. The cp command copy the file successfully whether it has write permission or not.

I record a video to show the complete procedures.

_20241001_171130.webm

@the8472
Copy link
Member

the8472 commented Oct 1, 2024

Ok, then I suspect this is an issue with windows or the cifs driver that it can't open-create a file for writing and simultanously give it read-only permissions.

Setting the permissions early is intentional: #58803

Can you verify that this fails on the windows share but succeeds on your local drives?

use std::os::unix::fs::OpenOptionsExt;
use std::fs::OpenOptions;
fn main() {
    let path = "....";
    OpenOptions::new().mode(0o444).write(true).create(true).truncate(true).open(path).expect("should succeed");
}

@TobisLee
Copy link
Author

TobisLee commented Oct 1, 2024

Yeah, I run the code you provided, it fails on windows but succeeds on my local drives.

Thanks for your help, I'll look the reason that setting the permissions early. Is there some workarounds I can do to avoid this?

@the8472
Copy link
Member

the8472 commented Oct 1, 2024

At the moment your options are

  • making the source files writable
  • using some cifs mount options that force specific permissions?
  • coding a custom copy routine via OpenOptions + io::copy
  • trying the windows nfs share feature, maybe it behaves differently

For reference, which linux kernel version and which windows version are you using?

I use the kernel's SMB driver.

grep win-share /proc/mounts
//192.168.2.5/h /home/tlss/win-share/18tb cifs rw,relatime,vers=3.1.1,cache=strict,username=tlss1,uid=1000,forceuid,gid=0,noforcegid,addr=192.168.2.5,file_mode=0755,dir_mode=0755,soft,nounix,serverino,mapposix,reparse=nfs,rsize=4194304,wsize=4194304,bsize=1048576,retrans=1,echo_interval=60,actimeo=1,closetimeo=1 0 0

I see that you're mounting with nounix, try without that?

@TobisLee
Copy link
Author

TobisLee commented Oct 1, 2024

Thanks for your advice, you're so nice.

For reference, which linux kernel version and which windows version are you using?

My linux version is: Linux arch 6.10.10-arch1-1
My windows version is: Windows11 Pro 22H2 22621.4169

I see that you're mounting with nounix, try without that?

I don't use the nounix flag, I just add the following line to my fstab

//192.168.2.5/h   		/home/tlss/win-share/18tb   	cifs 		_netdev,nofail,username=usr,password=pwd,uid=1000 0 0

I try to add unix flag and re-mount, but I get some error message:

[   14.060694] CIFS: VFS: Server does not support mounting with posix SMB3.11 extensions
[   14.061005] CIFS: VFS: cifs_mount failed w/return code = -95

Is there any other way to mount without nounix?

@the8472
Copy link
Member

the8472 commented Oct 1, 2024

Ah turns out windows doesn't support the unix extensions, it's a samba thing.

@TobisLee
Copy link
Author

TobisLee commented Oct 2, 2024

All right. Then I'll try other network file share protocols. Huge thanks for your help. I'll close this issue.

@TobisLee TobisLee closed this as not planned Won't fix, can't repro, duplicate, stale Oct 2, 2024
@saethlin saethlin added O-windows Operating system: Windows C-discussion Category: Discussion or questions that doesn't represent real issues. T-libs Relevant to the library team, which will review and decide on the PR/issue. and removed C-bug Category: This is a bug. needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels Oct 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-discussion Category: Discussion or questions that doesn't represent real issues. O-windows Operating system: Windows T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

5 participants