While compiling on BeOS, a more difficult problem shows up in the testsuite: recursive "chown -R ..." and recursive removals "rm -r ..." fail with error message "Function not implemented" (= strerror(ENOSYS)). The reason:
BeOS doesn't have an fchdir() function, nor fdopendir(), nor a /proc filesystem. The fdopendir() function substitute can therefore never be expected to work. I expect that the same problem exists on DJGPP and on older Unices. You can also reproduce parts of the problems on Linux: $ ./configure Then comment out the definitions of HAVE_OPENAT, HAVE_FDOPENDIR, HAVE_FCHDIR, HAVE_FCHMOD in config.h. In lib/Makefile, add ${LIBOBJDIR}fchdir-stub$U.o to am__DEPENDENCIES_1 and to LIBOBJS. Also unmount /proc or make it inaccessible through "chmod 000 /proc". Then $ make $ make -k check chgrp/no-x fails chgrp/posix-H fails chgrp/basic fails chgrp/recurse fails chmod/no-x fails cp/link-preserve fails cp/same-file fails cp/dir-slash fails du/deref-args fails du/slash fails du/hard-link fails du/basic fails du/restore-wd fails du/exclude fails du/no-x fails du/trailing-slash fails du/deref fails du/two-args fails ln/misc fails misc/pwd-long fails mkdir/perm fails mv/part-hardlink fails mv/mv-special-1 fails mv/into-self-3 fails mv/hard-link-1 fails mv/part-symlink fails rm/empty-inacc fails rm/dot-rel fails rm/unread3 fails rm/cycle fails rm/rm1 fails rm/rm2 fails rm/rm3 fails rm/rm5 fails rm/r-1 fails rm/r-2 fails rm/r-3 fails rm/ir-1 fails rm/deep-1 fails du fails rmdir fails help-version fails Attached you find a fix. It allows to build coreutils on platforms without fchdir(). The tests that then still fail are: chgrp/no-x (just a slightly different error message) chmod/no-x (just a slightly different error message) du/no-x (just a slightly different error message) misc/pwd-long ("File name too long" errors, expected) rm/rm3 (asks different questions) rm/rm5 (asks different questions) Note: I made no attempt to minimize the code execution path on platforms without fchdir(); I tried to minimize the changes. I'm not sure if openat_needs_fchdir () should be used here. Using it means to bet that if the /proc based technique works once, it will work always. Hmm, platforms without fchdir() probably don't have a /proc filesystem anyway... 2006-08-19 Bruno Haible <[EMAIL PROTECTED]> * lib/fts.c (fts_open): If fchdir() does not exist, set FTS_NOCHDIR and unset FTS_CWDFD. (fts_stat): If fchdir() does not exist, use lstat instead of fstatat. * src/remove.c (AD_pop_and_chdir): If fchdir() does not exist, try opendir as an alternative to fdopendir. (lstatat, rmat, rmdirat): New macros. (write_protected_non_symlink, prompt): If fchdir() does not exist, use lstat instead of fstatat. (DO_UNLINK, DO_RMDIR): If fchdir() does not exist, use unlink or rmdir instead of unlinkat. (remove_entry): If fchdir() does not exist, use lstat instead of fstatat. (fd_to_subdirp): If either openat_permissive or fdopendir fails and fchdir() does not exist, try opendir + fstat as an alternative to fstat + fdopendir. (remove_cwd_entries, remove_dir): Use rmdirat instead of unlinkat. --- lib/fts.c.bak 2006-07-11 20:22:27.000000000 +0200 +++ lib/fts.c 2006-08-20 19:33:49.000000000 +0200 @@ -292,6 +292,17 @@ CLR(FTS_CWDFD); } +#if !HAVE_FDOPENDIR && !HAVE_FCHDIR + if (!ISSET(FTS_NOCHDIR) && openat_needs_fchdir ()) { + /* If the filenames in /proc are not accessible and fchdir() + is a stub, our fdopendir() substitute can not work reliably, + therefore opendirat() does not work reliably either, hence + it should not be called. */ + SET(FTS_NOCHDIR); + CLR(FTS_CWDFD); + } +#endif + /* Initialize fts_cwd_fd. */ sp->fts_cwd_fd = AT_FDCWD; if ( ISSET(FTS_CWDFD) && ! HAVE_OPENAT_SUPPORT) @@ -1177,8 +1188,16 @@ p->fts_errno = saved_errno; goto err; } - } else if (fstatat(sp->fts_cwd_fd, p->fts_accpath, sbp, - AT_SYMLINK_NOFOLLOW)) { + } +#if HAVE_OPENAT || HAVE_FCHDIR +# define lstatat(Fd_cwd, Filename, Buf) \ + fstatat (Fd_cwd, Filename, Buf, AT_SYMLINK_NOFOLLOW) +#else +/* If fchdir() is a stub, our fstatat() substitute cannot work reliably. */ +# define lstatat(Fd_cwd, Filename, Buf) \ + lstat (Filename, Buf) +#endif + else if (lstatat(sp->fts_cwd_fd, p->fts_accpath, sbp)) { p->fts_errno = errno; err: memset(sbp, 0, sizeof(struct stat)); return (FTS_NS); --- src/remove.c.bak 2006-07-05 08:56:17.000000000 +0200 +++ src/remove.c 2006-08-20 19:34:40.000000000 +0200 @@ -471,15 +471,24 @@ *dirp = fdopendir (fd); if (*dirp == NULL) { - error (0, errno, _("FATAL: cannot return to .. from %s"), - quote (full_filename ("."))); +#if !HAVE_FDOPENDIR && !HAVE_FCHDIR + /* If the filenames in /proc are not accessible and fchdir() is a + stub, our fdopendir() substitute can not work reliably. Need to + fall back to filename manipulations. */ + *dirp = opendir (full_filename (".")); +#endif + if (*dirp == NULL) + { + error (0, errno, _("FATAL: cannot return to .. from %s"), + quote (full_filename ("."))); - close_and_next:; - close (fd); + close_and_next:; + close (fd); - next_cmdline_arg:; - free (*prev_dir); - longjmp (ds->current_arg_jumpbuf, 1); + next_cmdline_arg:; + free (*prev_dir); + longjmp (ds->current_arg_jumpbuf, 1); + } } } else @@ -629,6 +638,38 @@ return ! (top->unremovable && hash_lookup (top->unremovable, file)); } +#if HAVE_OPENAT || HAVE_FCHDIR + +# define lstatat(Fd_cwd, Filename, Buf) \ + fstatat (Fd_cwd, Filename, Buf, AT_SYMLINK_NOFOLLOW) +# define rmat(Fd_cwd, Filename) \ + unlinkat (Fd_cwd, Filename, 0) +# define rmdirat(Fd_cwd, Filename) \ + unlinkat (Fd_cwd, Filename, AT_REMOVEDIR) + +#else + +/* If fchdir() is a stub, our fstatat() substitute cannot work reliably. */ + +# define lstatat(Fd_cwd, Filename, Buf) \ + lstat ((Fd_cwd) == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (Filename) \ + ? Filename \ + : full_filename (Filename), \ + Buf) + +/* If fchdir() is a stub, our unlinkat() substitute cannot work reliably. */ + +# define rmat(Fd_cwd, Filename) \ + unlink ((Fd_cwd) == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (Filename) \ + ? Filename \ + : full_filename (Filename)) +# define rmdirat(Fd_cwd, Filename) \ + rmdir ((Fd_cwd) == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (Filename) \ + ? Filename \ + : full_filename (Filename)) + +#endif + /* Return true if DIR is determined to be an empty directory. */ static bool is_empty_dir (int fd_cwd, char const *dir) @@ -670,7 +711,7 @@ struct stat **buf_p, struct stat *buf) { - if (fstatat (fd_cwd, file, buf, AT_SYMLINK_NOFOLLOW) != 0) + if (lstatat (fd_cwd, file, buf) != 0) return false; *buf_p = buf; if (S_ISLNK (buf->st_mode)) @@ -767,7 +808,7 @@ if (sbuf == NULL) { sbuf = &buf; - if (fstatat (fd_cwd, filename, sbuf, AT_SYMLINK_NOFOLLOW)) + if (lstatat (fd_cwd, filename, sbuf)) { /* lstat failed. This happens e.g., with `rm '''. */ error (0, errno, _("cannot remove %s"), @@ -859,7 +900,7 @@ #define DO_UNLINK(Fd_cwd, Filename, X) \ do \ { \ - if (unlinkat (Fd_cwd, Filename, 0) == 0) \ + if (rmat (Fd_cwd, Filename) == 0) \ { \ if ((X)->verbose) \ printf (_("removed %s\n"), quote (full_filename (Filename))); \ @@ -874,7 +915,7 @@ #define DO_RMDIR(Fd_cwd, Filename, X) \ do \ { \ - if (unlinkat (Fd_cwd, Filename, AT_REMOVEDIR) == 0) /* rmdir */ \ + if (rmdirat (Fd_cwd, Filename) == 0) \ { \ if ((X)->verbose) \ printf (_("removed directory: %s\n"), \ @@ -974,7 +1015,7 @@ else { struct stat sbuf; - if (fstatat (fd_cwd, filename, &sbuf, AT_SYMLINK_NOFOLLOW)) + if (lstatat (fd_cwd, filename, &sbuf)) { if (errno == ENOENT && x->ignore_missing_files) return RM_OK; @@ -1042,13 +1083,15 @@ int open_flags = O_RDONLY | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK; int fd_sub = openat_permissive (fd_cwd, f, open_flags, 0, cwd_errno); + if (fd_sub < 0) + goto failed; + /* Record dev/ino of F. We may compare them against saved values to thwart any attempt to subvert the traversal. They are also used to detect directory cycles. */ - if (fd_sub < 0 || fstat (fd_sub, subdir_sb) != 0) + if (fstat (fd_sub, subdir_sb) != 0) { - if (0 <= fd_sub) - close_preserve_errno (fd_sub); + close_preserve_errno (fd_sub); return NULL; } @@ -1063,10 +1106,32 @@ if (subdir_dirp == NULL) { close_preserve_errno (fd_sub); - return NULL; + goto failed; } return subdir_dirp; + + failed: +#if !HAVE_FDOPENDIR && !HAVE_FCHDIR + /* If the filenames in /proc are not accessible and fchdir() is a stub, + our fdopendir() substitute can not work reliably. Need to fall back + to filename manipulations. */ + subdir_dirp = opendir (fd_cwd == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (f) + ? f + : full_filename (f)); + if (subdir_dirp != NULL) + { + if (fstat (dirfd (subdir_dirp), subdir_sb) != 0) + { + int saved_errno = errno; + closedir (subdir_dirp); + errno = saved_errno; + return NULL; + } + return subdir_dirp; + } +#endif + return NULL; } /* Remove entries in the directory open on DIRP @@ -1166,7 +1231,7 @@ /* Upon fd_to_subdirp failure, try to remove F directly, in case it's just an empty directory. */ int saved_errno = errno; - if (unlinkat (dirfd (*dirp), f, AT_REMOVEDIR) == 0) + if (rmdirat (dirfd (*dirp), f) == 0) status = RM_OK; else error (0, saved_errno, @@ -1252,7 +1317,7 @@ /* Upon fd_to_subdirp failure, try to remove DIR directly, in case it's just an empty directory. */ int saved_errno = errno; - if (unlinkat (fd_cwd, dir, AT_REMOVEDIR) == 0) + if (rmdirat (fd_cwd, dir) == 0) return RM_OK; error (0, saved_errno, @@ -1326,7 +1391,7 @@ goto closedir_and_return; } - if (unlinkat (fd, empty_dir, AT_REMOVEDIR) == 0) + if (rmdirat (fd, empty_dir) == 0) { if (x->verbose) printf (_("removed directory: %s\n"),