diff options
Diffstat (limited to 'src/fileio.c')
-rw-r--r-- | src/fileio.c | 303 |
1 files changed, 221 insertions, 82 deletions
diff --git a/src/fileio.c b/src/fileio.c index d94805f316b..1a744e02e28 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -25,6 +25,10 @@ along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */ #include <sys/stat.h> #include <unistd.h> +#ifdef DARWIN_OS +#include <sys/attr.h> +#endif + #ifdef HAVE_PWD_H #include <pwd.h> #endif @@ -52,9 +56,18 @@ along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */ #include "region-cache.h" #include "frame.h" +#ifdef HAVE_LINUX_FS_H +# include <sys/ioctl.h> +# include <linux/fs.h> +#endif + #ifdef WINDOWSNT #define NOMINMAX 1 #include <windows.h> +/* The redundant #ifdef is to avoid compiler warning about unused macro. */ +#ifdef NOMINMAX +#undef NOMINMAX +#endif #include <sys/file.h> #include "w32.h" #endif /* not WINDOWSNT */ @@ -185,17 +198,17 @@ void report_file_errno (char const *string, Lisp_Object name, int errorno) { Lisp_Object data = CONSP (name) || NILP (name) ? name : list1 (name); - synchronize_system_messages_locale (); - char *str = strerror (errorno); + char *str = emacs_strerror (errorno); + AUTO_STRING (unibyte_str, str); Lisp_Object errstring - = code_convert_string_norecord (build_unibyte_string (str), - Vlocale_coding_system, 0); + = code_convert_string_norecord (unibyte_str, Vlocale_coding_system, 0); Lisp_Object errdata = Fcons (errstring, data); if (errorno == EEXIST) xsignal (Qfile_already_exists, errdata); else - xsignal (Qfile_error, Fcons (build_string (string), errdata)); + xsignal (errorno == ENOENT ? Qfile_missing : Qfile_error, + Fcons (build_string (string), errdata)); } /* Signal a file-access failure that set errno. STRING describes the @@ -214,12 +227,11 @@ report_file_error (char const *string, Lisp_Object name) void report_file_notify_error (const char *string, Lisp_Object name) { - Lisp_Object data = CONSP (name) || NILP (name) ? name : list1 (name); - synchronize_system_messages_locale (); - char *str = strerror (errno); + char *str = emacs_strerror (errno); + AUTO_STRING (unibyte_str, str); Lisp_Object errstring - = code_convert_string_norecord (build_unibyte_string (str), - Vlocale_coding_system, 0); + = code_convert_string_norecord (unibyte_str, Vlocale_coding_system, 0); + Lisp_Object data = CONSP (name) || NILP (name) ? name : list1 (name); Lisp_Object errdata = Fcons (errstring, data); xsignal (Qfile_notify_error, Fcons (build_string (string), errdata)); @@ -510,7 +522,8 @@ This operation exists because a directory is also a file, but its name as a directory is different from its name as a file. The result can be used as the value of `default-directory' or passed as second argument to `expand-file-name'. -For a Unix-syntax file name, just appends a slash. */) +For a Unix-syntax file name, just appends a slash unless a trailing slash +is already present. */) (Lisp_Object file) { char *buf; @@ -871,6 +884,11 @@ filesystem tree, not (expand-file-name ".." dirname). */) /* Detect MSDOS file names with drive specifiers. */ && ! (IS_DRIVE (o[0]) && IS_DEVICE_SEP (o[1]) && IS_DIRECTORY_SEP (o[2])) + /* Detect escaped file names without drive spec after "/:". + These should not be recursively expanded, to avoid + including the default directory twice in the expanded + result. */ + && ! (o[0] == '/' && o[1] == ':') #ifdef WINDOWSNT /* Detect Windows file names in UNC format. */ && ! (IS_DIRECTORY_SEP (o[0]) && IS_DIRECTORY_SEP (o[1])) @@ -1015,11 +1033,9 @@ filesystem tree, not (expand-file-name ".." dirname). */) /* Drive must be set, so this is okay. */ if (strcmp (nm - 2, SSDATA (name)) != 0) { - char temp[] = " :"; - name = make_specified_string (nm, -1, p - nm, multibyte); - temp[0] = DRIVE_LETTER (drive); - AUTO_STRING (drive_prefix, temp); + char temp[] = { DRIVE_LETTER (drive), ':', 0 }; + AUTO_STRING_WITH_LEN (drive_prefix, temp, 2); name = concat2 (drive_prefix, name); } #ifdef WINDOWSNT @@ -1053,7 +1069,11 @@ filesystem tree, not (expand-file-name ".." dirname). */) newdir = newdirlim = 0; - if (nm[0] == '~') /* prefix ~ */ + if (nm[0] == '~' /* prefix ~ */ +#ifdef DOS_NT + && !is_escaped /* don't expand ~ in escaped file names */ +#endif + ) { if (IS_DIRECTORY_SEP (nm[1]) || nm[1] == 0) /* ~ by itself */ @@ -1832,6 +1852,18 @@ barf_or_query_if_file_exists (Lisp_Object absname, bool known_to_exist, } } +#ifndef WINDOWSNT +/* Copy data to DEST from SOURCE if possible. Return true if OK. */ +static bool +clone_file (int dest, int source) +{ +#ifdef FICLONE + return ioctl (dest, FICLONE, source) == 0; +#endif + return false; +} +#endif + DEFUN ("copy-file", Fcopy_file, Scopy_file, 2, 6, "fCopy file: \nGCopy %s to file: \np\nP", doc: /* Copy FILE to NEWNAME. Both args must be strings. @@ -1978,7 +2010,7 @@ permissions. */) record_unwind_protect_int (close_file_unwind, ofd); - off_t oldsize = 0, newsize = 0; + off_t oldsize = 0, newsize; if (already_exists) { @@ -1994,17 +2026,19 @@ permissions. */) immediate_quit = 1; QUIT; - while (true) + + if (clone_file (ofd, ifd)) + newsize = st.st_size; + else { char buf[MAX_ALLOCA]; - ptrdiff_t n = emacs_read (ifd, buf, sizeof buf); + ptrdiff_t n; + for (newsize = 0; 0 < (n = emacs_read (ifd, buf, sizeof buf)); + newsize += n) + if (emacs_write_sig (ofd, buf, n) != n) + report_file_error ("Write error", newname); if (n < 0) report_file_error ("Read error", file); - if (n == 0) - break; - if (emacs_write_sig (ofd, buf, n) != n) - report_file_error ("Write error", newname); - newsize += n; } /* Truncate any existing output file after writing the data. This @@ -2211,6 +2245,105 @@ internal_delete_file (Lisp_Object filename) return NILP (tem); } +/* Filesystems are case-sensitive on all supported systems except + MS-Windows, MS-DOS, Cygwin, and Mac OS X. They are always + case-insensitive on the first two, but they may or may not be + case-insensitive on Cygwin and OS X. The following function + attempts to provide a runtime test on those two systems. If the + test is not conclusive, we assume case-insensitivity on Cygwin and + case-sensitivity on Mac OS X. + + FIXME: Mounted filesystems on Posix hosts, like Samba shares or + NFS-mounted Windows volumes, might be case-insensitive. Can we + detect this? */ + +static bool +file_name_case_insensitive_p (const char *filename) +{ + /* Use pathconf with _PC_CASE_INSENSITIVE or _PC_CASE_SENSITIVE if + those flags are available. As of this writing (2016-11-14), + Cygwin is the only platform known to support the former (starting + with Cygwin-2.6.1), and Mac OS X is the only platform known to + support the latter. + + There have been reports that pathconf with _PC_CASE_SENSITIVE + does not work reliably on Mac OS X. If you have a problem, + please recompile Emacs with -D DARWIN_OS_CASE_SENSITIVE_FIXME=1 or + -D DARWIN_OS_CASE_SENSITIVE_FIXME=2, and file a bug report saying + whether this fixed your problem. */ + +#ifdef _PC_CASE_INSENSITIVE + int res = pathconf (filename, _PC_CASE_INSENSITIVE); + if (res >= 0) + return res > 0; +#elif defined _PC_CASE_SENSITIVE && !defined DARWIN_OS_CASE_SENSITIVE_FIXME + int res = pathconf (filename, _PC_CASE_SENSITIVE); + if (res >= 0) + return res == 0; +#endif + +#ifdef DARWIN_OS +# ifndef DARWIN_OS_CASE_SENSITIVE_FIXME + int DARWIN_OS_CASE_SENSITIVE_FIXME = 0; +# endif + + if (DARWIN_OS_CASE_SENSITIVE_FIXME == 1) + { + /* This is based on developer.apple.com's getattrlist man page. */ + struct attrlist alist = {.volattr = ATTR_VOL_CAPABILITIES}; + vol_capabilities_attr_t vcaps; + if (getattrlist (filename, &alist, &vcaps, sizeof vcaps, 0) == 0) + { + if (vcaps.valid[VOL_CAPABILITIES_FORMAT] & VOL_CAP_FMT_CASE_SENSITIVE) + return ! (vcaps.capabilities[VOL_CAPABILITIES_FORMAT] + & VOL_CAP_FMT_CASE_SENSITIVE); + } + } + else if (DARWIN_OS_CASE_SENSITIVE_FIXME == 2) + { + /* The following is based on + http://lists.apple.com/archives/darwin-dev/2007/Apr/msg00010.html. */ + struct attrlist alist; + unsigned char buffer[sizeof (vol_capabilities_attr_t) + sizeof (size_t)]; + + memset (&alist, 0, sizeof (alist)); + alist.volattr = ATTR_VOL_CAPABILITIES; + if (getattrlist (filename, &alist, buffer, sizeof (buffer), 0) + || !(alist.volattr & ATTR_VOL_CAPABILITIES)) + return 0; + vol_capabilities_attr_t *vcaps = buffer; + return !(vcaps->capabilities[0] & VOL_CAP_FMT_CASE_SENSITIVE); + } +#endif /* DARWIN_OS */ + +#if defined CYGWIN || defined DOS_NT + return true; +#else + return false; +#endif +} + +DEFUN ("file-name-case-insensitive-p", Ffile_name_case_insensitive_p, + Sfile_name_case_insensitive_p, 1, 1, 0, + doc: /* Return t if file FILENAME is on a case-insensitive filesystem. +The arg must be a string. */) + (Lisp_Object filename) +{ + Lisp_Object handler; + + CHECK_STRING (filename); + filename = Fexpand_file_name (filename, Qnil); + + /* If the file name has special constructs in it, + call the corresponding file handler. */ + handler = Ffind_file_name_handler (filename, Qfile_name_case_insensitive_p); + if (!NILP (handler)) + return call2 (handler, Qfile_name_case_insensitive_p, filename); + + filename = ENCODE_FILE (filename); + return file_name_case_insensitive_p (SSDATA (filename)) ? Qt : Qnil; +} + DEFUN ("rename-file", Frename_file, Srename_file, 2, 3, "fRename file: \nGRename %s to file: \np", doc: /* Rename FILE as NEWNAME. Both args must be strings. @@ -2230,12 +2363,11 @@ This is what happens in interactive use with M-x. */) file = Fexpand_file_name (file, Qnil); if ((!NILP (Ffile_directory_p (newname))) -#ifdef DOS_NT - /* If the file names are identical but for the case, - don't attempt to move directory to itself. */ - && (NILP (Fstring_equal (Fdowncase (file), Fdowncase (newname)))) -#endif - ) + /* If the filesystem is case-insensitive and the file names are + identical but for the case, don't attempt to move directory + to itself. */ + && (NILP (Ffile_name_case_insensitive_p (file)) + || NILP (Fstring_equal (Fdowncase (file), Fdowncase (newname))))) { Lisp_Object fname = (NILP (Ffile_directory_p (file)) ? file : Fdirectory_file_name (file)); @@ -2256,14 +2388,12 @@ This is what happens in interactive use with M-x. */) encoded_file = ENCODE_FILE (file); encoded_newname = ENCODE_FILE (newname); -#ifdef DOS_NT - /* If the file names are identical but for the case, don't ask for - confirmation: they simply want to change the letter-case of the - file name. */ - if (NILP (Fstring_equal (Fdowncase (file), Fdowncase (newname)))) -#endif - if (NILP (ok_if_already_exists) - || INTEGERP (ok_if_already_exists)) + /* If the filesystem is case-insensitive and the file names are + identical but for the case, don't ask for confirmation: they + simply want to change the letter-case of the file name. */ + if ((!(file_name_case_insensitive_p (SSDATA (encoded_file))) + || NILP (Fstring_equal (Fdowncase (file), Fdowncase (newname)))) + && ((NILP (ok_if_already_exists) || INTEGERP (ok_if_already_exists)))) barf_or_query_if_file_exists (newname, false, "rename to it", INTEGERP (ok_if_already_exists), false); if (rename (SSDATA (encoded_file), SSDATA (encoded_newname)) < 0) @@ -2544,7 +2674,7 @@ DEFUN ("file-writable-p", Ffile_writable_p, Sfile_writable_p, 1, 1, 0, /* 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 (SDATA (dir)) ? Qt : Qnil; + return file_directory_p (SSDATA (dir)) ? Qt : Qnil; #else return check_writable (SSDATA (dir), W_OK | X_OK) ? Qt : Qnil; #endif @@ -2775,7 +2905,7 @@ See `file-symlink-p' to distinguish symlinks. */) /* Tell stat to use expensive method to get accurate info. */ Vw32_get_true_file_attributes = Qt; - result = stat (SDATA (absname), &st); + result = stat (SSDATA (absname), &st); Vw32_get_true_file_attributes = tem; if (result < 0) @@ -3363,6 +3493,21 @@ restore_window_points (Lisp_Object window_markers, ptrdiff_t inserted, } } +/* Make sure the gap is at Z_BYTE. This is required to treat buffer + text as a linear C char array. */ +static void +maybe_move_gap (struct buffer *b) +{ + if (BUF_GPT_BYTE (b) != BUF_Z_BYTE (b)) + { + struct buffer *cb = current_buffer; + + set_buffer_internal (b); + move_gap_both (Z, Z_BYTE); + set_buffer_internal (cb); + } +} + /* FIXME: insert-file-contents should be split with the top-level moved to Elisp and only the core kept in C. */ @@ -3436,9 +3581,6 @@ by calling `format-decode', which see. */) if (!NILP (BVAR (current_buffer, read_only))) Fbarf_if_buffer_read_only (Qnil); - if (!NILP (Ffboundp (Qundo_auto__undoable_change_no_timer))) - call0 (Qundo_auto__undoable_change_no_timer); - val = Qnil; p = Qnil; orig_filename = Qnil; @@ -3830,6 +3972,7 @@ by calling `format-decode', which see. */) if (! giveup_match_end) { ptrdiff_t temp; + ptrdiff_t this_count = SPECPDL_INDEX (); /* We win! We can handle REPLACE the optimized way. */ @@ -3859,13 +4002,19 @@ by calling `format-decode', which see. */) beg_offset += same_at_start - BEGV_BYTE; end_offset -= ZV_BYTE - same_at_end; - invalidate_buffer_caches (current_buffer, - BYTE_TO_CHAR (same_at_start), - same_at_end_charpos); - del_range_byte (same_at_start, same_at_end, 0); + /* This binding is to avoid ask-user-about-supersession-threat + being called in insert_from_buffer or del_range_bytes (via + prepare_to_modify_buffer). + AFAICT we could avoid ask-user-about-supersession-threat by setting + current_buffer->modtime earlier, but we could still end up calling + ask-user-about-supersession-threat if the file is modified while + we read it, so we bind buffer-file-name instead. */ + specbind (intern ("buffer-file-name"), Qnil); + del_range_byte (same_at_start, same_at_end); /* Insert from the file at the proper position. */ temp = BYTE_TO_CHAR (same_at_start); SET_PT_BOTH (temp, same_at_start); + unbind_to (this_count, Qnil); /* If display currently starts at beginning of line, keep it that way. */ @@ -3949,6 +4098,7 @@ by calling `format-decode', which see. */) coding_system = CODING_ID_NAME (coding.id); set_coding_system = true; + maybe_move_gap (XBUFFER (conversion_buffer)); decoded = BUF_BEG_ADDR (XBUFFER (conversion_buffer)); inserted = (BUF_Z_BYTE (XBUFFER (conversion_buffer)) - BUF_BEG_BYTE (XBUFFER (conversion_buffer))); @@ -3969,10 +4119,9 @@ by calling `format-decode', which see. */) /* Truncate the buffer to the size of the file. */ if (same_at_start != same_at_end) { - invalidate_buffer_caches (current_buffer, - BYTE_TO_CHAR (same_at_start), - BYTE_TO_CHAR (same_at_end)); - del_range_byte (same_at_start, same_at_end, 0); + /* See previous specbind for the reason behind this. */ + specbind (intern ("buffer-file-name"), Qnil); + del_range_byte (same_at_start, same_at_end); } inserted = 0; @@ -4020,12 +4169,11 @@ by calling `format-decode', which see. */) we are taking from the decoded string. */ inserted -= (ZV_BYTE - same_at_end) + (same_at_start - BEGV_BYTE); + /* See previous specbind for the reason behind this. */ + specbind (intern ("buffer-file-name"), Qnil); if (same_at_end != same_at_start) { - invalidate_buffer_caches (current_buffer, - BYTE_TO_CHAR (same_at_start), - same_at_end_charpos); - del_range_byte (same_at_start, same_at_end, 0); + del_range_byte (same_at_start, same_at_end); temp = GPT; eassert (same_at_start == GPT_BYTE); same_at_start = GPT_BYTE; @@ -4046,10 +4194,6 @@ by calling `format-decode', which see. */) same_at_start + inserted - BEGV_BYTE + BUF_BEG_BYTE (XBUFFER (conversion_buffer))) - same_at_start_charpos); - /* This binding is to avoid ask-user-about-supersession-threat - being called in insert_from_buffer (via in - prepare_to_modify_buffer). */ - specbind (intern ("buffer-file-name"), Qnil); insert_from_buffer (XBUFFER (conversion_buffer), same_at_start_charpos, inserted_chars, 0); /* Set `inserted' to the number of inserted characters. */ @@ -4504,7 +4648,7 @@ by calling `format-decode', which see. */) PT - BEG, Z - PT - inserted); if (read_quit) - Fsignal (Qquit, Qnil); + quit (); /* Retval needs to be dealt with in all cases consistently. */ if (NILP (val)) @@ -4614,8 +4758,7 @@ choose_write_coding_system (Lisp_Object start, Lisp_Object end, Lisp_Object file } /* If the decided coding-system doesn't specify end-of-line - format, we use that of - `default-buffer-file-coding-system'. */ + format, we use that of `buffer-file-coding-system'. */ if (! using_default_coding) { Lisp_Object dflt = BVAR (&buffer_defaults, buffer_file_coding_system); @@ -4693,7 +4836,7 @@ write_region (Lisp_Object start, Lisp_Object end, Lisp_Object filename, { int open_flags; int mode; - off_t offset IF_LINT (= 0); + off_t offset UNINIT; bool open_and_close_file = desc < 0; bool ok; int save_errno = 0; @@ -4701,7 +4844,7 @@ write_region (Lisp_Object start, Lisp_Object end, Lisp_Object filename, struct stat st; struct timespec modtime; ptrdiff_t count = SPECPDL_INDEX (); - ptrdiff_t count1 IF_LINT (= 0); + ptrdiff_t count1 UNINIT; Lisp_Object handler; Lisp_Object visit_file; Lisp_Object annotations; @@ -4810,7 +4953,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_BINARY | O_CREAT; + open_flags = O_WRONLY | O_CREAT; open_flags |= EQ (mustbenew, Qexcl) ? O_EXCL : !NILP (append) ? 0 : O_TRUNC; if (NUMBERP (append)) offset = file_offset (append); @@ -4929,7 +5072,7 @@ 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 | O_BINARY, 0); + int desc1 = emacs_open (fn, O_WRONLY, 0); if (desc1 >= 0) { struct stat st1; @@ -5382,25 +5525,15 @@ An argument specifies the modification time value to use static Lisp_Object auto_save_error (Lisp_Object error_val) { - Lisp_Object msg; - int i; - auto_save_error_occurred = 1; ring_bell (XFRAME (selected_frame)); AUTO_STRING (format, "Auto-saving %s: %s"); - msg = CALLN (Fformat, format, BVAR (current_buffer, name), - Ferror_message_string (error_val)); - - for (i = 0; i < 3; ++i) - { - if (i == 0) - message3 (msg); - else - message3_nolog (msg); - Fsleep_for (make_number (1), Qnil); - } + Lisp_Object msg = CALLN (Fformat, format, BVAR (current_buffer, name), + Ferror_message_string (error_val)); + call3 (intern ("display-warning"), + intern ("auto-save"), msg, intern ("error")); return Qnil; } @@ -5801,8 +5934,6 @@ syms_of_fileio (void) which gives a list of operations it handles. */ DEFSYM (Qoperations, "operations"); - DEFSYM (Qundo_auto__undoable_change_no_timer, "undo-auto--undoable-change-no-timer"); - DEFSYM (Qexpand_file_name, "expand-file-name"); DEFSYM (Qsubstitute_in_file_name, "substitute-in-file-name"); DEFSYM (Qdirectory_file_name, "directory-file-name"); @@ -5814,6 +5945,7 @@ syms_of_fileio (void) DEFSYM (Qmake_directory_internal, "make-directory-internal"); DEFSYM (Qmake_directory, "make-directory"); DEFSYM (Qdelete_file, "delete-file"); + 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"); DEFSYM (Qmake_symbolic_link, "make-symbolic-link"); @@ -5852,6 +5984,7 @@ syms_of_fileio (void) DEFSYM (Qfile_error, "file-error"); DEFSYM (Qfile_already_exists, "file-already-exists"); DEFSYM (Qfile_date_error, "file-date-error"); + DEFSYM (Qfile_missing, "file-missing"); DEFSYM (Qfile_notify_error, "file-notify-error"); DEFSYM (Qexcl, "excl"); @@ -5904,6 +6037,11 @@ behaves as if file names were encoded in `utf-8'. */); Fput (Qfile_date_error, Qerror_message, build_pure_c_string ("Cannot set file date")); + Fput (Qfile_missing, Qerror_conditions, + Fpurecopy (list3 (Qfile_missing, Qfile_error, Qerror))); + Fput (Qfile_missing, Qerror_message, + build_pure_c_string ("File is missing")); + Fput (Qfile_notify_error, Qerror_conditions, Fpurecopy (list3 (Qfile_notify_error, Qfile_error, Qerror))); Fput (Qfile_notify_error, Qerror_message, @@ -6071,6 +6209,7 @@ This includes interactive calls to `delete-file' and defsubr (&Smake_directory_internal); defsubr (&Sdelete_directory_internal); defsubr (&Sdelete_file); + defsubr (&Sfile_name_case_insensitive_p); defsubr (&Srename_file); defsubr (&Sadd_name_to_file); defsubr (&Smake_symbolic_link); |