1 /* $OpenBSD: path.c,v 1.23 2019/06/28 13:34:59 deraadt Exp $ */ 2 3 #include <sys/stat.h> 4 5 #include <errno.h> 6 #include <string.h> 7 #include <unistd.h> 8 9 #include "sh.h" 10 11 /* 12 * Contains a routine to search a : separated list of 13 * paths (a la CDPATH) and make appropriate file names. 14 * Also contains a routine to simplify .'s and ..'s out of 15 * a path name. 16 * 17 * Larry Bouzane (larry@cs.mun.ca) 18 */ 19 20 static char *do_phys_path(XString *, char *, const char *); 21 22 /* 23 * Makes a filename into result using the following algorithm. 24 * - make result NULL 25 * - if file starts with '/', append file to result & set cdpathp to NULL 26 * - if file starts with ./ or ../ append cwd and file to result 27 * and set cdpathp to NULL 28 * - if the first element of cdpathp doesnt start with a '/' xx or '.' xx 29 * then cwd is appended to result. 30 * - the first element of cdpathp is appended to result 31 * - file is appended to result 32 * - cdpathp is set to the start of the next element in cdpathp (or NULL 33 * if there are no more elements. 34 * The return value indicates whether a non-null element from cdpathp 35 * was appended to result. 36 */ 37 int 38 make_path(const char *cwd, const char *file, 39 char **cdpathp, /* & of : separated list */ 40 XString *xsp, 41 int *phys_pathp) 42 { 43 int rval = 0; 44 int use_cdpath = 1; 45 char *plist; 46 int len; 47 int plen = 0; 48 char *xp = Xstring(*xsp, xp); 49 50 if (!file) 51 file = null; 52 53 if (file[0] == '/') { 54 *phys_pathp = 0; 55 use_cdpath = 0; 56 } else { 57 if (file[0] == '.') { 58 char c = file[1]; 59 60 if (c == '.') 61 c = file[2]; 62 if (c == '/' || c == '\0') 63 use_cdpath = 0; 64 } 65 66 plist = *cdpathp; 67 if (!plist) 68 use_cdpath = 0; 69 else if (use_cdpath) { 70 char *pend; 71 72 for (pend = plist; *pend && *pend != ':'; pend++) 73 ; 74 plen = pend - plist; 75 *cdpathp = *pend ? ++pend : NULL; 76 } 77 78 if ((use_cdpath == 0 || !plen || plist[0] != '/') && 79 (cwd && *cwd)) { 80 len = strlen(cwd); 81 XcheckN(*xsp, xp, len); 82 memcpy(xp, cwd, len); 83 xp += len; 84 if (cwd[len - 1] != '/') 85 Xput(*xsp, xp, '/'); 86 } 87 *phys_pathp = Xlength(*xsp, xp); 88 if (use_cdpath && plen) { 89 XcheckN(*xsp, xp, plen); 90 memcpy(xp, plist, plen); 91 xp += plen; 92 if (plist[plen - 1] != '/') 93 Xput(*xsp, xp, '/'); 94 rval = 1; 95 } 96 } 97 98 len = strlen(file) + 1; 99 XcheckN(*xsp, xp, len); 100 memcpy(xp, file, len); 101 102 if (!use_cdpath) 103 *cdpathp = NULL; 104 105 return rval; 106 } 107 108 /* 109 * Simplify pathnames containing "." and ".." entries. 110 * ie, simplify_path("/a/b/c/./../d/..") returns "/a/b" 111 */ 112 void 113 simplify_path(char *path) 114 { 115 char *cur; 116 char *t; 117 int isrooted; 118 char *very_start = path; 119 char *start; 120 121 if (!*path) 122 return; 123 124 if ((isrooted = (path[0] == '/'))) 125 very_start++; 126 127 /* Before After 128 * /foo/ /foo 129 * /foo/../../bar /bar 130 * /foo/./blah/.. /foo 131 * . . 132 * .. .. 133 * ./foo foo 134 * foo/../../../bar ../../bar 135 */ 136 137 for (cur = t = start = very_start; ; ) { 138 /* treat multiple '/'s as one '/' */ 139 while (*t == '/') 140 t++; 141 142 if (*t == '\0') { 143 if (cur == path) 144 /* convert empty path to dot */ 145 *cur++ = '.'; 146 *cur = '\0'; 147 break; 148 } 149 150 if (t[0] == '.') { 151 if (!t[1] || t[1] == '/') { 152 t += 1; 153 continue; 154 } else if (t[1] == '.' && (!t[2] || t[2] == '/')) { 155 if (!isrooted && cur == start) { 156 if (cur != very_start) 157 *cur++ = '/'; 158 *cur++ = '.'; 159 *cur++ = '.'; 160 start = cur; 161 } else if (cur != start) 162 while (--cur > start && *cur != '/') 163 ; 164 t += 2; 165 continue; 166 } 167 } 168 169 if (cur != very_start) 170 *cur++ = '/'; 171 172 /* find/copy next component of pathname */ 173 while (*t && *t != '/') 174 *cur++ = *t++; 175 } 176 } 177 178 179 void 180 set_current_wd(char *path) 181 { 182 int len; 183 char *p = path; 184 185 if (!p && !(p = ksh_get_wd(NULL, 0))) 186 p = null; 187 188 len = strlen(p) + 1; 189 190 if (len > current_wd_size) 191 current_wd = aresize(current_wd, current_wd_size = len, APERM); 192 memcpy(current_wd, p, len); 193 if (p != path && p != null) 194 afree(p, ATEMP); 195 } 196 197 char * 198 get_phys_path(const char *path) 199 { 200 XString xs; 201 char *xp; 202 203 Xinit(xs, xp, strlen(path) + 1, ATEMP); 204 205 xp = do_phys_path(&xs, xp, path); 206 207 if (!xp) 208 return NULL; 209 210 if (Xlength(xs, xp) == 0) 211 Xput(xs, xp, '/'); 212 Xput(xs, xp, '\0'); 213 214 return Xclose(xs, xp); 215 } 216 217 static char * 218 do_phys_path(XString *xsp, char *xp, const char *path) 219 { 220 const char *p, *q; 221 int len, llen; 222 int savepos; 223 char lbuf[PATH_MAX]; 224 225 Xcheck(*xsp, xp); 226 for (p = path; p; p = q) { 227 while (*p == '/') 228 p++; 229 if (!*p) 230 break; 231 len = (q = strchr(p, '/')) ? (size_t)(q - p) : strlen(p); 232 if (len == 1 && p[0] == '.') 233 continue; 234 if (len == 2 && p[0] == '.' && p[1] == '.') { 235 while (xp > Xstring(*xsp, xp)) { 236 xp--; 237 if (*xp == '/') 238 break; 239 } 240 continue; 241 } 242 243 savepos = Xsavepos(*xsp, xp); 244 Xput(*xsp, xp, '/'); 245 XcheckN(*xsp, xp, len + 1); 246 memcpy(xp, p, len); 247 xp += len; 248 *xp = '\0'; 249 250 llen = readlink(Xstring(*xsp, xp), lbuf, sizeof(lbuf) - 1); 251 if (llen == -1) { 252 /* EINVAL means it wasn't a symlink... */ 253 if (errno != EINVAL) 254 return NULL; 255 continue; 256 } 257 lbuf[llen] = '\0'; 258 259 /* If absolute path, start from scratch.. */ 260 xp = lbuf[0] == '/' ? Xstring(*xsp, xp) : 261 Xrestpos(*xsp, xp, savepos); 262 if (!(xp = do_phys_path(xsp, xp, lbuf))) 263 return NULL; 264 } 265 return xp; 266 } 267