Hi, I ran into an issue that might or might not be a bug in nfs4. When creating a file that does not previously exist on my system with openat(AT_FDCWD, fname, O_CREAT | O_EXCL, 0777); vs. openat(AT_FDCWD, fname, O_CREAT, 0777); the file has permissions 0755 for the first version and 0600 for the second. umask is 0022 in both cases, the calls are in the same program, right after each other (with an unlink in between). I am mostly worried about the executable bit for the owner, which is lost. Executing the code on an ext4 filesystem "works", meaning that it produces the same permissions for both openat calls, regardless of O_EXCL. My questions would be - Is that expected, or an indication that something is off? - Could it be some issue in the backend, not in nfs4 itself? - Can someone reproduce this on a NFS4 mount (test is below)? Thanks - Eph System details ************** The storage backend is some SPSC IBM System. $> uname -a Linux xxxx 5.4.0-66-generic #74-Ubuntu SMP Wed Jan 27 22:54:38 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux $> mount -vv |grep foo 10.0.11.183:/export/foo on /import/foo type nfs4 (rw,relatime,vers=4.0,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=x.x.x.x,local_lock=none,addr=x.x.x.x) strace ****** This is an strace of a program that triggered this behaviour. Note how the second openat call has no O_EXCL, but the last lstat reports 0640 as mode: lstat("filename.sh", 0x7ffc2f148c20) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "filename.sh", O_WRONLY|O_CREAT|O_EXCL, 0777) = 4 write(4, "#!/bin/bash\n#\n# Build and run hb"..., 1973) = 1973 fstat(4, {st_mode=S_IFREG|0755, st_size=1973, ...}) = 0 close(4) = 0 lstat("filename.sh", {st_mode=S_IFREG|0755, st_size=1973, ...}) = 0 unlink("filename.sh") = 0 openat(AT_FDCWD, "filename.sh", O_WRONLY|O_CREAT|O_TRUNC, 0777) = 4 write(4, "#!/bin/bash\n#\n# Build and run hb"..., 1973) = 1973 close(4) = 0 lstat("filename.sh", {st_mode=S_IFREG|0640, st_size=1973, ...}) = 0 Test script to reproduce ************************ Reproduce with gcc test.c && ./a.out test_file The reported st_modes should be identical. -------------8<--------------- #include <stdio.h> #include <stdlib.h> #include <memory.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <malloc.h> #define FAIL() {fprintf(stderr, "Failed at %s:%d\n", \ __FILE__,(int)__LINE__); exit(2); } #define EXPECT(A,B) {if ((A) != (B)) FAIL(); } int run_test(const char* const fname) { int res; int fd; struct stat mystat; res = lstat(fname, &mystat); if (res != -1) { /* File exists, delete */ res = unlink(fname); EXPECT(res, 0); } res = lstat(fname, &mystat); EXPECT(res, -1); /* Create file with O_EXCL */ fd = openat(AT_FDCWD, fname, O_CREAT | O_EXCL, 0777); if (fd == -1) FAIL(); res = close(fd); EXPECT(res, 0); res = lstat(fname, &mystat); EXPECT(res, 0); printf("st_mode after creating with O_EXCL: %4o\n", mystat.st_mode); /* Delete file */ res = unlink(fname); EXPECT(res, 0); /* Create file without O_EXCL */ fd = openat(AT_FDCWD, fname, O_CREAT, 0777); if (fd == -1) FAIL(); res = close(fd); EXPECT(res, 0); res = lstat(fname, &mystat); EXPECT(res, 0); printf("st_mode after creating w/o O_EXCL: %4o\n", mystat.st_mode); } int main(int argc, const char** argv) { if (argc < 2) { printf("Delete and re-create a file with different modes,\n"); printf("checking the file permissions bits each time.\n"); printf("Usage:\n"); printf(" %s <filename>\n", argv[0]); printf("ATTENTION: The passed filename will be deleted.\n"); return 1; } const char* fname = argv[1]; return run_test(fname); }