adjustname() should canonicalize the given path, but it does not do it always, resulting in some bizzare situations.
it calls realpath(3) but when it fails the given path is returned as-is. This in practice can happen quite often since it's not usual for an editor to visit new files. A quick way to replicate the issue is to $ mg foo # assuming `foo' does not exist type something then save C-x C-f foo RET surprise, now you're in "foo<2>", a different buffer for the same file. On the other hand, expandtilde() has its own fair share of issues too. We can instead syntactically clean the path. It loose the "follow symlink" part, but it always work and is generally less surprising. I've checked that all the callers of adjustname() either provide an absolute path or are happy with it being resolved via the current buffer working directory. getbufcwd() respects the global-wd-mode so that case is also handled. (Some of the calls can/should be bubbled up or dropped, but that's for another diff.) An annoying thing is that adjustname() returns a pointer to a static area and it's not uncommon for that path to be fed to adjustname() again, hence the temp buffer. One quick way to hit that case is "mg ." which will adjustname() in main and then later again in dired_(). ok? Index: cscope.c =================================================================== RCS file: /home/cvs/src/usr.bin/mg/cscope.c,v retrieving revision 1.22 diff -u -p -r1.22 cscope.c --- cscope.c 8 Mar 2023 04:43:11 -0000 1.22 +++ cscope.c 23 Apr 2023 15:28:25 -0000 @@ -338,7 +338,7 @@ jumptomatch(void) if (curmatch == NULL || currecord == NULL) return (FALSE); - adjf = adjustname(currecord->filename, TRUE); + adjf = adjustname(currecord->filename); if (adjf == NULL) return (FALSE); if ((bp = findbuffer(adjf)) == NULL) Index: def.h =================================================================== RCS file: /home/cvs/src/usr.bin/mg/def.h,v retrieving revision 1.180 diff -u -p -r1.180 def.h --- def.h 21 Apr 2023 13:39:37 -0000 1.180 +++ def.h 23 Apr 2023 15:30:55 -0000 @@ -485,7 +485,7 @@ int ffclose(FILE *, struct buffer *); int ffputbuf(FILE *, struct buffer *, int); int ffgetline(FILE *, char *, int, int *); int fbackupfile(const char *); -char *adjustname(const char *, int); +char *adjustname(const char *); FILE *startupfile(char *, char *, char *, size_t); int copy(char *, char *); struct list *make_file_list(char *); Index: dir.c =================================================================== RCS file: /home/cvs/src/usr.bin/mg/dir.c,v retrieving revision 1.33 diff -u -p -r1.33 dir.c --- dir.c 8 Mar 2023 04:43:11 -0000 1.33 +++ dir.c 23 Apr 2023 15:28:25 -0000 @@ -117,7 +117,7 @@ do_makedir(char *path) mode_t dir_mode, f_mode, oumask; char *slash; - if ((path = adjustname(path, TRUE)) == NULL) + if ((path = adjustname(path)) == NULL) return (FALSE); /* Remove trailing slashes */ Index: dired.c =================================================================== RCS file: /home/cvs/src/usr.bin/mg/dired.c,v retrieving revision 1.102 diff -u -p -r1.102 dired.c --- dired.c 8 Mar 2023 04:43:11 -0000 1.102 +++ dired.c 23 Apr 2023 15:28:25 -0000 @@ -469,7 +469,7 @@ d_copy(int f, int n) else if (bufp[0] == '\0') return (FALSE); - topath = adjustname(toname, TRUE); + topath = adjustname(toname); if (stat(topath, &statbuf) == 0) { if (S_ISDIR(statbuf.st_mode)) { ret = snprintf(toname, sizeof(toname), "%s/%s", @@ -479,7 +479,7 @@ d_copy(int f, int n) ewprintf("Directory name too long"); return (FALSE); } - topath = adjustname(toname, TRUE); + topath = adjustname(toname); } } if (topath == NULL) @@ -528,7 +528,7 @@ d_rename(int f, int n) else if (bufp[0] == '\0') return (FALSE); - topath = adjustname(toname, TRUE); + topath = adjustname(toname); if (stat(topath, &statbuf) == 0) { if (S_ISDIR(statbuf.st_mode)) { ret = snprintf(toname, sizeof(toname), "%s/%s", @@ -538,7 +538,7 @@ d_rename(int f, int n) ewprintf("Directory name too long"); return (FALSE); } - topath = adjustname(toname, TRUE); + topath = adjustname(toname); } } if (topath == NULL) @@ -901,7 +901,7 @@ dired_(char *dname) int i; size_t len; - if ((dname = adjustname(dname, TRUE)) == NULL) { + if ((dname = adjustname(dname)) == NULL) { dobeep(); ewprintf("Bad directory name"); return (NULL); @@ -1110,7 +1110,7 @@ dired_jump(int f, int n) if (ret != TRUE) return ret; - fname = adjustname(fname, TRUE); + fname = adjustname(fname); if (fname != NULL) gotofile(fname); @@ -1134,7 +1134,7 @@ d_gotofile(int f, int n) else if (fnp[0] == '\0') return (FALSE); - fpth = adjustname(fpath, TRUE); /* Removes last '/' if dir... */ + fpth = adjustname(fpath); /* Removes last '/' if dir... */ if (fpth == NULL || strlen(fpth) == lenfpath - 1) { /* ...hence -1. */ ewprintf("No file to find"); /* Current directory given so */ return (TRUE); /* return at present location. */ Index: extend.c =================================================================== RCS file: /home/cvs/src/usr.bin/mg/extend.c,v retrieving revision 1.80 diff -u -p -r1.80 extend.c --- extend.c 17 Apr 2023 10:11:30 -0000 1.80 +++ extend.c 23 Apr 2023 15:28:25 -0000 @@ -629,7 +629,7 @@ evalfile(int f, int n) return (ABORT); if (bufp[0] == '\0') return (FALSE); - if ((bufp = adjustname(fname, TRUE)) == NULL) + if ((bufp = adjustname(fname)) == NULL) return (FALSE); ret = ffropen(&ffp, bufp, NULL); if (ret == FIODIR) Index: file.c =================================================================== RCS file: /home/cvs/src/usr.bin/mg/file.c,v retrieving revision 1.103 diff -u -p -r1.103 file.c --- file.c 8 Mar 2023 04:43:11 -0000 1.103 +++ file.c 23 Apr 2023 15:28:25 -0000 @@ -37,7 +37,7 @@ fileinsert(int f, int n) return (ABORT); else if (bufp[0] == '\0') return (FALSE); - adjf = adjustname(bufp, TRUE); + adjf = adjustname(bufp); if (adjf == NULL) return (FALSE); return (insertfile(adjf, NULL, FALSE)); @@ -64,7 +64,7 @@ filevisit(int f, int n) return (ABORT); else if (bufp[0] == '\0') return (FALSE); - adjf = adjustname(fname, TRUE); + adjf = adjustname(fname); if (adjf == NULL) return (FALSE); if (fisdir(adjf) == TRUE) @@ -116,7 +116,7 @@ do_filevisitalt(char *fn) if (status == ABORT || status == FALSE) return (ABORT); - adjf = adjustname(fn, TRUE); + adjf = adjustname(fn); if (adjf == NULL) return (FALSE); if (fisdir(adjf) == TRUE) @@ -165,7 +165,7 @@ poptofile(int f, int n) return (ABORT); else if (bufp[0] == '\0') return (FALSE); - adjf = adjustname(fname, TRUE); + adjf = adjustname(fname); if (adjf == NULL) return (FALSE); if (fisdir(adjf) == TRUE) @@ -519,7 +519,7 @@ filewrite(int f, int n) else if (bufp[0] == '\0') return (FALSE); - adjfname = adjustname(fname, TRUE); + adjfname = adjustname(fname); if (adjfname == NULL) return (FALSE); Index: fileio.c =================================================================== RCS file: /home/cvs/src/usr.bin/mg/fileio.c,v retrieving revision 1.111 diff -u -p -r1.111 fileio.c --- fileio.c 30 Mar 2023 19:00:02 -0000 1.111 +++ fileio.c 23 Apr 2023 16:02:37 -0000 @@ -288,37 +288,73 @@ fbackupfile(const char *fn) } /* - * Convert "fn" to a canonicalized absolute filename, replacing - * a leading ~/ with the user's home dir, following symlinks, and - * remove all occurrences of /./ and /../ + * Convert "p" to a canonicalized absolute filename, replacing + * a leading ~/ with the user's home dir and resolving all + * occurrences of /./ and /../ */ char * -adjustname(const char *fn, int slashslash) +adjustname(const char *p) { static char fnb[PATH_MAX]; - const char *cp, *ep = NULL; - char *path; + const char *ep; + struct passwd *pw; + char *q, buf[PATH_MAX], user[LOGIN_NAME_MAX]; + size_t ulen; - if (slashslash == TRUE) { - cp = fn + strlen(fn) - 1; - for (; cp >= fn; cp--) { - if (ep && (*cp == '/')) { - fn = ep; - break; - } - if (*cp == '/' || *cp == '~') - ep = cp; - else - ep = NULL; + buf[0] = '\0'; + if (*p == '~') { + p++; + ep = strchr(p, '/'); + if (ep == NULL) + ep = strchr(p, '\0'); + ulen = ep - p; + if (ulen == 0) + pw = getpwuid(geteuid()); + else { + if (ulen >= sizeof(user)) + return (NULL); + memcpy(user, p, ulen); + user[ulen] = '\0'; + pw = getpwnam(user); } - } - if ((path = expandtilde(fn)) == NULL) + if (pw == NULL || + strlcpy(buf, pw->pw_dir, sizeof(buf)) >= sizeof(buf) || + strlcat(buf, "/", sizeof(buf)) >= sizeof(buf)) { + dobeep_msg("can't expand tilde"); + return (NULL); + } + p = ep; + } else if (*p != '/' && !getbufcwd(buf, sizeof(buf))) return (NULL); - if (realpath(path, fnb) == NULL) - (void)strlcpy(fnb, path, sizeof(fnb)); + if (strlcat(buf, p, sizeof(buf)) >= sizeof(buf)) { + dobeep_msg("Path too long"); + return (NULL); + } - free(path); + p = q = buf; + while (*p && (q - buf) < sizeof(buf)) { + if (p[0] == '/' && (p[1] == '/' || p[1] == '\0')) + p++; + else if (p[0] == '/' && p[1] == '.' && + (p[2] == '/' || p[2] == '\0')) + p += 2; + else if (p[0] == '/' && p[1] == '.' && p[2] == '.' && + (p[3] == '/' || p[3] == '\0')) { + p += 3; + while (q != buf && *--q != '/') + ; /* nop */ + } else + *q++ = *p++; + } + if (*p != '\0' || q - buf >= sizeof(buf)) { + dobeep_msg("Path too long"); + return (NULL); + } + if (q == buf) + *q++ = '/'; + *q = '\0'; + (void)strlcpy(fnb, buf, sizeof(fnb)); return (fnb); } @@ -464,10 +500,10 @@ make_file_list(char *buf) len = strlen(buf); if (len && buf[len - 1] == '.') { buf[len - 1] = 'x'; - dir = adjustname(buf, TRUE); + dir = adjustname(buf); buf[len - 1] = '.'; } else - dir = adjustname(buf, TRUE); + dir = adjustname(buf); if (dir == NULL) return (NULL); /* @@ -648,7 +684,7 @@ backuptohomedir(int f, int n) char *p; if (bkupdir == NULL) { - p = adjustname(c, TRUE); + p = adjustname(c); bkupdir = strndup(p, NFILEN); if (bkupdir == NULL) return(FALSE); @@ -691,67 +727,4 @@ bkupleavetmp(const char *fn) return (TRUE); return (FALSE); -} - -/* - * Expand file names beginning with '~' if appropriate: - * 1, if ./~fn exists, continue without expanding tilde. - * 2, else, if username 'fn' exists, expand tilde with home directory path. - * 3, otherwise, continue and create new buffer called ~fn. - */ -char * -expandtilde(const char *fn) -{ - struct passwd *pw; - struct stat statbuf; - const char *cp; - char user[LOGIN_NAME_MAX], path[NFILEN]; - char *ret; - size_t ulen, plen; - - path[0] = '\0'; - - if (fn[0] != '~' || stat(fn, &statbuf) == 0) { - if ((ret = strndup(fn, NFILEN)) == NULL) - return (NULL); - return(ret); - } - cp = strchr(fn, '/'); - if (cp == NULL) - cp = fn + strlen(fn); /* point to the NUL byte */ - ulen = cp - &fn[1]; - if (ulen >= sizeof(user)) { - if ((ret = strndup(fn, NFILEN)) == NULL) - return (NULL); - return(ret); - } - if (ulen == 0) /* ~/ or ~ */ - pw = getpwuid(geteuid()); - else { /* ~user/ or ~user */ - memcpy(user, &fn[1], ulen); - user[ulen] = '\0'; - pw = getpwnam(user); - } - if (pw != NULL) { - plen = strlcpy(path, pw->pw_dir, sizeof(path)); - if (plen == 0 || path[plen - 1] != '/') { - if (strlcat(path, "/", sizeof(path)) >= sizeof(path)) { - dobeep(); - ewprintf("Path too long"); - return (NULL); - } - } - fn = cp; - if (*fn == '/') - fn++; - } - if (strlcat(path, fn, sizeof(path)) >= sizeof(path)) { - dobeep(); - ewprintf("Path too long"); - return (NULL); - } - if ((ret = strndup(path, NFILEN)) == NULL) - return (NULL); - - return (ret); } Index: grep.c =================================================================== RCS file: /home/cvs/src/usr.bin/mg/grep.c,v retrieving revision 1.50 diff -u -p -r1.50 grep.c --- grep.c 8 Mar 2023 04:43:11 -0000 1.50 +++ grep.c 23 Apr 2023 15:28:25 -0000 @@ -288,7 +288,7 @@ compile_goto_error(int f, int n) goto fail; adjf = path; } else { - adjf = adjustname(fname, TRUE); + adjf = adjustname(fname); } free(line); Index: main.c =================================================================== RCS file: /home/cvs/src/usr.bin/mg/main.c,v retrieving revision 1.95 diff -u -p -r1.95 main.c --- main.c 14 Apr 2023 15:34:08 -0000 1.95 +++ main.c 23 Apr 2023 15:28:25 -0000 @@ -200,7 +200,7 @@ main(int argc, char **argv) startrow = lval; } else { notnum: - cp = adjustname(argv[i], FALSE); + cp = adjustname(argv[i]); if (cp != NULL) { if (nfiles == 1) splitwind(0, 1); Index: tags.c =================================================================== RCS file: /home/cvs/src/usr.bin/mg/tags.c,v retrieving revision 1.27 diff -u -p -r1.27 tags.c --- tags.c 29 Mar 2023 19:09:04 -0000 1.27 +++ tags.c 23 Apr 2023 15:28:25 -0000 @@ -508,7 +508,7 @@ loadbuffer(char *bname) return (FALSE); } } else { - if ((adjf = adjustname(bname, TRUE)) == NULL) + if ((adjf = adjustname(bname)) == NULL) return (FALSE); if ((bufp = findbuffer(adjf)) == NULL) return (FALSE);