summaryrefslogtreecommitdiff
path: root/src/fileio.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/fileio.c')
-rw-r--r--src/fileio.c820
1 files changed, 510 insertions, 310 deletions
diff --git a/src/fileio.c b/src/fileio.c
index a5d29d81fb7..12da7a9ed3a 100644
--- a/src/fileio.c
+++ b/src/fileio.c
@@ -56,6 +56,10 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include "region-cache.h"
#include "frame.h"
+#ifdef HAVE_ANDROID
+#include "android.h"
+#endif /* HAVE_ANDROID */
+
#ifdef HAVE_LINUX_FS_H
# include <sys/ioctl.h>
# include <linux/fs.h>
@@ -109,6 +113,42 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include "commands.h"
+#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY
+
+/* Type describing a file descriptor used by functions such as
+ `insert-file-contents'. */
+
+typedef int emacs_fd;
+
+/* Function used to read and open from such a file descriptor. */
+
+#define emacs_fd_open emacs_open
+#define emacs_fd_close emacs_close
+#define emacs_fd_read emacs_read_quit
+#define emacs_fd_lseek lseek
+#define emacs_fd_fstat sys_fstat
+#define emacs_fd_valid_p(fd) ((fd) >= 0)
+
+/* This is not used on MS Windows. */
+
+#ifndef WINDOWSNT
+#define emacs_fd_to_int(fds) (fds)
+#endif /* WINDOWSNT */
+
+#else /* HAVE_ANDROID && !defined ANDROID_STUBIFY */
+
+typedef struct android_fd_or_asset emacs_fd;
+
+#define emacs_fd_open android_open_asset
+#define emacs_fd_close android_close_asset
+#define emacs_fd_read android_asset_read_quit
+#define emacs_fd_lseek android_asset_lseek
+#define emacs_fd_fstat android_asset_fstat
+#define emacs_fd_valid_p(fd) ((fd).asset != ((void *) -1))
+#define emacs_fd_to_int(fds) ((fds).asset ? -1 : (fds).fd)
+
+#endif /* !defined HAVE_ANDROID || defined ANDROID_STUBIFY */
+
/* True during writing of auto-save files. */
static bool auto_saving;
@@ -134,13 +174,52 @@ static dev_t timestamp_file_system;
is added here. */
static Lisp_Object Vwrite_region_annotation_buffers;
-static Lisp_Object file_name_directory (Lisp_Object);
+static Lisp_Object emacs_readlinkat (int, char const *);
static bool a_write (int, Lisp_Object, ptrdiff_t, ptrdiff_t,
Lisp_Object *, struct coding_system *);
static bool e_write (int, Lisp_Object, ptrdiff_t, ptrdiff_t,
struct coding_system *);
+
+/* Establish that ENCODED is not contained within a special directory
+ whose contents are not eligible for Unix VFS operations. Signal a
+ `file-error' with REASON if it does. */
+
+static void
+check_vfs_filename (Lisp_Object encoded, const char *reason)
+{
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+ const char *name;
+
+ name = SSDATA (encoded);
+
+ if (android_is_special_directory (name, "/assets")
+ || android_is_special_directory (name, "/content"))
+ xsignal2 (Qfile_error, build_string (reason), encoded);
+#endif /* defined HAVE_ANDROID && !defined ANDROID_STUBIFY */
+}
+
+#ifdef HAVE_LIBSELINUX
+
+/* Return whether SELinux is enabled and pertinent to FILE. Provide
+ for cases where FILE is or is a constituent of a special
+ directory, such as /assets or /content on Android. */
+
+static bool
+selinux_enabled_p (const char *file)
+{
+ return (is_selinux_enabled ()
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+ && !android_is_special_directory (file, "/assets")
+ && !android_is_special_directory (file, "/content")
+#endif /* defined HAVE_ANDROID && !defined ANDROID_STUBIFY */
+ );
+}
+
+#endif /* HAVE_LIBSELINUX */
+
+
/* Test whether FILE is accessible for AMODE.
Return true if successful, false (setting errno) otherwise. */
@@ -159,7 +238,7 @@ file_access_p (char const *file, int amode)
}
#endif
- if (faccessat (AT_FDCWD, file, amode, AT_EACCESS) == 0)
+ if (sys_faccessat (AT_FDCWD, file, amode, AT_EACCESS) == 0)
return true;
#ifdef CYGWIN
@@ -263,11 +342,20 @@ close_file_unwind (int fd)
emacs_close (fd);
}
+static void
+close_file_unwind_emacs_fd (void *ptr)
+{
+ emacs_fd *fd;
+
+ fd = ptr;
+ emacs_fd_close (*fd);
+}
+
void
fclose_unwind (void *arg)
{
FILE *stream = arg;
- fclose (stream);
+ emacs_fclose (stream);
}
/* Restore point, having saved it as a marker. */
@@ -369,7 +457,7 @@ Given a Unix syntax file name, returns a string ending in slash. */)
/* Return the directory component of FILENAME, or nil if FILENAME does
not contain a directory component. */
-static Lisp_Object
+Lisp_Object
file_name_directory (Lisp_Object filename)
{
char *beg = SSDATA (filename);
@@ -755,7 +843,7 @@ For that reason, you should normally use `make-temp-file' instead. */)
DEFUN ("file-name-concat", Ffile_name_concat, Sfile_name_concat, 1, MANY, 0,
doc: /* Append COMPONENTS to DIRECTORY and return the resulting string.
-Elements in COMPONENTS must be a string or nil.
+Each element in COMPONENTS must be a string or nil.
DIRECTORY or the non-final elements in COMPONENTS may or may not end
with a slash -- if they don't end with a slash, a slash will be
inserted before concatenating.
@@ -888,6 +976,10 @@ user_homedir (char const *name)
p[length] = 0;
struct passwd *pw = getpwnam (p);
SAFE_FREE ();
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+ if (pw && !pw->pw_dir && pw->pw_uid == getuid ())
+ return (char *) android_get_home_directory ();
+#endif
if (!pw || (pw->pw_dir && !IS_ABSOLUTE_FILE_NAME (pw->pw_dir)))
return NULL;
return pw->pw_dir;
@@ -1878,6 +1970,11 @@ get_homedir (void)
pw = getpwuid (getuid ());
if (pw)
home = pw->pw_dir;
+
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+ if (!home && pw && pw->pw_uid == getuid ())
+ return android_get_home_directory ();
+#endif
if (!home)
return "";
}
@@ -2176,7 +2273,8 @@ permissions. */)
#else
bool already_exists = false;
mode_t new_mask;
- int ifd, ofd;
+ emacs_fd ifd;
+ int ofd;
struct stat st;
#endif
@@ -2219,26 +2317,31 @@ permissions. */)
report_file_error ("Copying permissions to", newname);
}
#else /* not WINDOWSNT */
- ifd = emacs_open (SSDATA (encoded_file), O_RDONLY, 0);
+ ifd = emacs_fd_open (SSDATA (encoded_file), O_RDONLY | O_NONBLOCK, 0);
- if (ifd < 0)
+ if (!emacs_fd_valid_p (ifd))
report_file_error ("Opening input file", file);
- record_unwind_protect_int (close_file_unwind, ifd);
+ record_unwind_protect_ptr (close_file_unwind_emacs_fd, &ifd);
- if (fstat (ifd, &st) != 0)
+ if (emacs_fd_fstat (ifd, &st) != 0)
report_file_error ("Input file status", file);
if (!NILP (preserve_permissions))
{
#if HAVE_LIBSELINUX
- if (is_selinux_enabled ())
+ if (selinux_enabled_p (SSDATA (encoded_file))
+ /* Eschew copying SELinux contexts if they're inapplicable
+ to the destination file. */
+ && selinux_enabled_p (SSDATA (encoded_newname))
+ && emacs_fd_to_int (ifd) != -1)
{
- conlength = fgetfilecon (ifd, &con);
+ conlength = fgetfilecon (emacs_fd_to_int (ifd),
+ &con);
if (conlength == -1)
report_file_error ("Doing fgetfilecon", file);
}
-#endif
+#endif /* HAVE_LIBSELINUX */
}
/* We can copy only regular files. */
@@ -2272,7 +2375,7 @@ permissions. */)
if (already_exists)
{
struct stat out_st;
- if (fstat (ofd, &out_st) != 0)
+ if (sys_fstat (ofd, &out_st) != 0)
report_file_error ("Output file status", newname);
if (st.st_dev == out_st.st_dev && st.st_ino == out_st.st_ino)
report_file_errno ("Input and output files are the same",
@@ -2283,7 +2386,8 @@ permissions. */)
maybe_quit ();
- if (clone_file (ofd, ifd))
+ if (emacs_fd_to_int (ifd) != -1
+ && clone_file (ofd, emacs_fd_to_int (ifd)))
newsize = st.st_size;
else
{
@@ -2291,30 +2395,38 @@ permissions. */)
ssize_t copied;
#ifndef MSDOS
- for (newsize = 0; newsize < insize; newsize += copied)
+ newsize = 0;
+
+ if (emacs_fd_to_int (ifd) != -1)
{
- /* Copy at most COPY_MAX bytes at a time; this is min
- (PTRDIFF_MAX, SIZE_MAX) truncated to a value that is
- surely aligned well. */
- ssize_t ssize_max = TYPE_MAXIMUM (ssize_t);
- ptrdiff_t copy_max = min (ssize_max, SIZE_MAX) >> 30 << 30;
- off_t intail = insize - newsize;
- ptrdiff_t len = min (intail, copy_max);
- copied = copy_file_range (ifd, NULL, ofd, NULL, len, 0);
- if (copied <= 0)
- break;
- maybe_quit ();
+ for (; newsize < insize; newsize += copied)
+ {
+ /* Copy at most COPY_MAX bytes at a time; this is min
+ (PTRDIFF_MAX, SIZE_MAX) truncated to a value that is
+ surely aligned well. */
+ ssize_t ssize_max = TYPE_MAXIMUM (ssize_t);
+ ptrdiff_t copy_max = min (ssize_max, SIZE_MAX) >> 30 << 30;
+ off_t intail = insize - newsize;
+ ptrdiff_t len = min (intail, copy_max);
+ copied = copy_file_range (emacs_fd_to_int (ifd), NULL,
+ ofd, NULL, len, 0);
+ if (copied <= 0)
+ break;
+ maybe_quit ();
+ }
}
#endif /* MSDOS */
/* Fall back on read+write if copy_file_range failed, or if the
- input is empty and so could be a /proc file. read+write will
- either succeed, or report an error more precisely than
- copy_file_range would. */
+ input is empty and so could be a /proc file, or if ifd is an
+ invention of android.c. read+write will either succeed, or
+ report an error more precisely than copy_file_range
+ would. */
if (newsize != insize || insize == 0)
{
char buf[MAX_ALLOCA];
- for (; (copied = emacs_read_quit (ifd, buf, sizeof buf));
+
+ for (; (copied = emacs_fd_read (ifd, buf, sizeof buf));
newsize += copied)
{
if (copied < 0)
@@ -2362,8 +2474,10 @@ permissions. */)
}
}
- switch (!NILP (preserve_permissions)
- ? qcopy_acl (SSDATA (encoded_file), ifd,
+ switch ((!NILP (preserve_permissions)
+ && emacs_fd_to_int (ifd) != -1)
+ ? qcopy_acl (SSDATA (encoded_file),
+ emacs_fd_to_int (ifd),
SSDATA (encoded_newname), ofd,
preserved_permissions)
: (already_exists
@@ -2382,11 +2496,18 @@ permissions. */)
{
/* Set the modified context back to the file. */
bool fail = fsetfilecon (ofd, con) != 0;
+ freecon (con);
+
/* See https://debbugs.gnu.org/11245 for ENOTSUP. */
- if (fail && errno != ENOTSUP)
+ if (fail
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+ /* Treat SELinux errors copying files leniently on Android,
+ since the system usually forbids user programs from
+ changing file contexts. */
+ && errno != EACCES
+#endif /* defined HAVE_ANDROID && !defined ANDROID_STUBIFY */
+ && errno != ENOTSUP)
report_file_error ("Doing fsetfilecon", newname);
-
- freecon (con);
}
#endif
@@ -2395,7 +2516,17 @@ permissions. */)
struct timespec ts[2];
ts[0] = get_stat_atime (&st);
ts[1] = get_stat_mtime (&st);
- if (futimens (ofd, ts) != 0)
+ if (futimens (ofd, ts) != 0
+ /* Various versions of the Android C library are missing
+ futimens, prompting Gnulib to install a fallback that
+ uses fdutimens instead. However, fdutimens is not
+ supported on many Android kernels, so just silently fail
+ if errno is ENOTSUP or ENOSYS. */
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+ && errno != ENOTSUP
+ && errno != ENOSYS
+#endif
+ )
xsignal2 (Qfile_date_error,
build_string ("Cannot set file date"), newname);
}
@@ -2403,7 +2534,9 @@ permissions. */)
if (emacs_close (ofd) < 0)
report_file_error ("Write error", newname);
- emacs_close (ifd);
+ /* Note that ifd is not closed twice because unwind_protects are
+ discarded at the end of this function. */
+ emacs_fd_close (ifd);
#ifdef MSDOS
/* In DJGPP v2.0 and later, fstat usually returns true file mode bits,
@@ -2436,7 +2569,7 @@ DEFUN ("make-directory-internal", Fmake_directory_internal,
dir = SSDATA (encoded_dir);
- if (mkdir (dir, 0777 & ~auto_saving_dir_umask) != 0)
+ if (emacs_mkdir (dir, 0777 & ~auto_saving_dir_umask) != 0)
report_file_error ("Creating directory", directory);
return Qnil;
@@ -2455,47 +2588,26 @@ DEFUN ("delete-directory-internal", Fdelete_directory_internal,
encoded_dir = ENCODE_FILE (directory);
dir = SSDATA (encoded_dir);
- if (rmdir (dir) != 0)
+ if (emacs_rmdir (dir) != 0)
report_file_error ("Removing directory", directory);
return Qnil;
}
-DEFUN ("delete-file", Fdelete_file, Sdelete_file, 1, 2,
- "(list (read-file-name \
- (if (and delete-by-moving-to-trash (null current-prefix-arg)) \
- \"Move file to trash: \" \"Delete file: \") \
- nil default-directory (confirm-nonexistent-file-or-buffer)) \
- (null current-prefix-arg))",
- doc: /* Delete file named FILENAME. If it is a symlink, remove the symlink.
-If file has multiple names, it continues to exist with the other names.
-TRASH non-nil means to trash the file instead of deleting, provided
-`delete-by-moving-to-trash' is non-nil.
-
-When called interactively, TRASH is t if no prefix argument is given.
-With a prefix argument, TRASH is nil. */)
- (Lisp_Object filename, Lisp_Object trash)
+DEFUN ("delete-file-internal", Fdelete_file_internal, Sdelete_file_internal, 1, 1, 0,
+ doc: /* Delete file named FILENAME; internal use only.
+If it is a symlink, remove the symlink.
+If file has multiple names, it continues to exist with the other names. */)
+ (Lisp_Object filename)
{
- Lisp_Object handler;
Lisp_Object encoded_file;
- if (!NILP (Ffile_directory_p (filename))
- && NILP (Ffile_symlink_p (filename)))
- xsignal2 (Qfile_error,
- build_string ("Removing old name: is a directory"),
- filename);
+ CHECK_STRING (filename);
filename = Fexpand_file_name (filename, Qnil);
-
- handler = Ffind_file_name_handler (filename, Qdelete_file);
- if (!NILP (handler))
- return call3 (handler, Qdelete_file, filename, trash);
-
- if (delete_by_moving_to_trash && !NILP (trash))
- return call1 (Qmove_file_to_trash, filename);
-
encoded_file = ENCODE_FILE (filename);
- if (unlink (SSDATA (encoded_file)) != 0 && errno != ENOENT)
+ if (emacs_unlink (SSDATA (encoded_file)) != 0
+ && errno != ENOENT)
report_file_error ("Removing old name", filename);
return Qnil;
}
@@ -2516,7 +2628,7 @@ internal_delete_file (Lisp_Object filename)
{
Lisp_Object tem;
- tem = internal_condition_case_2 (Fdelete_file, filename, Qnil,
+ tem = internal_condition_case_1 (Fdelete_file_internal, filename,
Qt, internal_delete_file_1);
return NILP (tem);
}
@@ -2524,7 +2636,7 @@ internal_delete_file (Lisp_Object filename)
#endif
/* Return -1 if FILE is a case-insensitive file name, 0 if not,
- and a positive errno value if the result cannot be determined. */
+ and 1 if the result cannot be determined. */
static int
file_name_case_insensitive_err (Lisp_Object file)
@@ -2558,7 +2670,7 @@ file_name_case_insensitive_err (Lisp_Object file)
return - (res == 0);
# endif
if (errno != EINVAL)
- return errno;
+ return 1;
#endif
#if defined CYGWIN || defined DOS_NT
@@ -2658,8 +2770,10 @@ This is what happens in interactive use with M-x. */)
int rename_errno UNINIT;
if (!plain_rename)
{
- if (renameat_noreplace (AT_FDCWD, SSDATA (encoded_file),
- AT_FDCWD, SSDATA (encoded_newname))
+ if (emacs_renameat_noreplace (AT_FDCWD,
+ SSDATA (encoded_file),
+ AT_FDCWD,
+ SSDATA (encoded_newname))
== 0)
return Qnil;
@@ -2681,7 +2795,8 @@ This is what happens in interactive use with M-x. */)
if (plain_rename)
{
- if (rename (SSDATA (encoded_file), SSDATA (encoded_newname)) == 0)
+ if (emacs_rename (SSDATA (encoded_file),
+ SSDATA (encoded_newname)) == 0)
return Qnil;
rename_errno = errno;
/* Don't prompt again. */
@@ -2705,38 +2820,26 @@ This is what happens in interactive use with M-x. */)
}
if (dirp)
call4 (Qcopy_directory, file, newname, Qt, Qnil);
- else
- {
- Lisp_Object symlink_target
- = (S_ISLNK (file_st.st_mode)
- ? check_emacs_readlinkat (AT_FDCWD, file, SSDATA (encoded_file))
- : Qnil);
- if (!NILP (symlink_target))
- Fmake_symbolic_link (symlink_target, newname, ok_if_already_exists);
- else if (S_ISFIFO (file_st.st_mode))
- {
- /* If it's a FIFO, calling `copy-file' will hang if it's a
- inter-file system move, so do it here. (It will signal
- an error in that case, but it won't hang in any case.) */
- if (!NILP (ok_if_already_exists))
- barf_or_query_if_file_exists (newname, false,
- "rename to it",
- FIXNUMP (ok_if_already_exists),
- false);
- if (rename (SSDATA (encoded_file), SSDATA (encoded_newname)) != 0)
- report_file_errno ("Renaming", list2 (file, newname), errno);
- return Qnil;
- }
+ else if (S_ISREG (file_st.st_mode))
+ Fcopy_file (file, newname, ok_if_already_exists, Qt, Qt, Qt);
+ else if (S_ISLNK (file_st.st_mode))
+ {
+ Lisp_Object target = emacs_readlinkat (AT_FDCWD,
+ SSDATA (encoded_file));
+ if (!NILP (target))
+ Fmake_symbolic_link (target, newname, ok_if_already_exists);
else
- Fcopy_file (file, newname, ok_if_already_exists, Qt, Qt, Qt);
+ report_file_error ("Renaming", list2 (file, newname));
}
+ else
+ report_file_errno ("Renaming", list2 (file, newname), rename_errno);
specpdl_ref count = SPECPDL_INDEX ();
specbind (Qdelete_by_moving_to_trash, Qnil);
if (dirp)
call2 (Qdelete_directory, file, Qt);
else
- Fdelete_file (file, Qnil);
+ call2 (Qdelete_file, file, Qnil);
return unbind_to (count, Qnil);
}
@@ -2774,6 +2877,10 @@ This is what happens in interactive use with M-x. */)
encoded_file = ENCODE_FILE (file);
encoded_newname = ENCODE_FILE (newname);
+ check_vfs_filename (encoded_file, "Trying to create hard link to "
+ "file within special directory");
+ check_vfs_filename (encoded_newname, "Trying to create hard link"
+ " within special directory");
if (link (SSDATA (encoded_file), SSDATA (encoded_newname)) == 0)
return Qnil;
@@ -2784,7 +2891,7 @@ This is what happens in interactive use with M-x. */)
|| FIXNUMP (ok_if_already_exists))
barf_or_query_if_file_exists (newname, true, "make it a new name",
FIXNUMP (ok_if_already_exists), false);
- unlink (SSDATA (newname));
+ emacs_unlink (SSDATA (newname));
if (link (SSDATA (encoded_file), SSDATA (encoded_newname)) == 0)
return Qnil;
}
@@ -2828,7 +2935,8 @@ This happens for interactive use with M-x. */)
encoded_target = ENCODE_FILE (target);
encoded_linkname = ENCODE_FILE (linkname);
- if (symlink (SSDATA (encoded_target), SSDATA (encoded_linkname)) == 0)
+ if (emacs_symlink (SSDATA (encoded_target),
+ SSDATA (encoded_linkname)) == 0)
return Qnil;
if (errno == ENOSYS)
@@ -2841,8 +2949,9 @@ This happens for interactive use with M-x. */)
|| FIXNUMP (ok_if_already_exists))
barf_or_query_if_file_exists (linkname, true, "make it a link",
FIXNUMP (ok_if_already_exists), false);
- unlink (SSDATA (encoded_linkname));
- if (symlink (SSDATA (encoded_target), SSDATA (encoded_linkname)) == 0)
+ emacs_unlink (SSDATA (encoded_linkname));
+ if (emacs_symlink (SSDATA (encoded_target),
+ SSDATA (encoded_linkname)) == 0)
return Qnil;
}
@@ -2982,7 +3091,8 @@ If there is no error, returns nil. */)
encoded_filename = ENCODE_FILE (absname);
- if (faccessat (AT_FDCWD, SSDATA (encoded_filename), R_OK, AT_EACCESS) != 0)
+ if (sys_faccessat (AT_FDCWD, SSDATA (encoded_filename), R_OK,
+ AT_EACCESS) != 0)
report_file_error (SSDATA (string), filename);
return Qnil;
@@ -2990,15 +3100,29 @@ If there is no error, returns nil. */)
/* Relative to directory FD, return the symbolic link value of FILENAME.
On failure, return nil (setting errno). */
+
static Lisp_Object
emacs_readlinkat (int fd, char const *filename)
{
- static struct allocator const emacs_norealloc_allocator =
- { xmalloc, NULL, xfree, memory_full };
+ static struct allocator const emacs_norealloc_allocator = {
+ xmalloc,
+ NULL,
+ xfree,
+ memory_full,
+ };
+
Lisp_Object val;
char readlink_buf[1024];
- char *buf = careadlinkat (fd, filename, readlink_buf, sizeof readlink_buf,
- &emacs_norealloc_allocator, readlinkat);
+ char *buf;
+
+ buf = careadlinkat (fd, filename, readlink_buf, sizeof readlink_buf,
+ &emacs_norealloc_allocator,
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+ android_readlinkat
+#else /* !HAVE_ANDROID || ANDROID_STUBIFY */
+ readlinkat
+#endif /* HAVE_ANDROID && !ANDROID_STUBIFY */
+ );
if (!buf)
return Qnil;
@@ -3084,12 +3208,13 @@ file_directory_p (Lisp_Object file)
{
#ifdef DOS_NT
/* This is cheaper than 'stat'. */
- bool retval = faccessat (AT_FDCWD, SSDATA (file), D_OK, AT_EACCESS) == 0;
+ bool retval = sys_faccessat (AT_FDCWD, SSDATA (file),
+ D_OK, AT_EACCESS) == 0;
if (!retval && errno == EACCES)
errno = ENOTDIR; /* like the non-DOS_NT branch below does */
return retval;
#else
-# ifdef O_PATH
+# if defined O_PATH && !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
/* Use O_PATH if available, as it avoids races and EOVERFLOW issues. */
int fd = emacs_openat (AT_FDCWD, SSDATA (file),
O_PATH | O_CLOEXEC | O_DIRECTORY, 0);
@@ -3198,7 +3323,11 @@ file_accessible_directory_p (Lisp_Object file)
There are three exceptions: "", "/", and "//". Leave "" alone,
as it's invalid. Append only "." to the other two exceptions as
"/" and "//" are distinct on some platforms, whereas "/", "///",
- "////", etc. are all equivalent. */
+ "////", etc. are all equivalent.
+
+ Android has a special directory named "/assets". There is no "."
+ directory there, but appending a "/" is sufficient to check
+ whether or not it is a directory. */
if (! len)
dir = data;
else
@@ -3208,6 +3337,7 @@ file_accessible_directory_p (Lisp_Object file)
special cases "/" and "//", and it's a safe optimization
here. After appending '.', append another '/' to work around
a macOS bug (Bug#30350). */
+
static char const appended[] = "/./";
char *buf = SAFE_ALLOCA (len + sizeof appended);
memcpy (buf, data, len);
@@ -3267,6 +3397,9 @@ or if SELinux is disabled, or if Emacs lacks SELinux support. */)
{
Lisp_Object user = Qnil, role = Qnil, type = Qnil, range = Qnil;
Lisp_Object absname = expand_and_dir_to_file (filename);
+#ifdef HAVE_LIBSELINUX
+ const char *file;
+#endif /* HAVE_LIBSELINUX */
/* If the file name has special constructs in it,
call the corresponding file name handler. */
@@ -3275,11 +3408,13 @@ or if SELinux is disabled, or if Emacs lacks SELinux support. */)
if (!NILP (handler))
return call2 (handler, Qfile_selinux_context, absname);
-#if HAVE_LIBSELINUX
- if (is_selinux_enabled ())
+#ifdef HAVE_LIBSELINUX
+ file = SSDATA (ENCODE_FILE (absname));
+
+ if (selinux_enabled_p (file))
{
char *con;
- int conlength = lgetfilecon (SSDATA (ENCODE_FILE (absname)), &con);
+ int conlength = lgetfilecon (file, &con);
if (conlength > 0)
{
context_t context = context_new (con);
@@ -3298,7 +3433,7 @@ or if SELinux is disabled, or if Emacs lacks SELinux support. */)
|| errno == ENOTSUP))
report_file_error ("getting SELinux context", absname);
}
-#endif
+#endif /* HAVE_LIBSELINUX */
return list4 (user, role, type, range);
}
@@ -3324,10 +3459,11 @@ or if Emacs was not compiled with SELinux support. */)
Lisp_Object type = CAR_SAFE (CDR_SAFE (CDR_SAFE (context)));
Lisp_Object range = CAR_SAFE (CDR_SAFE (CDR_SAFE (CDR_SAFE (context))));
char *con;
+ const char *name;
bool fail;
int conlength;
context_t parsed_con;
-#endif
+#endif /* HAVE_LIBSELINUX */
absname = Fexpand_file_name (filename, BVAR (current_buffer, directory));
@@ -3338,11 +3474,13 @@ or if Emacs was not compiled with SELinux support. */)
return call3 (handler, Qset_file_selinux_context, absname, context);
#if HAVE_LIBSELINUX
- if (is_selinux_enabled ())
+ encoded_absname = ENCODE_FILE (absname);
+ name = SSDATA (encoded_absname);
+
+ if (selinux_enabled_p (name))
{
/* Get current file context. */
- encoded_absname = ENCODE_FILE (absname);
- conlength = lgetfilecon (SSDATA (encoded_absname), &con);
+ conlength = lgetfilecon (name, &con);
if (conlength > 0)
{
parsed_con = context_new (con);
@@ -3372,18 +3510,18 @@ or if Emacs was not compiled with SELinux support. */)
fail = (lsetfilecon (SSDATA (encoded_absname),
context_str (parsed_con))
!= 0);
+ context_free (parsed_con);
+ freecon (con);
+
/* See https://debbugs.gnu.org/11245 for ENOTSUP. */
if (fail && errno != ENOTSUP)
report_file_error ("Doing lsetfilecon", absname);
-
- context_free (parsed_con);
- freecon (con);
return fail ? Qnil : Qt;
}
else
report_file_error ("Doing lgetfilecon", absname);
}
-#endif
+#endif /* HAVE_LIBSELINUX */
return Qnil;
}
@@ -3479,10 +3617,10 @@ support. */)
fail = (acl_set_file (SSDATA (encoded_absname), ACL_TYPE_ACCESS,
acl)
!= 0);
+ acl_free (acl);
if (fail && acl_errno_valid (errno))
report_file_error ("Setting ACL", absname);
- acl_free (acl);
return fail ? Qnil : Qt;
}
# endif
@@ -3532,6 +3670,8 @@ Interactively, prompt for FILENAME, and read MODE with
command from GNU Coreutils. */)
(Lisp_Object filename, Lisp_Object mode, Lisp_Object flag)
{
+ Lisp_Object encoded;
+
CHECK_FIXNUM (mode);
int nofollow = symlink_nofollow_flag (flag);
Lisp_Object absname = Fexpand_file_name (filename,
@@ -3543,9 +3683,10 @@ command from GNU Coreutils. */)
if (!NILP (handler))
return call4 (handler, Qset_file_modes, absname, mode, flag);
- char *fname = SSDATA (ENCODE_FILE (absname));
+ encoded = ENCODE_FILE (absname);
+ char *fname = SSDATA (encoded);
mode_t imode = XFIXNUM (mode) & 07777;
- if (fchmodat (AT_FDCWD, fname, imode, nofollow) != 0)
+ if (emacs_fchmodat (AT_FDCWD, fname, imode, nofollow) != 0)
report_file_error ("Doing chmod", absname);
return Qnil;
@@ -3562,7 +3703,9 @@ in the permissions of newly created files will be disabled.
Note that when `write-region' creates a file, it resets the
execute bit, even if the mask set by this function allows that bit
-by having the corresponding bit in the mask reset. */)
+by having the corresponding bit in the mask reset.
+
+See also `with-file-modes'. */)
(Lisp_Object mode)
{
mode_t oldrealmask, oldumask, newumask;
@@ -3615,6 +3758,8 @@ TIMESTAMP is in the format of `current-time'. */)
return call4 (handler, Qset_file_times, absname, timestamp, flag);
Lisp_Object encoded_absname = ENCODE_FILE (absname);
+ check_vfs_filename (encoded_absname, "Trying to set access times of"
+ " file within special directory");
if (utimensat (AT_FDCWD, SSDATA (encoded_absname), ts, nofollow) != 0)
{
@@ -3647,6 +3792,7 @@ otherwise, if FILE2 does not exist, the answer is t. */)
(Lisp_Object file1, Lisp_Object file2)
{
struct stat st1, st2;
+ Lisp_Object encoded;
CHECK_STRING (file1);
CHECK_STRING (file2);
@@ -3663,8 +3809,10 @@ otherwise, if FILE2 does not exist, the answer is t. */)
if (!NILP (handler))
return call3 (handler, Qfile_newer_than_file_p, absname1, absname2);
+ encoded = ENCODE_FILE (absname1);
+
int err1;
- if (emacs_fstatat (AT_FDCWD, SSDATA (ENCODE_FILE (absname1)), &st1, 0) == 0)
+ if (emacs_fstatat (AT_FDCWD, SSDATA (encoded), &st1, 0) == 0)
err1 = 0;
else
{
@@ -3753,7 +3901,7 @@ union read_non_regular
{
struct
{
- int fd;
+ emacs_fd fd;
ptrdiff_t inserted, trytry;
} s;
GCALIGNED_UNION_MEMBER
@@ -3764,11 +3912,12 @@ static Lisp_Object
read_non_regular (Lisp_Object state)
{
union read_non_regular *data = XFIXNUMPTR (state);
- int nbytes = emacs_read_quit (data->s.fd,
- ((char *) BEG_ADDR + PT_BYTE - BEG_BYTE
- + data->s.inserted),
- data->s.trytry);
- return make_fixnum (nbytes);
+ intmax_t nbytes
+ = emacs_fd_read (data->s.fd,
+ ((char *) BEG_ADDR + PT_BYTE - BEG_BYTE
+ + data->s.inserted),
+ data->s.trytry);
+ return make_int (nbytes);
}
@@ -3896,15 +4045,22 @@ characters in the buffer. If VISIT is non-nil, BEG and END must be nil.
When inserting data from a special file (e.g., /dev/urandom), you
can't specify VISIT or BEG, and END should be specified to avoid
-inserting unlimited data into the buffer.
-
-If optional fifth argument REPLACE is non-nil, replace the current
-buffer contents (in the accessible portion) with the file contents.
-This is better than simply deleting and inserting the whole thing
-because (1) it preserves some marker positions (in unchanged portions
-at the start and end of the buffer) and (2) it puts less data in the
-undo list. When REPLACE is non-nil, the second return value is the
-number of characters that replace previous buffer contents.
+inserting unlimited data into the buffer from some special files
+which otherwise could supply infinite amounts of data.
+
+If optional fifth argument REPLACE is non-nil and FILENAME names a
+regular file, replace the current buffer contents (in the accessible
+portion) with the file's contents. This is better than simply
+deleting and inserting the whole thing because (1) it preserves some
+marker positions (in unchanged portions at the start and end of the
+buffer) and (2) it puts less data in the undo list. When REPLACE is
+non-nil, the second element of the return value is the number of
+characters that replace the previous buffer contents.
+
+If FILENAME is not a regular file and REPLACE is `if-regular', erase
+the accessible portion of the buffer and insert the new contents. Any
+other non-nil value of REPLACE will signal an error if FILENAME is not
+a regular file.
This function does code conversion according to the value of
`coding-system-for-read' or `file-coding-system-alist', and sets the
@@ -3912,14 +4068,13 @@ variable `last-coding-system-used' to the coding system actually used.
In addition, this function decodes the inserted text from known formats
by calling `format-decode', which see. */)
- (Lisp_Object filename, Lisp_Object visit, Lisp_Object beg, Lisp_Object end, Lisp_Object replace)
+ (Lisp_Object filename, Lisp_Object visit, Lisp_Object beg, Lisp_Object end,
+ Lisp_Object replace)
{
struct stat st;
struct timespec mtime;
- int fd;
+ emacs_fd fd;
ptrdiff_t inserted = 0;
- ptrdiff_t how_much;
- off_t beg_offset, end_offset;
int unprocessed;
specpdl_ref count = SPECPDL_INDEX ();
Lisp_Object handler, val, insval, orig_filename, old_undo;
@@ -3932,7 +4087,8 @@ by calling `format-decode', which see. */)
bool replace_handled = false;
bool set_coding_system = false;
Lisp_Object coding_system;
- bool read_quit = false;
+ /* Negative if read error, 0 if OK so far, positive if quit. */
+ ptrdiff_t read_quit = 0;
/* If the undo log only contains the insertion, there's no point
keeping it. It's typically when we first fill a file-buffer. */
bool empty_undo_list_p
@@ -3981,11 +4137,22 @@ by calling `format-decode', which see. */)
goto handled;
}
+ if (!NILP (visit))
+ {
+ if (!NILP (beg) || !NILP (end))
+ error ("Attempt to visit less than an entire file");
+ if (BEG < Z && NILP (replace))
+ error ("Cannot do file visiting in a non-empty buffer");
+ }
+
+ off_t beg_offset = !NILP (beg) ? file_offset (beg) : 0;
+ off_t end_offset = !NILP (end) ? file_offset (end) : -1;
+
orig_filename = filename;
filename = ENCODE_FILE (filename);
- fd = emacs_open (SSDATA (filename), O_RDONLY, 0);
- if (fd < 0)
+ fd = emacs_fd_open (SSDATA (filename), O_RDONLY, 0);
+ if (!emacs_fd_valid_p (fd))
{
save_errno = errno;
if (NILP (visit))
@@ -4003,7 +4170,7 @@ by calling `format-decode', which see. */)
}
specpdl_ref fd_index = SPECPDL_INDEX ();
- record_unwind_protect_int (close_file_unwind, fd);
+ record_unwind_protect_ptr (close_file_unwind_emacs_fd, &fd);
/* Replacement should preserve point as it preserves markers. */
if (!NILP (replace))
@@ -4013,50 +4180,45 @@ by calling `format-decode', which see. */)
XCAR (XCAR (window_markers)));
}
- if (fstat (fd, &st) != 0)
+ if (emacs_fd_fstat (fd, &st) != 0)
report_file_error ("Input file status", orig_filename);
mtime = get_stat_mtime (&st);
- /* This code will need to be changed in order to work on named
- pipes, and it's probably just not worth it. So we should at
- least signal an error. */
+ /* The REPLACE code will need to be changed in order to work on
+ named pipes, and it's probably just not worth it. So we should
+ at least signal an error. */
+
if (!S_ISREG (st.st_mode))
{
regular = false;
- seekable = lseek (fd, 0, SEEK_CUR) != (off_t) -1;
- if (! NILP (visit))
- {
- eassert (inserted == 0);
- goto notfound;
- }
+ if (!NILP (replace))
+ {
+ if (!EQ (replace, Qif_regular))
+ xsignal2 (Qfile_error,
+ build_string ("not a regular file"), orig_filename);
+ else
+ /* Set REPLACE to Qunbound, indicating that we are trying
+ to replace the buffer contents with that of a
+ non-regular file. */
+ replace = Qunbound;
+ }
- if (!NILP (beg) && !seekable)
+ /* Forbid specifying BEG together with a special file, as per
+ the doc string. */
+
+ if (!NILP (beg))
xsignal2 (Qfile_error,
build_string ("cannot use a start position in a non-seekable file/device"),
orig_filename);
- if (!NILP (replace))
- xsignal2 (Qfile_error,
- build_string ("not a regular file"), orig_filename);
+ /* Now ascertain if this file is seekable, by detecting if
+ seeking leads to -1 being returned. */
+ seekable
+ = emacs_fd_lseek (fd, 0, SEEK_CUR) != (off_t) -1;
}
- if (!NILP (visit))
- {
- if (!NILP (beg) || !NILP (end))
- error ("Attempt to visit less than an entire file");
- if (BEG < Z && NILP (replace))
- error ("Cannot do file visiting in a non-empty buffer");
- }
-
- if (!NILP (beg))
- beg_offset = file_offset (beg);
- else
- beg_offset = 0;
-
- if (!NILP (end))
- end_offset = file_offset (end);
- else
+ if (end_offset < 0)
{
if (!regular)
end_offset = TYPE_MAXIMUM (off_t);
@@ -4117,7 +4279,7 @@ by calling `format-decode', which see. */)
else
{
/* Don't try looking inside a file for a coding system
- specification if it is not seekable. */
+ specification if it is not a regular file. */
if (regular && !NILP (Vset_auto_coding_function))
{
/* Find a coding system specified in the heading two
@@ -4128,17 +4290,17 @@ by calling `format-decode', which see. */)
int nread;
if (st.st_size <= (1024 * 4))
- nread = emacs_read_quit (fd, read_buf, 1024 * 4);
+ nread = emacs_fd_read (fd, read_buf, 1024 * 4);
else
{
- nread = emacs_read_quit (fd, read_buf, 1024);
+ nread = emacs_fd_read (fd, read_buf, 1024);
if (nread == 1024)
{
int ntail;
- if (lseek (fd, - (1024 * 3), SEEK_END) < 0)
+ if (emacs_fd_lseek (fd, st.st_size - 1024 * 3, SEEK_CUR) < 0)
report_file_error ("Setting file position",
orig_filename);
- ntail = emacs_read_quit (fd, read_buf + nread, 1024 * 3);
+ ntail = emacs_fd_read (fd, read_buf + nread, 1024 * 3);
nread = ntail < 0 ? ntail : nread + ntail;
}
}
@@ -4179,7 +4341,7 @@ by calling `format-decode', which see. */)
specpdl_ptr--;
/* Rewind the file for the actual read done later. */
- if (lseek (fd, 0, SEEK_SET) < 0)
+ if (emacs_fd_lseek (fd, 0, SEEK_SET) < 0)
report_file_error ("Setting file position", orig_filename);
}
}
@@ -4225,7 +4387,8 @@ by calling `format-decode', which see. */)
method and hope for the best.
But if we discover the need for conversion, we give up on this method
and let the following if-statement handle the replace job. */
- if (!NILP (replace)
+ if ((!NILP (replace)
+ && !BASE_EQ (replace, Qunbound))
&& BEGV < ZV
&& (NILP (coding_system)
|| ! CODING_REQUIRE_DECODING (&coding)))
@@ -4238,7 +4401,7 @@ by calling `format-decode', which see. */)
if (beg_offset != 0)
{
- if (lseek (fd, beg_offset, SEEK_SET) < 0)
+ if (emacs_fd_lseek (fd, beg_offset, SEEK_SET) < 0)
report_file_error ("Setting file position", orig_filename);
}
@@ -4246,7 +4409,7 @@ by calling `format-decode', which see. */)
match the text at the beginning of the buffer. */
while (true)
{
- int nread = emacs_read_quit (fd, read_buf, sizeof read_buf);
+ int nread = emacs_fd_read (fd, read_buf, sizeof read_buf);
if (nread < 0)
report_file_error ("Read error", orig_filename);
else if (nread == 0)
@@ -4281,7 +4444,7 @@ by calling `format-decode', which see. */)
there's no need to replace anything. */
if (same_at_start - BEGV_BYTE == end_offset - beg_offset)
{
- emacs_close (fd);
+ emacs_fd_close (fd);
clear_unwind_protect (fd_index);
/* Truncate the buffer to the size of the file. */
@@ -4304,14 +4467,14 @@ by calling `format-decode', which see. */)
break;
/* How much can we scan in the next step? */
trial = min (curpos, sizeof read_buf);
- if (lseek (fd, curpos - trial, SEEK_SET) < 0)
+ if (emacs_fd_lseek (fd, curpos - trial, SEEK_SET) < 0)
report_file_error ("Setting file position", orig_filename);
total_read = nread = 0;
while (total_read < trial)
{
- nread = emacs_read_quit (fd, read_buf + total_read,
- trial - total_read);
+ nread = emacs_fd_read (fd, read_buf + total_read,
+ trial - total_read);
if (nread < 0)
report_file_error ("Read error", orig_filename);
else if (nread == 0)
@@ -4412,7 +4575,9 @@ by calling `format-decode', which see. */)
is needed, in a simple way that needs a lot of memory.
The preceding if-statement handles the case of no conversion
in a more optimized way. */
- if (!NILP (replace) && ! replace_handled && BEGV < ZV)
+ if ((!NILP (replace)
+ && !BASE_EQ (replace, Qunbound))
+ && ! replace_handled && BEGV < ZV)
{
ptrdiff_t same_at_start_charpos;
ptrdiff_t inserted_chars;
@@ -4420,7 +4585,7 @@ by calling `format-decode', which see. */)
ptrdiff_t bufpos;
unsigned char *decoded;
ptrdiff_t temp;
- ptrdiff_t this = 0;
+ ptrdiff_t this;
specpdl_ref this_count = SPECPDL_INDEX ();
bool multibyte
= ! NILP (BVAR (current_buffer, enable_multibyte_characters));
@@ -4431,7 +4596,7 @@ by calling `format-decode', which see. */)
/* First read the whole file, performing code conversion into
CONVERSION_BUFFER. */
- if (lseek (fd, beg_offset, SEEK_SET) < 0)
+ if (emacs_fd_lseek (fd, beg_offset, SEEK_SET) < 0)
report_file_error ("Setting file position", orig_filename);
inserted = 0; /* Bytes put into CONVERSION_BUFFER so far. */
@@ -4442,8 +4607,8 @@ by calling `format-decode', which see. */)
/* Read at most READ_BUF_SIZE bytes at a time, to allow
quitting while reading a huge file. */
- this = emacs_read_quit (fd, read_buf + unprocessed,
- READ_BUF_SIZE - unprocessed);
+ this = emacs_fd_read (fd, read_buf + unprocessed,
+ READ_BUF_SIZE - unprocessed);
if (this <= 0)
break;
@@ -4458,7 +4623,7 @@ by calling `format-decode', which see. */)
if (this < 0)
report_file_error ("Read error", orig_filename);
- emacs_close (fd);
+ emacs_fd_close (fd);
clear_unwind_protect (fd_index);
if (unprocessed > 0)
@@ -4597,22 +4762,28 @@ by calling `format-decode', which see. */)
prepare_to_modify_buffer (PT, PT, NULL);
}
+ /* If REPLACE is Qunbound, buffer contents are being replaced with
+ text read from a FIFO or a device. Erase the entire accessible
+ portion of the buffer. */
+
+ if (BASE_EQ (replace, Qunbound))
+ del_range (BEGV, ZV);
+
move_gap_both (PT, PT_BYTE);
- if (GAP_SIZE < total)
- make_gap (total - GAP_SIZE);
- if (beg_offset != 0 || !NILP (replace))
+ /* Ensure the gap is at least one byte larger than needed for the
+ estimated file size, so that in the usual case we read to EOF
+ without reallocating. */
+ if (GAP_SIZE <= total)
+ make_gap (total - GAP_SIZE + 1);
+
+ if (beg_offset != 0 || (!NILP (replace)
+ && !BASE_EQ (replace, Qunbound)))
{
- if (lseek (fd, beg_offset, SEEK_SET) < 0)
+ if (emacs_fd_lseek (fd, beg_offset, SEEK_SET) < 0)
report_file_error ("Setting file position", orig_filename);
}
- /* In the following loop, HOW_MUCH contains the total bytes read so
- far for a regular file, and not changed for a special file. But,
- before exiting the loop, it is set to a negative value if I/O
- error occurs. */
- how_much = 0;
-
/* Total bytes inserted. */
inserted = 0;
@@ -4621,22 +4792,26 @@ by calling `format-decode', which see. */)
{
ptrdiff_t gap_size = GAP_SIZE;
- while (how_much < total)
+ while (NILP (end) || inserted < total)
{
- /* `try' is reserved in some compilers (Microsoft C). */
- ptrdiff_t trytry = min (total - how_much, READ_BUF_SIZE);
ptrdiff_t this;
+ if (gap_size == 0)
+ {
+ /* The size estimate was wrong. Make the gap 50% larger. */
+ make_gap (GAP_SIZE >> 1);
+ gap_size = GAP_SIZE - inserted;
+ }
+
+ /* 'try' is reserved in some compilers (Microsoft C). */
+ ptrdiff_t trytry = min (gap_size, READ_BUF_SIZE);
+ if (seekable || !NILP (end))
+ trytry = min (trytry, total - inserted);
+
if (!seekable && NILP (end))
{
Lisp_Object nbytes;
-
- /* Maybe make more room. */
- if (gap_size < trytry)
- {
- make_gap (trytry - gap_size);
- gap_size = GAP_SIZE - inserted;
- }
+ intmax_t number;
/* Read from the file, capturing `quit'. When an
error occurs, end the loop, and arrange for a quit
@@ -4648,38 +4823,32 @@ by calling `format-decode', which see. */)
if (NILP (nbytes))
{
- read_quit = true;
+ read_quit = 1;
break;
}
- this = XFIXNUM (nbytes);
+ if (!integer_to_intmax (nbytes, &number)
+ && number > PTRDIFF_MAX)
+ buffer_overflow ();
+
+ this = number;
}
else
- {
- /* Allow quitting out of the actual I/O. We don't make text
- part of the buffer until all the reading is done, so a C-g
- here doesn't do any harm. */
- this = emacs_read_quit (fd,
- ((char *) BEG_ADDR + PT_BYTE - BEG_BYTE
- + inserted),
- trytry);
- }
+ /* Allow quitting out of the actual I/O. We don't make text
+ part of the buffer until all the reading is done, so a
+ C-g here doesn't do any harm. */
+ this = emacs_fd_read (fd,
+ ((char *) BEG_ADDR + PT_BYTE - BEG_BYTE
+ + inserted),
+ trytry);
if (this <= 0)
{
- how_much = this;
+ read_quit = this;
break;
}
gap_size -= this;
-
- /* For a regular file, where TOTAL is the real size,
- count HOW_MUCH to compare with it.
- For a special file, where TOTAL is just a buffer size,
- so don't bother counting in HOW_MUCH.
- (INSERTED is where we count the number of characters inserted.) */
- if (seekable || !NILP (end))
- how_much += this;
inserted += this;
}
}
@@ -4697,10 +4866,10 @@ by calling `format-decode', which see. */)
else
Fset (Qdeactivate_mark, Qt);
- emacs_close (fd);
+ emacs_fd_close (fd);
clear_unwind_protect (fd_index);
- if (how_much < 0)
+ if (read_quit < 0)
report_file_error ("Read error", orig_filename);
notfound:
@@ -4856,9 +5025,14 @@ by calling `format-decode', which see. */)
Funlock_file (BVAR (current_buffer, file_truename));
Funlock_file (filename);
}
+
+#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY
+ /* Under Android, modtime and st.st_size can be valid even if FD
+ is not a regular file. */
if (!regular)
xsignal2 (Qfile_error,
build_string ("not a regular file"), orig_filename);
+#endif /* !defined HAVE_ANDROID || defined ANDROID_STUBIFY */
}
if (set_coding_system)
@@ -4876,8 +5050,10 @@ by calling `format-decode', which see. */)
}
}
- /* Decode file format. */
- if (inserted > 0)
+ /* Decode file format. Don't do this if Qformat_decode is not
+ bound, which can happen when called early during loadup. */
+
+ if (inserted > 0 && !NILP (Ffboundp (Qformat_decode)))
{
/* Don't run point motion or modification hooks when decoding. */
specpdl_ref count1 = SPECPDL_INDEX ();
@@ -5322,6 +5498,7 @@ write_region (Lisp_Object start, Lisp_Object end, Lisp_Object filename,
}
encoded_filename = ENCODE_FILE (filename);
+
fn = SSDATA (encoded_filename);
open_flags = O_WRONLY | O_CREAT;
open_flags |= EQ (mustbenew, Qexcl) ? O_EXCL : !NILP (append) ? 0 : O_TRUNC;
@@ -5408,7 +5585,7 @@ write_region (Lisp_Object start, Lisp_Object end, Lisp_Object filename,
modtime = invalid_timespec ();
if (visiting)
{
- if (fstat (desc, &st) == 0)
+ if (sys_fstat (desc, &st) == 0)
modtime = get_stat_mtime (&st);
else
ok = 0, save_errno = errno;
@@ -5442,42 +5619,52 @@ write_region (Lisp_Object start, Lisp_Object end, Lisp_Object filename,
if (timespec_valid_p (modtime)
&& ! (valid_timestamp_file_system && st.st_dev == timestamp_file_system))
{
- int desc1 = emacs_open (fn, O_WRONLY, 0);
- if (desc1 >= 0)
+ struct stat st1;
+
+ /* The code below previously tried to open FN O_WRONLY,
+ subsequently calling fstat on the opened file descriptor.
+ This proved inefficient and resulted in FN being truncated
+ under several Android filesystems, and as such has been
+ changed to a call to `stat'. */
+
+ if (emacs_fstatat (AT_FDCWD, fn, &st1, 0) == 0
+ && st.st_dev == st1.st_dev
+ && (st.st_ino == st1.st_ino
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+ /* `st1.st_ino' == 0 indicates that the inode number
+ cannot be extracted from this document file, despite
+ `st' potentially being backed by a real file. */
+ || st1.st_ino == 0
+#endif /* defined HAVE_ANDROID && !defined ANDROID_STUBIFY */
+ ))
{
- struct stat st1;
- if (fstat (desc1, &st1) == 0
- && st.st_dev == st1.st_dev && st.st_ino == st1.st_ino)
+ /* Use the heuristic if it appears to be valid. With neither
+ O_EXCL nor O_TRUNC, if Emacs happened to write nothing to the
+ file, the time stamp won't change. Also, some non-POSIX
+ systems don't update an empty file's time stamp when
+ truncating it. Finally, file systems with 100 ns or worse
+ resolution sometimes seem to have bugs: on a system with ns
+ resolution, checking ns % 100 incorrectly avoids the heuristic
+ 1% of the time, but the problem should be temporary as we will
+ try again on the next time stamp. */
+ bool use_heuristic
+ = ((open_flags & (O_EXCL | O_TRUNC)) != 0
+ && st.st_size != 0
+ && modtime.tv_nsec % 100 != 0);
+
+ struct timespec modtime1 = get_stat_mtime (&st1);
+ if (use_heuristic
+ && timespec_cmp (modtime, modtime1) == 0
+ && st.st_size == st1.st_size)
{
- /* Use the heuristic if it appears to be valid. With neither
- O_EXCL nor O_TRUNC, if Emacs happened to write nothing to the
- file, the time stamp won't change. Also, some non-POSIX
- systems don't update an empty file's time stamp when
- truncating it. Finally, file systems with 100 ns or worse
- resolution sometimes seem to have bugs: on a system with ns
- resolution, checking ns % 100 incorrectly avoids the heuristic
- 1% of the time, but the problem should be temporary as we will
- try again on the next time stamp. */
- bool use_heuristic
- = ((open_flags & (O_EXCL | O_TRUNC)) != 0
- && st.st_size != 0
- && modtime.tv_nsec % 100 != 0);
-
- struct timespec modtime1 = get_stat_mtime (&st1);
- if (use_heuristic
- && timespec_cmp (modtime, modtime1) == 0
- && st.st_size == st1.st_size)
- {
- timestamp_file_system = st.st_dev;
- valid_timestamp_file_system = 1;
- }
- else
- {
- st.st_size = st1.st_size;
- modtime = modtime1;
- }
+ timestamp_file_system = st.st_dev;
+ valid_timestamp_file_system = 1;
+ }
+ else
+ {
+ st.st_size = st1.st_size;
+ modtime = modtime1;
}
- emacs_close (desc1);
}
}
@@ -5823,7 +6010,6 @@ See Info node `(elisp)Modification Time' for more details. */)
return call2 (handler, Qverify_visited_file_modtime, buf);
filename = ENCODE_FILE (BVAR (b, filename));
-
mtime = (emacs_fstatat (AT_FDCWD, SSDATA (filename), &st, 0) == 0
? get_stat_mtime (&st)
: time_error_value (errno));
@@ -5883,7 +6069,7 @@ in `current-time' or an integer flag as returned by `visited-file-modtime'. */)
error ("An indirect buffer does not have a visited file");
else
{
- register Lisp_Object filename;
+ register Lisp_Object filename, encoded;
struct stat st;
Lisp_Object handler;
@@ -5896,7 +6082,9 @@ 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);
- if (emacs_fstatat (AT_FDCWD, SSDATA (ENCODE_FILE (filename)), &st, 0)
+ encoded = ENCODE_FILE (filename);
+
+ if (emacs_fstatat (AT_FDCWD, SSDATA (encoded), &st, 0)
== 0)
{
current_buffer->modtime = get_stat_mtime (&st);
@@ -5969,7 +6157,7 @@ do_auto_save_unwind (void *arg)
if (stream != NULL)
{
block_input ();
- fclose (stream);
+ emacs_fclose (stream);
unblock_input ();
}
}
@@ -6295,13 +6483,18 @@ effect except for flushing STREAM's data. */)
#ifndef DOS_NT
+#if defined STAT_STATFS2_BSIZE || defined STAT_STATFS2_FRSIZE \
+ || defined STAT_STATFS2_FSIZE || defined STAT_STATFS3_OSF1 \
+ || defined STAT_STATFS4 || defined STAT_STATVFS \
+ || defined STAT_STATVFS64
+
/* Yield a Lisp number equal to BLOCKSIZE * BLOCKS, with the result
negated if NEGATE. */
static Lisp_Object
blocks_to_bytes (uintmax_t blocksize, uintmax_t blocks, bool negate)
{
intmax_t n;
- if (!INT_MULTIPLY_WRAPV (blocksize, blocks, &n))
+ if (!ckd_mul (&n, blocksize, blocks))
return make_int (negate ? -n : n);
Lisp_Object bs = make_uint (blocksize);
if (negate)
@@ -6309,6 +6502,8 @@ blocks_to_bytes (uintmax_t blocksize, uintmax_t blocks, bool negate)
return CALLN (Ftimes, bs, make_uint (blocks));
}
+#endif
+
DEFUN ("file-system-info", Ffile_system_info, Sfile_system_info, 1, 1, 0,
doc: /* Return storage information about the file system FILENAME is on.
Value is a list of numbers (TOTAL FREE AVAIL), where TOTAL is the total
@@ -6330,6 +6525,11 @@ If the underlying system call fails, value is nil. */)
error ("Invalid handler in `file-name-handler-alist'");
}
+ /* Try to detect whether or not fsusage.o is actually built. */
+#if defined STAT_STATFS2_BSIZE || defined STAT_STATFS2_FRSIZE \
+ || defined STAT_STATFS2_FSIZE || defined STAT_STATFS3_OSF1 \
+ || defined STAT_STATFS4 || defined STAT_STATVFS \
+ || defined STAT_STATVFS64
struct fs_usage u;
if (get_fs_usage (SSDATA (ENCODE_FILE (filename)), NULL, &u) != 0)
return errno == ENOSYS ? Qnil : file_attribute_errno (filename, errno);
@@ -6337,6 +6537,9 @@ If the underlying system call fails, value is nil. */)
blocks_to_bytes (u.fsu_blocksize, u.fsu_bfree, false),
blocks_to_bytes (u.fsu_blocksize, u.fsu_bavail,
u.fsu_bavail_top_bit_set));
+#else
+ return Qnil;
+#endif
}
#endif /* !DOS_NT */
@@ -6348,24 +6551,6 @@ init_fileio (void)
umask (realmask);
valid_timestamp_file_system = 0;
-
- /* fsync can be a significant performance hit. Often it doesn't
- suffice to make the file-save operation survive a crash. For
- batch scripts, which are typically part of larger shell commands
- that don't fsync other files, its effect on performance can be
- significant so its utility is particularly questionable.
- Hence, for now by default fsync is used only when interactive.
-
- For more on why fsync often fails to work on today's hardware, see:
- Zheng M et al. Understanding the robustness of SSDs under power fault.
- 11th USENIX Conf. on File and Storage Technologies, 2013 (FAST '13), 271-84
- https://www.usenix.org/system/files/conference/fast13/fast13-final80.pdf
-
- For more on why fsync does not suffice even if it works properly, see:
- Roche X. Necessary step(s) to synchronize filename operations on disk.
- Austin Group Defect 672, 2013-03-19
- https://austingroupbugs.net/view.php?id=672 */
- write_region_inhibit_fsync = noninteractive;
}
void
@@ -6385,7 +6570,7 @@ syms_of_fileio (void)
DEFSYM (Qcopy_file, "copy-file");
DEFSYM (Qmake_directory_internal, "make-directory-internal");
DEFSYM (Qmake_directory, "make-directory");
- DEFSYM (Qdelete_file, "delete-file");
+ DEFSYM (Qdelete_file_internal, "delete-file-internal");
DEFSYM (Qfile_name_case_insensitive_p, "file-name-case-insensitive-p");
DEFSYM (Qrename_file, "rename-file");
DEFSYM (Qadd_name_to_file, "add-name-to-file");
@@ -6623,9 +6808,22 @@ file is usually more useful if it contains the deleted text. */);
DEFVAR_BOOL ("write-region-inhibit-fsync", write_region_inhibit_fsync,
doc: /* Non-nil means don't call fsync in `write-region'.
This variable affects calls to `write-region' as well as save commands.
-Setting this to nil may avoid data loss if the system loses power or
-the operating system crashes. By default, it is non-nil in batch mode. */);
- write_region_inhibit_fsync = 0; /* See also `init_fileio' above. */
+By default, it is non-nil.
+
+Although setting this to nil may avoid data loss if the system loses power,
+it can be a significant performance hit in the usual case, and it doesn't
+necessarily cause file-save operations to actually survive a crash. */);
+
+ /* For more on why fsync often fails to work on today's hardware, see:
+ Zheng M et al. Understanding the robustness of SSDs under power fault.
+ 11th USENIX Conf. on File and Storage Technologies, 2013 (FAST '13), 271-84
+ https://www.usenix.org/system/files/conference/fast13/fast13-final80.pdf
+
+ For more on why fsync does not suffice even if it works properly, see:
+ Roche X. Necessary step(s) to synchronize filename operations on disk.
+ Austin Group Defect 672, 2013-03-19
+ https://austingroupbugs.net/view.php?id=672 */
+ write_region_inhibit_fsync = true;
DEFVAR_BOOL ("delete-by-moving-to-trash", delete_by_moving_to_trash,
doc: /* Specifies whether to use the system's trash can.
@@ -6636,8 +6834,8 @@ This includes interactive calls to `delete-file' and
delete_by_moving_to_trash = 0;
DEFSYM (Qdelete_by_moving_to_trash, "delete-by-moving-to-trash");
- /* Lisp function for moving files to trash. */
- DEFSYM (Qmove_file_to_trash, "move-file-to-trash");
+ /* Lisp function for interactive file delete with trashing */
+ DEFSYM (Qdelete_file, "delete-file");
/* Lisp function for recursively copying directories. */
DEFSYM (Qcopy_directory, "copy-directory");
@@ -6667,7 +6865,7 @@ This includes interactive calls to `delete-file' and
defsubr (&Scopy_file);
defsubr (&Smake_directory_internal);
defsubr (&Sdelete_directory_internal);
- defsubr (&Sdelete_file);
+ defsubr (&Sdelete_file_internal);
defsubr (&Sfile_name_case_insensitive_p);
defsubr (&Srename_file);
defsubr (&Sadd_name_to_file);
@@ -6709,9 +6907,11 @@ This includes interactive calls to `delete-file' and
#ifndef DOS_NT
defsubr (&Sfile_system_info);
-#endif
+#endif /* DOS_NT */
#ifdef HAVE_SYNC
defsubr (&Sunix_sync);
-#endif
+#endif /* HAVE_SYNC */
+
+ DEFSYM (Qif_regular, "if-regular");
}