Commit 9dc306b1 authored by Paul Eggert's avatar Paul Eggert

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.
parent ae3edf0a
Pipeline #3184 passed with stage
in 53 minutes and 50 seconds
......@@ -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
......
......@@ -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
......
......@@ -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);
}
......
......@@ -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';
......
This diff is collapsed.
......@@ -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 <https://bugzilla.redhat.com/show_bug.cgi?id=1384153>. */
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;
......
......@@ -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);
......
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment