>From a2df04a05a68d5a4afa39cfe463f72108b31bc25 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Tue, 13 Nov 2018 09:29:14 -0800 Subject: [PATCH] Act like POSIX sh if $HOME is relative POSIX says sh ~/foo should act like $HOME/foo even if $HOME is relative, so be consistent with that (Bug#33255). * admin/merge-gnulib (GNULIB_MODULES): Add dosname. * src/buffer.c (init_buffer): Use emacs_wd to get initial working directory with slash appended if needed. (default-directory): Say it must be absolute. * src/emacs.c (emacs_wd): New global variable. (init_cmdargs): Dir arg is now char const *. (main): Set emacs_wd. * src/emacs.c (main) [NS_IMPL_COCOA]: * src/fileio.c (Fexpand_file_name): Use get_homedir instead of egetenv ("HOME"). * src/fileio.c: Include dosname.h, for IS_ABSOLUTE_FILE_NAME. (splice_dir_file, get_homedir): New functions. * src/xrdb.c (gethomedir): Remove. All callers changed to use get_homedir and splice_dir_file. * test/src/fileio-tests.el (fileio-tests--relative-HOME): New test. --- admin/merge-gnulib | 2 +- src/buffer.c | 28 ++++++------------ src/emacs.c | 20 ++++++++----- src/fileio.c | 62 +++++++++++++++++++++++++++++++++++++--- src/lisp.h | 3 ++ src/xrdb.c | 54 +++++++--------------------------- test/src/fileio-tests.el | 8 ++++++ 7 files changed, 103 insertions(+), 74 deletions(-) diff --git a/admin/merge-gnulib b/admin/merge-gnulib index 575e3fa74a..84dcb0b875 100755 --- a/admin/merge-gnulib +++ b/admin/merge-gnulib @@ -30,7 +30,7 @@ GNULIB_MODULES= careadlinkat close-stream count-leading-zeros count-one-bits count-trailing-zeros crypto/md5-buffer crypto/sha1-buffer crypto/sha256-buffer crypto/sha512-buffer - d-type diffseq dtoastr dtotimespec dup2 + d-type diffseq dosname dtoastr dtotimespec dup2 environ execinfo explicit_bzero faccessat fcntl fcntl-h fdatasync fdopendir filemode filevercmp flexmember fpieee fstatat fsusage fsync diff --git a/src/buffer.c b/src/buffer.c index ac2de7d19f..90ef886b22 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -5268,9 +5268,7 @@ init_buffer_once (void) void init_buffer (int initialized) { - char *pwd; Lisp_Object temp; - ptrdiff_t len; #ifdef USE_MMAP_FOR_BUFFERS if (initialized) @@ -5324,7 +5322,7 @@ init_buffer (int initialized) if (NILP (BVAR (&buffer_defaults, enable_multibyte_characters))) Fset_buffer_multibyte (Qnil); - pwd = emacs_get_current_dir_name (); + char const *pwd = emacs_wd; if (!pwd) { @@ -5336,22 +5334,16 @@ init_buffer (int initialized) { /* Maybe this should really use some standard subroutine whose definition is filename syntax dependent. */ - len = strlen (pwd); - if (!(IS_DIRECTORY_SEP (pwd[len - 1]))) - { - /* Grow buffer to add directory separator and '\0'. */ - pwd = realloc (pwd, len + 2); - if (!pwd) - fatal ("get_current_dir_name: %s\n", strerror (errno)); - pwd[len] = DIRECTORY_SEP; - pwd[len + 1] = '\0'; - len++; - } + ptrdiff_t len = strlen (pwd); + bool add_slash = ! IS_DIRECTORY_SEP (pwd[len - 1]); /* At this moment, we still don't know how to decode the directory name. So, we keep the bytes in unibyte form so that file I/O routines correctly get the original bytes. */ - bset_directory (current_buffer, make_unibyte_string (pwd, len)); + Lisp_Object dirname = make_unibyte_string (pwd, len + add_slash); + if (add_slash) + SSET (dirname, len, DIRECTORY_SEP); + bset_directory (current_buffer, dirname); /* Add /: to the front of the name if it would otherwise be treated as magic. */ @@ -5372,8 +5364,6 @@ init_buffer (int initialized) temp = get_minibuffer (0); bset_directory (XBUFFER (temp), BVAR (current_buffer, directory)); - - free (pwd); } /* Similar to defvar_lisp but define a variable whose value is the @@ -5706,8 +5696,8 @@ visual lines rather than logical lines. See the documentation of DEFVAR_PER_BUFFER ("default-directory", &BVAR (current_buffer, directory), Qstringp, doc: /* Name of default directory of current buffer. -It should be a directory name (as opposed to a directory file-name). -On GNU and Unix systems, directory names end in a slash `/'. +It should be an absolute directory name; on GNU and Unix systems, +these names start with `/' or `~' and end with `/'. To interactively change the default directory, use command `cd'. */); DEFVAR_PER_BUFFER ("auto-fill-function", &BVAR (current_buffer, auto_fill_function), diff --git a/src/emacs.c b/src/emacs.c index 512174d562..acb4959bfe 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -204,6 +204,9 @@ HANDLE w32_daemon_event; char **initial_argv; int initial_argc; +/* The name of the working directory, or NULL if this info is unavailable. */ +char const *emacs_wd; + static void sort_args (int argc, char **argv); static void syms_of_emacs (void); @@ -406,7 +409,7 @@ terminate_due_to_signal (int sig, int backtrace_limit) /* Code for dealing with Lisp access to the Unix command line. */ static void -init_cmdargs (int argc, char **argv, int skip_args, char *original_pwd) +init_cmdargs (int argc, char **argv, int skip_args, char const *original_pwd) { int i; Lisp_Object name, dir, handler; @@ -694,7 +697,7 @@ main (int argc, char **argv) char *ch_to_dir = 0; /* If we use --chdir, this records the original directory. */ - char *original_pwd = 0; + char const *original_pwd = 0; /* Record (approximately) where the stack begins. */ stack_bottom = (char *) &stack_bottom_variable; @@ -794,6 +797,8 @@ main (int argc, char **argv) exit (0); } + emacs_wd = emacs_get_current_dir_name (); + if (argmatch (argv, argc, "-chdir", "--chdir", 4, &ch_to_dir, &skip_args)) { #ifdef WINDOWSNT @@ -804,13 +809,14 @@ main (int argc, char **argv) filename_from_ansi (ch_to_dir, newdir); ch_to_dir = newdir; #endif - original_pwd = emacs_get_current_dir_name (); if (chdir (ch_to_dir) != 0) { fprintf (stderr, "%s: Can't chdir to %s: %s\n", argv[0], ch_to_dir, strerror (errno)); exit (1); } + original_pwd = emacs_wd; + emacs_wd = emacs_get_current_dir_name (); } #if defined (HAVE_SETRLIMIT) && defined (RLIMIT_STACK) && !defined (CYGWIN) @@ -1289,21 +1295,21 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem { #ifdef NS_IMPL_COCOA /* Started from GUI? */ - /* FIXME: Do the right thing if getenv returns NULL, or if + /* FIXME: Do the right thing if get_homedir returns "", or if chdir fails. */ if (! inhibit_window_system && ! isatty (STDIN_FILENO) && ! ch_to_dir) - chdir (getenv ("HOME")); + chdir (get_homedir ()); if (skip_args < argc) { if (!strncmp (argv[skip_args], "-psn", 4)) { skip_args += 1; - if (! ch_to_dir) chdir (getenv ("HOME")); + if (! ch_to_dir) chdir (get_homedir ()); } else if (skip_args+1 < argc && !strncmp (argv[skip_args+1], "-psn", 4)) { skip_args += 2; - if (! ch_to_dir) chdir (getenv ("HOME")); + if (! ch_to_dir) chdir (get_homedir ()); } } #endif /* COCOA */ diff --git a/src/fileio.c b/src/fileio.c index 7fb865809f..e178c39fc1 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -96,6 +96,7 @@ along with GNU Emacs. If not, see . */ #include #include #include +#include #include #include #include @@ -1093,8 +1094,7 @@ the root directory. */) { Lisp_Object tem; - if (!(newdir = egetenv ("HOME"))) - newdir = newdirlim = ""; + newdir = get_homedir (); nm++; #ifdef WINDOWSNT if (newdir[0]) @@ -1109,7 +1109,7 @@ the root directory. */) #endif tem = build_string (newdir); newdirlim = newdir + SBYTES (tem); - /* `egetenv' may return a unibyte string, which will bite us + /* get_homedir may return a unibyte string, which will bite us if we expect the directory to be multibyte. */ if (multibyte && !STRING_MULTIBYTE (tem)) { @@ -1637,7 +1637,6 @@ See also the function `substitute-in-file-name'.") } #endif -/* If /~ or // appears, discard everything through first slash. */ static bool file_name_absolute_p (const char *filename) { @@ -1650,6 +1649,61 @@ file_name_absolute_p (const char *filename) ); } +/* Put into BUF the concatenation of DIR and FILE, with an intervening + directory separator if needed. Return a pointer to the null byte + at the end of the concatenated string. */ +char * +splice_dir_file (char *buf, char const *dir, char const *file) +{ + char *e = stpcpy (buf, dir); + *e = DIRECTORY_SEP; + e += ! (buf < e && IS_DIRECTORY_SEP (e[-1])); + return stpcpy (e, file); +} + +/* Get the home directory, an absolute file name. Return the empty + string on failure. The returned value does not survive garbage + collection, calls to this function, or calls to the getpwnam class + of functions. */ +char const * +get_homedir (void) +{ + char const *home = egetenv ("HOME"); + if (!home) + { + static char const *userenv[] = {"LOGNAME", "USER"}; + struct passwd *pw = NULL; + for (int i = 0; i < ARRAYELTS (userenv); i++) + { + char *user = egetenv (userenv[i]); + if (user) + { + pw = getpwnam (user); + if (pw) + break; + } + } + if (!pw) + pw = getpwuid (getuid ()); + if (pw) + home = pw->pw_dir; + if (!home) + return ""; + } + if (IS_ABSOLUTE_FILE_NAME (home)) + return home; + if (!emacs_wd) + error ("$HOME is relative to unknown directory"); + static char *ahome; + static ptrdiff_t ahomesize; + ptrdiff_t ahomelenbound = strlen (emacs_wd) + 1 + strlen (home) + 1; + if (ahomesize <= ahomelenbound) + ahome = xpalloc (ahome, &ahomesize, ahomelenbound + 1 - ahomesize, -1, 1); + splice_dir_file (ahome, emacs_wd, home); + return ahome; +} + +/* If /~ or // appears, discard everything through first slash. */ static char * search_embedded_absfilename (char *nm, char *endp) { diff --git a/src/lisp.h b/src/lisp.h index f8ffb33a64..7e7dba631f 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -4061,6 +4061,8 @@ extern void syms_of_marker (void); /* Defined in fileio.c. */ +extern char *splice_dir_file (char *, char const *, char const *); +extern char const *get_homedir (void); extern Lisp_Object expand_and_dir_to_file (Lisp_Object); extern Lisp_Object write_region (Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object, @@ -4185,6 +4187,7 @@ extern void syms_of_frame (void); /* Defined in emacs.c. */ extern char **initial_argv; extern int initial_argc; +extern char const *emacs_wd; #if defined (HAVE_X_WINDOWS) || defined (HAVE_NS) extern bool display_arg; #endif diff --git a/src/xrdb.c b/src/xrdb.c index 4abf1ad84e..87c2faf659 100644 --- a/src/xrdb.c +++ b/src/xrdb.c @@ -202,35 +202,6 @@ magic_db (const char *string, ptrdiff_t string_len, const char *class, } -static char * -gethomedir (void) -{ - struct passwd *pw; - char *ptr; - char *copy; - - if ((ptr = getenv ("HOME")) == NULL) - { - if ((ptr = getenv ("LOGNAME")) != NULL - || (ptr = getenv ("USER")) != NULL) - pw = getpwnam (ptr); - else - pw = getpwuid (getuid ()); - - if (pw) - ptr = pw->pw_dir; - } - - if (ptr == NULL) - return xstrdup ("/"); - - ptrdiff_t len = strlen (ptr); - copy = xmalloc (len + 2); - strcpy (copy + len, "/"); - return memcpy (copy, ptr, len); -} - - /* Find the first element of SEARCH_PATH which exists and is readable, after expanding the %-escapes. Return 0 if we didn't find any, and the path name of the one we found otherwise. */ @@ -316,12 +287,11 @@ get_user_app (const char *class) if (! db) { /* Check in the home directory. This is a bit of a hack; let's - hope one's home directory doesn't contain any %-escapes. */ - char *home = gethomedir (); + hope one's home directory doesn't contain ':' or '%'. */ + char const *home = get_homedir (); db = search_magic_path (home, class, "%L/%N"); if (! db) db = search_magic_path (home, class, "%N"); - xfree (home); } return db; @@ -346,10 +316,9 @@ get_user_db (Display *display) else { /* Use ~/.Xdefaults. */ - char *home = gethomedir (); - ptrdiff_t homelen = strlen (home); - char *filename = xrealloc (home, homelen + sizeof xdefaults); - strcpy (filename + homelen, xdefaults); + char const *home = get_homedir (); + char *filename = xmalloc (strlen (home) + 1 + sizeof xdefaults); + splice_dir_file (filename, home, xdefaults); db = XrmGetFileDatabase (filename); xfree (filename); } @@ -380,13 +349,12 @@ get_environ_db (void) if (STRINGP (system_name)) { /* Use ~/.Xdefaults-HOSTNAME. */ - char *home = gethomedir (); - ptrdiff_t homelen = strlen (home); - ptrdiff_t filenamesize = (homelen + sizeof xdefaults - + 1 + SBYTES (system_name)); - p = filename = xrealloc (home, filenamesize); - lispstpcpy (stpcpy (stpcpy (filename + homelen, xdefaults), "-"), - system_name); + char const *home = get_homedir (); + p = filename = xmalloc (strlen (home) + 1 + sizeof xdefaults + + 1 + SBYTES (system_name)); + char *e = splice_dir_file (p, home, xdefaults); + *e++ = '/'; + lispstpcpy (e, system_name); } } diff --git a/test/src/fileio-tests.el b/test/src/fileio-tests.el index 5d12685fa1..b7b78bbda0 100644 --- a/test/src/fileio-tests.el +++ b/test/src/fileio-tests.el @@ -95,3 +95,11 @@ fileio-tests--symlink-failure (should (equal (file-name-as-directory "d:/abc/") "d:/abc/")) (should (equal (file-name-as-directory "D:\\abc/") "d:/abc/")) (should (equal (file-name-as-directory "D:/abc//") "d:/abc//"))) + +(ert-deftest fileio-tests--relative-HOME () + "Test that expand-file-name works even when HOME is relative." + (let ((old-home (getenv "HOME"))) + (setenv "HOME" "a/b/c") + (should (equal (expand-file-name "~/foo") + (expand-file-name "a/b/c/foo"))) + (setenv "HOME" old-home))) -- 2.19.1