From 9dc306b1db08196684d05a474148e16305adbad0 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Tue, 17 Sep 2019 19:18:14 -0700 Subject: [PATCH] Improve reporting of I/O, access errors Signal an error for file-oriented errors that are not tame errors like ENOENT and ENOTDIR (Bug#37389). Do this for primitives exposed to Lisp; the lower level internal C API merely makes errno values available to higher-level C code. * doc/lispref/files.texi (Testing Accessibility) (File Attributes, Extended Attributes): Do not say that the functions return nil when the return value cannot be determined. * etc/NEWS: Mention the change. * src/dired.c (Ffile_attributes): Fix doc string confusion about opening a file vs getting its attributes. (file_attributes): Signal serious errors. * src/fileio.c (check_existing, check_executable) (check_writable): Remove. All callers changed to use check_file_access or file_access_p. (file_access_p, file_metadata_errno, file_attribute_errno) (file_test_errno, check_file_access, check_emacs_readlinkat): New functions. * src/fileio.c (Ffile_executable_p, Ffile_readable_p) (Ffile_name_case_insensitive_p, Frename_file, Ffile_exists_p): (Ffile_symlink_p, Ffile_directory_p) (Ffile_accessible_directory_p, Ffile_regular_p) (Ffile_selinux_context, Ffile_acl, Ffile_modes) (Ffile_newer_than_file_p, Fset_visited_file_modtime) (Ffile_system_info): * src/filelock.c (unlock_file, Ffile_locked_p): * src/lread.c (Fload): Signal serious errors. * src/fileio.c (Ffile_writable_p): Remove unnecessary CHECK_STRING. (emacs_readlinkat): Now static. * src/filelock.c (current_lock_owner, lock_if_free): Return a positive errno on error, and the negative of the old old value on success. All callers changed. * src/lread.c (openp): Propagate serious errno values to caller. --- doc/lispref/files.texi | 13 +- etc/NEWS | 10 ++ src/dired.c | 9 +- src/emacs.c | 2 +- src/fileio.c | 359 +++++++++++++++++++++++------------------ src/filelock.c | 86 +++++----- src/lisp.h | 5 +- src/lread.c | 21 ++- 8 files changed, 282 insertions(+), 223 deletions(-) diff --git a/doc/lispref/files.texi b/doc/lispref/files.texi index 18a1f4908d6..fba9622fecf 100644 --- a/doc/lispref/files.texi +++ b/doc/lispref/files.texi @@ -856,8 +856,7 @@ systems, this is true if the file exists and you have execute permission on the containing directories, regardless of the permissions of the file itself.) -If the file does not exist, or if access control policies prevent you -from finding its attributes, this function returns @code{nil}. +If the file does not exist, this function returns @code{nil}. Directories are files, so @code{file-exists-p} can return @code{t} when given a directory. However, because @code{file-exists-p} follows @@ -1262,7 +1261,7 @@ on the 19th, @file{aug-20} was written on the 20th, and the file @defun file-attributes filename &optional id-format @anchor{Definition of file-attributes} This function returns a list of attributes of file @var{filename}. If -the specified file's attributes cannot be accessed, it returns @code{nil}. +the specified file does not exist, it returns @code{nil}. This function does not follow symbolic links. The optional parameter @var{id-format} specifies the preferred format of attributes @acronym{UID} and @acronym{GID} (see below)---the @@ -1464,9 +1463,8 @@ The underlying ACL implementation is platform-specific; on GNU/Linux and BSD, Emacs uses the POSIX ACL interface, while on MS-Windows Emacs emulates the POSIX ACL interface with native file security APIs. -If Emacs was not compiled with ACL support, or the file does not exist -or is inaccessible, or Emacs was unable to determine the ACL entries -for any other reason, then the return value is @code{nil}. +If ACLs are not supported or the file does not exist, +then the return value is @code{nil}. @end defun @defun file-selinux-context filename @@ -1478,8 +1476,7 @@ for details about what these actually mean. The return value has the same form as what @code{set-file-selinux-context} takes for its @var{context} argument (@pxref{Changing Files}). -If Emacs was not compiled with SELinux support, or the file does not -exist or is inaccessible, or if the system does not support SELinux, +If SELinux is not supported or the file does not exist, then the return value is @code{(nil nil nil nil)}. @end defun diff --git a/etc/NEWS b/etc/NEWS index 9aec8da5663..dce4903384f 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2005,6 +2005,16 @@ file name if there is no user named "foo". ** The FILENAME argument to 'file-name-base' is now mandatory and no longer defaults to 'buffer-file-name'. ++++ +** File metadata primitives now signal an error if I/O, access, or +other serious errors prevent them from determining the result. +Formerly, these functions often (though not always) returned nil. +For example, if the directory /etc/firewalld is not searchable, +(file-symlink-p "/etc/firewalld/firewalld.conf") now signals an error +instead of returning nil, because file-symlink-p cannot determine +whether a symbolic link exists there. These functions still behave as +before if the only problem is that the file does not exist. + --- ** The function 'eldoc-message' now accepts a single argument. Programs that called it with multiple arguments before should pass diff --git a/src/dired.c b/src/dired.c index df03bc32cef..3768b6dbb7c 100644 --- a/src/dired.c +++ b/src/dired.c @@ -819,7 +819,7 @@ stat_gname (struct stat *st) DEFUN ("file-attributes", Ffile_attributes, Sfile_attributes, 1, 2, 0, doc: /* Return a list of attributes of file FILENAME. -Value is nil if specified file cannot be opened. +Value is nil if specified file does not exist. ID-FORMAT specifies the preferred format of attributes uid and gid (see below) - valid values are `string' and `integer'. The latter is the @@ -939,15 +939,14 @@ file_attributes (int fd, char const *name, information to be accurate. */ w32_stat_get_owner_group = 1; #endif - if (fstatat (fd, name, &s, AT_SYMLINK_NOFOLLOW) == 0) - err = 0; + err = fstatat (fd, name, &s, AT_SYMLINK_NOFOLLOW) == 0 ? 0 : errno; #ifdef WINDOWSNT w32_stat_get_owner_group = 0; #endif } if (err != 0) - return unbind_to (count, Qnil); + return unbind_to (count, file_attribute_errno (filename, err)); Lisp_Object file_type; if (S_ISLNK (s.st_mode)) @@ -956,7 +955,7 @@ file_attributes (int fd, char const *name, symlink is replaced between the call to fstatat and the call to emacs_readlinkat. Detect this race unless the replacement is also a symlink. */ - file_type = emacs_readlinkat (fd, name); + file_type = check_emacs_readlinkat (fd, filename, name); if (NILP (file_type)) return unbind_to (count, Qnil); } diff --git a/src/emacs.c b/src/emacs.c index 558dd11a351..eb732810db4 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -746,7 +746,7 @@ load_pdump_find_executable (char const *argv0, ptrdiff_t *candidate_size) candidate[path_part_length] = DIRECTORY_SEP; memcpy (candidate + path_part_length + 1, argv0, argv0_length + 1); struct stat st; - if (check_executable (candidate) + if (file_access_p (candidate, X_OK) && stat (candidate, &st) == 0 && S_ISREG (st.st_mode)) return candidate; *candidate = '\0'; diff --git a/src/fileio.c b/src/fileio.c index 81c29ca0cca..0977516f019 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -141,54 +141,38 @@ static bool e_write (int, Lisp_Object, ptrdiff_t, ptrdiff_t, struct coding_system *); -/* Return true if FILENAME exists, otherwise return false and set errno. */ - -static bool -check_existing (const char *filename) -{ - return faccessat (AT_FDCWD, filename, F_OK, AT_EACCESS) == 0; -} - -/* Return true if file FILENAME exists and can be executed. */ +/* Test whether FILE is accessible for AMODE. + Return true if successful, false (setting errno) otherwise. */ bool -check_executable (char *filename) -{ - return faccessat (AT_FDCWD, filename, X_OK, AT_EACCESS) == 0; -} - -/* Return true if file FILENAME exists and can be accessed - according to AMODE, which should include W_OK. - On failure, return false and set errno. */ - -static bool -check_writable (const char *filename, int amode) +file_access_p (char const *file, int amode) { #ifdef MSDOS - /* FIXME: an faccessat implementation should be added to the - DOS/Windows ports and this #ifdef branch should be removed. */ - struct stat st; - if (stat (filename, &st) < 0) - return 0; - errno = EPERM; - return (st.st_mode & S_IWRITE || S_ISDIR (st.st_mode)); -#else /* not MSDOS */ - bool res = faccessat (AT_FDCWD, filename, amode, AT_EACCESS) == 0; -#ifdef CYGWIN - /* faccessat may have returned failure because Cygwin couldn't - determine the file's UID or GID; if so, we return success. */ - if (!res) + if (amode & W_OK) { - int faccessat_errno = errno; + /* FIXME: The MS-DOS faccessat implementation should handle this. */ struct stat st; - if (stat (filename, &st) < 0) - return 0; - res = (st.st_uid == -1 || st.st_gid == -1); - errno = faccessat_errno; - } -#endif /* CYGWIN */ - return res; -#endif /* not MSDOS */ + if (stat (file, &st) != 0) + return false; + errno = EPERM; + return st.st_mode & S_IWRITE || S_ISDIR (st.st_mode); + } +#endif + + if (faccessat (AT_FDCWD, file, amode, AT_EACCESS) == 0) + return true; + +#ifdef CYGWIN + /* Return success if faccessat failed because Cygwin couldn't + determine the file's UID or GID. */ + int err = errno; + struct stat st; + if (stat (file, &st) == 0 && (st.st_uid == -1 || st.st_gid == -1)) + return true; + errno = err; +#endif + + return false; } /* Signal a file-access failure. STRING describes the failure, @@ -251,6 +235,30 @@ report_file_notify_error (const char *string, Lisp_Object name) } #endif +/* ACTION failed for FILE with errno ERR. Signal an error if ERR + means the file's metadata could not be retrieved even though it may + exist, otherwise return nil. */ + +static Lisp_Object +file_metadata_errno (char const *action, Lisp_Object file, int err) +{ + if (err == ENOENT || err == ENOTDIR || err == 0) + return Qnil; + report_file_errno (action, file, err); +} + +Lisp_Object +file_attribute_errno (Lisp_Object file, int err) +{ + return file_metadata_errno ("Getting attributes", file, err); +} + +static Lisp_Object +file_test_errno (Lisp_Object file, int err) +{ + return file_metadata_errno ("Testing file", file, err); +} + void close_file_unwind (int fd) { @@ -2446,8 +2454,12 @@ The arg must be a string. */) while (true) { int err = file_name_case_insensitive_err (filename); - if (! (err == ENOENT || err == ENOTDIR)) - return err < 0 ? Qt : Qnil; + switch (err) + { + case -1: return Qt; + default: return file_test_errno (filename, err); + case ENOENT: case ENOTDIR: break; + } Lisp_Object parent = file_name_directory (filename); /* Avoid infinite loop if the root is reported as non-existing (impossible?). */ @@ -2560,7 +2572,7 @@ This is what happens in interactive use with M-x. */) { Lisp_Object symlink_target = (S_ISLNK (file_st.st_mode) - ? emacs_readlinkat (AT_FDCWD, SSDATA (encoded_file)) + ? check_emacs_readlinkat (AT_FDCWD, file, SSDATA (encoded_file)) : Qnil); if (!NILP (symlink_target)) Fmake_symbolic_link (symlink_target, newname, ok_if_already_exists); @@ -2708,32 +2720,48 @@ file_name_absolute_p (char const *filename) || user_homedir (&filename[1])))); } -DEFUN ("file-exists-p", Ffile_exists_p, Sfile_exists_p, 1, 1, 0, - doc: /* Return t if file FILENAME exists (whether or not you can read it). -See also `file-readable-p' and `file-attributes'. -This returns nil for a symlink to a nonexistent file. -Use `file-symlink-p' to test for such links. */) - (Lisp_Object filename) -{ - Lisp_Object absname; - Lisp_Object handler; +/* Return t if FILE exists and is accessible via OPERATION and AMODE, + nil (setting errno) if not. Signal an error if the result cannot + be determined. */ - CHECK_STRING (filename); - absname = Fexpand_file_name (filename, Qnil); - - /* If the file name has special constructs in it, - call the corresponding file name handler. */ - handler = Ffind_file_name_handler (absname, Qfile_exists_p); +static Lisp_Object +check_file_access (Lisp_Object file, Lisp_Object operation, int amode) +{ + file = Fexpand_file_name (file, Qnil); + Lisp_Object handler = Ffind_file_name_handler (file, operation); if (!NILP (handler)) { - Lisp_Object result = call2 (handler, Qfile_exists_p, absname); + Lisp_Object ok = call2 (handler, operation, file); + /* This errno value is bogus. Any caller that depends on errno + should be rethought anyway, to avoid a race between testing a + handled file's accessibility and using the file. */ errno = 0; - return result; + return ok; } - absname = ENCODE_FILE (absname); + char *encoded_file = SSDATA (ENCODE_FILE (file)); + bool ok = file_access_p (encoded_file, amode); + if (ok) + return Qt; + int err = errno; + if (err == EROFS || err == ETXTBSY + || (err == EACCES && amode != F_OK + && file_access_p (encoded_file, F_OK))) + { + errno = err; + return Qnil; + } + return file_test_errno (file, err); +} - return check_existing (SSDATA (absname)) ? Qt : Qnil; +DEFUN ("file-exists-p", Ffile_exists_p, Sfile_exists_p, 1, 1, 0, + doc: /* Return t if file FILENAME exists (whether or not you can read it). +See also `file-readable-p' and `file-attributes'. +This returns nil for a symlink to a nonexistent file. +Use `file-symlink-p' to test for such links. */) + (Lisp_Object filename) +{ + return check_file_access (filename, Qfile_exists_p, F_OK); } DEFUN ("file-executable-p", Ffile_executable_p, Sfile_executable_p, 1, 1, 0, @@ -2743,21 +2771,7 @@ For a directory, this means you can access files in that directory. purpose, though.) */) (Lisp_Object filename) { - Lisp_Object absname; - Lisp_Object handler; - - CHECK_STRING (filename); - absname = Fexpand_file_name (filename, Qnil); - - /* If the file name has special constructs in it, - call the corresponding file name handler. */ - handler = Ffind_file_name_handler (absname, Qfile_executable_p); - if (!NILP (handler)) - return call2 (handler, Qfile_executable_p, absname); - - absname = ENCODE_FILE (absname); - - return (check_executable (SSDATA (absname)) ? Qt : Qnil); + return check_file_access (filename, Qfile_executable_p, X_OK); } DEFUN ("file-readable-p", Ffile_readable_p, Sfile_readable_p, 1, 1, 0, @@ -2765,21 +2779,7 @@ DEFUN ("file-readable-p", Ffile_readable_p, Sfile_readable_p, 1, 1, 0, See also `file-exists-p' and `file-attributes'. */) (Lisp_Object filename) { - Lisp_Object absname; - Lisp_Object handler; - - CHECK_STRING (filename); - absname = Fexpand_file_name (filename, Qnil); - - /* If the file name has special constructs in it, - call the corresponding file name handler. */ - handler = Ffind_file_name_handler (absname, Qfile_readable_p); - if (!NILP (handler)) - return call2 (handler, Qfile_readable_p, absname); - - absname = ENCODE_FILE (absname); - return (faccessat (AT_FDCWD, SSDATA (absname), R_OK, AT_EACCESS) == 0 - ? Qt : Qnil); + return check_file_access (filename, Qfile_readable_p, R_OK); } DEFUN ("file-writable-p", Ffile_writable_p, Sfile_writable_p, 1, 1, 0, @@ -2789,7 +2789,6 @@ DEFUN ("file-writable-p", Ffile_writable_p, Sfile_writable_p, 1, 1, 0, Lisp_Object absname, dir, encoded; Lisp_Object handler; - CHECK_STRING (filename); absname = Fexpand_file_name (filename, Qnil); /* If the file name has special constructs in it, @@ -2799,7 +2798,7 @@ DEFUN ("file-writable-p", Ffile_writable_p, Sfile_writable_p, 1, 1, 0, return call2 (handler, Qfile_writable_p, absname); encoded = ENCODE_FILE (absname); - if (check_writable (SSDATA (encoded), W_OK)) + if (file_access_p (SSDATA (encoded), W_OK)) return Qt; if (errno != ENOENT) return Qnil; @@ -2810,14 +2809,23 @@ DEFUN ("file-writable-p", Ffile_writable_p, Sfile_writable_p, 1, 1, 0, dir = Fdirectory_file_name (dir); #endif /* MSDOS */ - dir = ENCODE_FILE (dir); + encoded = ENCODE_FILE (dir); #ifdef WINDOWSNT /* The read-only attribute of the parent directory doesn't affect whether a file or directory can be created within it. Some day we should check ACLs though, which do affect this. */ - return file_directory_p (dir) ? Qt : Qnil; + return file_directory_p (encoded) ? Qt : Qnil; #else - return check_writable (SSDATA (dir), W_OK | X_OK) ? Qt : Qnil; + if (file_access_p (SSDATA (encoded), W_OK | X_OK)) + return Qt; + int err = errno; + if (err == EROFS + || (err == EACCES && file_access_p (SSDATA (encoded), F_OK))) + { + errno = err; + return Qnil; + } + return file_test_errno (absname, err); #endif } @@ -2849,8 +2857,8 @@ If there is no error, returns nil. */) } /* Relative to directory FD, return the symbolic link value of FILENAME. - On failure, return nil. */ -Lisp_Object + On failure, return nil (setting errno). */ +static Lisp_Object emacs_readlinkat (int fd, char const *filename) { static struct allocator const emacs_norealloc_allocator = @@ -2869,6 +2877,27 @@ emacs_readlinkat (int fd, char const *filename) return val; } +/* Relative to directory FD, return the symbolic link value of FILE. + If FILE is not a symbolic link, return nil (setting errno). + Signal an error if the result cannot be determined. */ +Lisp_Object +check_emacs_readlinkat (int fd, Lisp_Object file, char const *encoded_file) +{ + Lisp_Object val = emacs_readlinkat (fd, encoded_file); + if (NILP (val)) + { + if (errno == EINVAL) + return val; +#ifdef CYGWIN + /* Work around Cygwin bugs. */ + if (errno == EIO || errno == EACCES) + return val; +#endif + return file_metadata_errno ("Reading symbolic link", file, errno); + } + return val; +} + DEFUN ("file-symlink-p", Ffile_symlink_p, Sfile_symlink_p, 1, 1, 0, doc: /* Return non-nil if file FILENAME is the name of a symbolic link. The value is the link target, as a string. @@ -2888,9 +2917,8 @@ This function does not check whether the link target exists. */) if (!NILP (handler)) return call2 (handler, Qfile_symlink_p, filename); - filename = ENCODE_FILE (filename); - - return emacs_readlinkat (AT_FDCWD, SSDATA (filename)); + return check_emacs_readlinkat (AT_FDCWD, filename, + SSDATA (ENCODE_FILE (filename))); } DEFUN ("file-directory-p", Ffile_directory_p, Sfile_directory_p, 1, 1, 0, @@ -2907,9 +2935,9 @@ See `file-symlink-p' to distinguish symlinks. */) if (!NILP (handler)) return call2 (handler, Qfile_directory_p, absname); - absname = ENCODE_FILE (absname); - - return file_directory_p (absname) ? Qt : Qnil; + if (file_directory_p (absname)) + return Qt; + return file_test_errno (absname, errno); } /* Return true if FILE is a directory or a symlink to a directory. @@ -2934,7 +2962,7 @@ file_directory_p (Lisp_Object file) /* O_PATH is defined but evidently this Linux kernel predates 2.6.39. Fall back on generic POSIX code. */ # endif - /* Use file_accessible_directory, as it avoids stat EOVERFLOW + /* Use file_accessible_directory_p, as it avoids stat EOVERFLOW problems and could be cheaper. However, if it fails because FILE is inaccessible, fall back on stat; if the latter fails with EOVERFLOW then FILE must have been a directory unless a race @@ -2990,8 +3018,13 @@ really is a readable and searchable directory. */) return r; } - absname = ENCODE_FILE (absname); - return file_accessible_directory_p (absname) ? Qt : Qnil; + Lisp_Object encoded_absname = ENCODE_FILE (absname); + if (file_accessible_directory_p (encoded_absname)) + return Qt; + int err = errno; + if (err == EACCES && file_access_p (SSDATA (encoded_absname), F_OK)) + return Qnil; + return file_test_errno (absname, err); } /* If FILE is a searchable directory or a symlink to a @@ -3043,7 +3076,7 @@ file_accessible_directory_p (Lisp_Object file) dir = buf; } - ok = check_existing (dir); + ok = file_access_p (dir, F_OK); saved_errno = errno; SAFE_FREE (); errno = saved_errno; @@ -3067,27 +3100,21 @@ See `file-symlink-p' to distinguish symlinks. */) if (!NILP (handler)) return call2 (handler, Qfile_regular_p, absname); - absname = ENCODE_FILE (absname); - #ifdef WINDOWSNT - { - int result; - Lisp_Object tem = Vw32_get_true_file_attributes; + /* Tell stat to use expensive method to get accurate info. */ + Lisp_Object true_attributes = Vw32_get_true_file_attributes; + Vw32_get_true_file_attributes = Qt; +#endif - /* Tell stat to use expensive method to get accurate info. */ - Vw32_get_true_file_attributes = Qt; - result = stat (SSDATA (absname), &st); - Vw32_get_true_file_attributes = tem; + int stat_result = stat (SSDATA (absname), &st); - if (result < 0) - return Qnil; - return S_ISREG (st.st_mode) ? Qt : Qnil; - } -#else - if (stat (SSDATA (absname), &st) < 0) - return Qnil; - return S_ISREG (st.st_mode) ? Qt : Qnil; +#ifdef WINDOWSNT + Vw32_get_true_file_attributes = true_attributes; #endif + + if (stat_result == 0) + return S_ISREG (st.st_mode) ? Qt : Qnil; + return file_test_errno (absname, errno); } DEFUN ("file-selinux-context", Ffile_selinux_context, @@ -3097,7 +3124,7 @@ The return value is a list (USER ROLE TYPE RANGE), where the list elements are strings naming the user, role, type, and range of the file's SELinux security context. -Return (nil nil nil nil) if the file is nonexistent or inaccessible, +Return (nil nil nil nil) if the file is nonexistent, or if SELinux is disabled, or if Emacs lacks SELinux support. */) (Lisp_Object filename) { @@ -3111,13 +3138,11 @@ or if SELinux is disabled, or if Emacs lacks SELinux support. */) if (!NILP (handler)) return call2 (handler, Qfile_selinux_context, absname); - absname = ENCODE_FILE (absname); - #if HAVE_LIBSELINUX if (is_selinux_enabled ()) { security_context_t con; - int conlength = lgetfilecon (SSDATA (absname), &con); + int conlength = lgetfilecon (SSDATA (ENCODE_FILE (absname)), &con); if (conlength > 0) { context_t context = context_new (con); @@ -3132,6 +3157,9 @@ or if SELinux is disabled, or if Emacs lacks SELinux support. */) context_free (context); freecon (con); } + else if (! (errno == ENOENT || errno == ENOTDIR || errno == ENODATA + || errno == ENOTSUP)) + report_file_error ("getting SELinux context", absname); } #endif @@ -3227,8 +3255,7 @@ DEFUN ("file-acl", Ffile_acl, Sfile_acl, 1, 1, 0, doc: /* Return ACL entries of file named FILENAME. The entries are returned in a format suitable for use in `set-file-acl' but is otherwise undocumented and subject to change. -Return nil if file does not exist or is not accessible, or if Emacs -was unable to determine the ACL entries. */) +Return nil if file does not exist. */) (Lisp_Object filename) { Lisp_Object acl_string = Qnil; @@ -3243,20 +3270,22 @@ was unable to determine the ACL entries. */) return call2 (handler, Qfile_acl, absname); # ifdef HAVE_ACL_SET_FILE - absname = ENCODE_FILE (absname); - # ifndef HAVE_ACL_TYPE_EXTENDED acl_type_t ACL_TYPE_EXTENDED = ACL_TYPE_ACCESS; # endif - acl_t acl = acl_get_file (SSDATA (absname), ACL_TYPE_EXTENDED); + acl_t acl = acl_get_file (SSDATA (ENCODE_FILE (absname)), ACL_TYPE_EXTENDED); if (acl == NULL) - return Qnil; - + { + if (errno == ENOENT || errno == ENOTDIR || errno == ENOTSUP) + return Qnil; + report_file_error ("Getting ACLs", absname); + } char *str = acl_to_text (acl, NULL); if (str == NULL) { + int err = errno; acl_free (acl); - return Qnil; + report_file_errno ("Getting ACLs", absname, err); } acl_string = build_string (str); @@ -3327,7 +3356,7 @@ support. */) DEFUN ("file-modes", Ffile_modes, Sfile_modes, 1, 1, 0, doc: /* Return mode bits of file named FILENAME, as an integer. -Return nil, if file does not exist or is not accessible. */) +Return nil if FILENAME does not exist. */) (Lisp_Object filename) { struct stat st; @@ -3339,11 +3368,8 @@ Return nil, if file does not exist or is not accessible. */) if (!NILP (handler)) return call2 (handler, Qfile_modes, absname); - absname = ENCODE_FILE (absname); - - if (stat (SSDATA (absname), &st) < 0) - return Qnil; - + if (stat (SSDATA (ENCODE_FILE (absname)), &st) != 0) + return file_attribute_errno (absname, errno); return make_fixnum (st.st_mode & 07777); } @@ -3487,14 +3513,27 @@ otherwise, if FILE2 does not exist, the answer is t. */) if (!NILP (handler)) return call3 (handler, Qfile_newer_than_file_p, absname1, absname2); - absname1 = ENCODE_FILE (absname1); - absname2 = ENCODE_FILE (absname2); + int err1; + if (stat (SSDATA (ENCODE_FILE (absname1)), &st1) == 0) + err1 = 0; + else + { + err1 = errno; + if (err1 != EOVERFLOW) + return file_test_errno (absname1, err1); + } - if (stat (SSDATA (absname1), &st1) < 0) - return Qnil; + if (stat (SSDATA (ENCODE_FILE (absname2)), &st2) != 0) + { + file_test_errno (absname2, errno); + return Qt; + } - if (stat (SSDATA (absname2), &st2) < 0) - return Qt; + if (err1) + { + file_test_errno (absname1, err1); + eassume (false); + } return (timespec_cmp (get_stat_mtime (&st2), get_stat_mtime (&st1)) < 0 ? Qt : Qnil); @@ -5686,13 +5725,13 @@ in `current-time' or an integer flag as returned by `visited-file-modtime'. */) /* The handler can find the file name the same way we did. */ return call2 (handler, Qset_visited_file_modtime, Qnil); - filename = ENCODE_FILE (filename); - - if (stat (SSDATA (filename), &st) >= 0) + if (stat (SSDATA (ENCODE_FILE (filename)), &st) == 0) { current_buffer->modtime = get_stat_mtime (&st); current_buffer->modtime_size = st.st_size; } + else + file_attribute_errno (filename, errno); } return Qnil; @@ -6103,22 +6142,22 @@ storage available to a non-superuser. All 3 numbers are in bytes. If the underlying system call fails, value is nil. */) (Lisp_Object filename) { - Lisp_Object encoded = ENCODE_FILE (Fexpand_file_name (filename, Qnil)); + filename = Fexpand_file_name (filename, Qnil); /* If the file name has special constructs in it, call the corresponding file name handler. */ - Lisp_Object handler = Ffind_file_name_handler (encoded, Qfile_system_info); + Lisp_Object handler = Ffind_file_name_handler (filename, Qfile_system_info); if (!NILP (handler)) { - Lisp_Object result = call2 (handler, Qfile_system_info, encoded); + Lisp_Object result = call2 (handler, Qfile_system_info, filename); if (CONSP (result) || NILP (result)) return result; error ("Invalid handler in `file-name-handler-alist'"); } struct fs_usage u; - if (get_fs_usage (SSDATA (encoded), NULL, &u) != 0) - return Qnil; + if (get_fs_usage (SSDATA (ENCODE_FILE (filename)), NULL, &u) != 0) + return errno == ENOSYS ? Qnil : file_attribute_errno (filename, errno); return list3 (blocks_to_bytes (u.fsu_blocksize, u.fsu_blocks, false), blocks_to_bytes (u.fsu_blocksize, u.fsu_bfree, false), blocks_to_bytes (u.fsu_blocksize, u.fsu_bavail, diff --git a/src/filelock.c b/src/filelock.c index 46349a63e4a..ff25d6475de 100644 --- a/src/filelock.c +++ b/src/filelock.c @@ -504,9 +504,9 @@ read_lock_data (char *lfname, char lfinfo[MAX_LFINFO + 1]) } /* Return 0 if nobody owns the lock file LFNAME or the lock is obsolete, - 1 if another process owns it (and set OWNER (if non-null) to info), - 2 if the current process owns it, - or -1 if something is wrong with the locking mechanism. */ + -1 if another process owns it (and set OWNER (if non-null) to info), + -2 if the current process owns it, + or an errno value if something is wrong with the locking mechanism. */ static int current_lock_owner (lock_info_type *owner, char *lfname) @@ -525,23 +525,23 @@ current_lock_owner (lock_info_type *owner, char *lfname) /* If nonexistent lock file, all is well; otherwise, got strange error. */ lfinfolen = read_lock_data (lfname, owner->user); if (lfinfolen < 0) - return errno == ENOENT ? 0 : -1; + return errno == ENOENT ? 0 : errno; if (MAX_LFINFO < lfinfolen) - return -1; + return ENAMETOOLONG; owner->user[lfinfolen] = 0; - /* Parse USER@HOST.PID:BOOT_TIME. If can't parse, return -1. */ + /* Parse USER@HOST.PID:BOOT_TIME. If can't parse, return EINVAL. */ /* The USER is everything before the last @. */ owner->at = at = memrchr (owner->user, '@', lfinfolen); if (!at) - return -1; + return EINVAL; owner->dot = dot = strrchr (at, '.'); if (!dot) - return -1; + return EINVAL; /* The PID is everything from the last '.' to the ':' or equivalent. */ if (! c_isdigit (dot[1])) - return -1; + return EINVAL; errno = 0; pid = strtoimax (dot + 1, &owner->colon, 10); if (errno == ERANGE) @@ -562,20 +562,20 @@ current_lock_owner (lock_info_type *owner, char *lfname) mistakenly transliterate ':' to U+F022 in symlink contents. See . */ if (! (boot[0] == '\200' && boot[1] == '\242')) - return -1; + return EINVAL; boot += 2; FALLTHROUGH; case ':': if (! c_isdigit (boot[0])) - return -1; + return EINVAL; boot_time = strtoimax (boot, &lfinfo_end, 10); break; default: - return -1; + return EINVAL; } if (lfinfo_end != owner->user + lfinfolen) - return -1; + return EINVAL; /* On current host? */ Lisp_Object system_name = Fsystem_name (); @@ -584,22 +584,22 @@ current_lock_owner (lock_info_type *owner, char *lfname) && memcmp (at + 1, SSDATA (system_name), SBYTES (system_name)) == 0) { if (pid == getpid ()) - ret = 2; /* We own it. */ + ret = -2; /* We own it. */ else if (0 < pid && pid <= TYPE_MAXIMUM (pid_t) && (kill (pid, 0) >= 0 || errno == EPERM) && (boot_time == 0 || (boot_time <= TYPE_MAXIMUM (time_t) && within_one_second (boot_time, get_boot_time ())))) - ret = 1; /* An existing process on this machine owns it. */ + ret = -1; /* An existing process on this machine owns it. */ /* The owner process is dead or has a strange pid, so try to zap the lockfile. */ else - return unlink (lfname); + return unlink (lfname) < 0 ? errno : 0; } else { /* If we wanted to support the check for stale locks on remote machines, here's where we'd do it. */ - ret = 1; + ret = -1; } return ret; @@ -608,9 +608,9 @@ current_lock_owner (lock_info_type *owner, char *lfname) /* Lock the lock named LFNAME if possible. Return 0 in that case. - Return positive if some other process owns the lock, and info about + Return negative if some other process owns the lock, and info about that process in CLASHER. - Return -1 if cannot lock for any other reason. */ + Return positive errno value if cannot lock for any other reason. */ static int lock_if_free (lock_info_type *clasher, char *lfname) @@ -618,20 +618,18 @@ lock_if_free (lock_info_type *clasher, char *lfname) int err; while ((err = lock_file_1 (lfname, 0)) == EEXIST) { - switch (current_lock_owner (clasher, lfname)) + err = current_lock_owner (clasher, lfname); + if (err != 0) { - case 2: - return 0; /* We ourselves locked it. */ - case 1: - return 1; /* Someone else has it. */ - case -1: - return -1; /* current_lock_owner returned strange error. */ + if (err < 0) + return -2 - err; /* We locked it, or someone else has it. */ + break; /* current_lock_owner returned strange error. */ } /* We deleted a stale lock; try again to lock the file. */ } - return err ? -1 : 0; + return err; } /* lock_file locks file FN, @@ -697,8 +695,9 @@ lock_file (Lisp_Object fn) /* Create the name of the lock-file for file fn */ MAKE_LOCK_NAME (lfname, encoded_fn); - /* Try to lock the lock. */ - if (0 < lock_if_free (&lock_info, lfname)) + /* Try to lock the lock. FIXME: This ignores errors when + lock_if_free returns a positive errno value. */ + if (lock_if_free (&lock_info, lfname) < 0) { /* Someone else has the lock. Consider breaking it. */ Lisp_Object attack; @@ -725,13 +724,16 @@ unlock_file (Lisp_Object fn) char *lfname; USE_SAFE_ALLOCA; - fn = Fexpand_file_name (fn, Qnil); - fn = ENCODE_FILE (fn); + Lisp_Object filename = Fexpand_file_name (fn, Qnil); + fn = ENCODE_FILE (filename); MAKE_LOCK_NAME (lfname, fn); - if (current_lock_owner (0, lfname) == 2) - unlink (lfname); + int err = current_lock_owner (0, lfname); + if (err == -2 && unlink (lfname) != 0 && errno != ENOENT) + err = errno; + if (0 < err) + report_file_errno ("Unlocking file", filename, err); SAFE_FREE (); } @@ -822,17 +824,17 @@ t if it is locked by you, else a string saying which user has locked it. */) USE_SAFE_ALLOCA; filename = Fexpand_file_name (filename, Qnil); - filename = ENCODE_FILE (filename); - - MAKE_LOCK_NAME (lfname, filename); + Lisp_Object encoded_filename = ENCODE_FILE (filename); + MAKE_LOCK_NAME (lfname, encoded_filename); owner = current_lock_owner (&locker, lfname); - if (owner <= 0) - ret = Qnil; - else if (owner == 2) - ret = Qt; - else - ret = make_string (locker.user, locker.at - locker.user); + switch (owner) + { + case -2: ret = Qt; break; + case -1: ret = make_string (locker.user, locker.at - locker.user); break; + case 0: ret = Qnil; break; + default: report_file_errno ("Testing file lock", filename, owner); + } SAFE_FREE (); return ret; diff --git a/src/lisp.h b/src/lisp.h index 02f8a7b6686..e68d2732e21 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -4299,7 +4299,6 @@ extern void syms_of_marker (void); /* Defined in fileio.c. */ -extern bool check_executable (char *); extern char *splice_dir_file (char *, char const *, char const *); extern bool file_name_absolute_p (const char *); extern char const *get_homedir (void); @@ -4310,12 +4309,14 @@ extern Lisp_Object write_region (Lisp_Object, Lisp_Object, Lisp_Object, extern void close_file_unwind (int); extern void fclose_unwind (void *); extern void restore_point_unwind (Lisp_Object); +extern bool file_access_p (char const *, int); extern Lisp_Object get_file_errno_data (const char *, Lisp_Object, int); extern AVOID report_file_errno (const char *, Lisp_Object, int); extern AVOID report_file_error (const char *, Lisp_Object); extern AVOID report_file_notify_error (const char *, Lisp_Object); +extern Lisp_Object file_attribute_errno (Lisp_Object, int); extern bool internal_delete_file (Lisp_Object); -extern Lisp_Object emacs_readlinkat (int, const char *); +extern Lisp_Object check_emacs_readlinkat (int, Lisp_Object, char const *); extern bool file_directory_p (Lisp_Object); extern bool file_accessible_directory_p (Lisp_Object); extern void init_fileio (void); diff --git a/src/lread.c b/src/lread.c index 6ae7a0d8ba0..d8883db46c1 100644 --- a/src/lread.c +++ b/src/lread.c @@ -1346,15 +1346,22 @@ Return t if the file exists and loads successfully. */) if (!load_prefer_newer && is_elc) { result = stat (SSDATA (efound), &s1); + int err = errno; if (result == 0) { SSET (efound, SBYTES (efound) - 1, 0); result = stat (SSDATA (efound), &s2); + err = errno; SSET (efound, SBYTES (efound) - 1, 'c'); + if (result != 0) + found = Fsubstring (found, make_fixnum (0), + make_fixnum (-1)); } - - if (result == 0 - && timespec_cmp (get_stat_mtime (&s1), get_stat_mtime (&s2)) < 0) + if (result != 0) + file_attribute_errno (found, err); + else if (timespec_cmp (get_stat_mtime (&s1), + get_stat_mtime (&s2)) + < 0) { /* Make the progress messages mention that source is newer. */ newer = 1; @@ -1748,16 +1755,20 @@ openp (Lisp_Object path, Lisp_Object str, Lisp_Object suffixes, { if (file_directory_p (encoded_fn)) last_errno = EISDIR; - else + else if (errno == ENOENT || errno == ENOTDIR) fd = 1; + else + last_errno = errno; } + else if (! (errno == ENOENT || errno == ENOTDIR)) + last_errno = errno; } else { fd = emacs_open (pfn, O_RDONLY, 0); if (fd < 0) { - if (errno != ENOENT) + if (! (errno == ENOENT || errno == ENOTDIR)) last_errno = errno; } else -- 2.39.2