/*********************************************************************** * * * This software is part of the ast package * * Copyright (c) 1989-2012 AT&T Intellectual Property * * and is licensed under the * * Eclipse Public License, Version 1.0 * * by AT&T Intellectual Property * * * * A copy of the License is available at * * http://www.eclipse.org/org/documents/epl-v10.html * * (with md5 checksum b35adb5213ca9657e911e9befb180842) * * * * Information and Software Systems Research * * AT&T Research * * Florham Park NJ * * * * Glenn Fowler * * David Korn * * Eduardo Krell * * * ***********************************************************************/ #pragma prototyped #include "3d.h" #ifndef MAXSYMLINKS #define MAXSYMLINKS 20 #endif /* * if is a hard link to state.opaque in the same directory then the file * is opaque and the errno should be ENOENT */ static int checkopaque(register char* path, struct stat* st) { register char* basesp; int oerrno = errno; struct stat statb; char savebuf[sizeof(state.opaque)]; register int r = 0; if (st->st_nlink <= 1) return 0; if (st->st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) return 0; /* * change the basename to state.opaque */ if (basesp = strrchr(path, '/')) basesp++; else basesp = path; memcpy(savebuf, basesp, sizeof(state.opaque)); strcpy(basesp, state.opaque); if (LSTAT(path, &statb)) { /* * for backward compatability */ basesp[3] = 0; if (LSTAT(path, &statb)) goto not_opaque; } if (statb.st_ino == st->st_ino && statb.st_dev == st->st_dev) { errno = ENOENT; r = statb.st_ino; } else errno = oerrno; not_opaque: memcpy(basesp, savebuf, sizeof(state.opaque)); return r; } /* * return real path name for path */ #if DEBUG static char* _pathreal(const char*, int, struct stat*); char* pathreal(const char* apath, register int type, struct stat* st) { char* path = (char*)apath; initialize(); message((-5, "pathreal: ++ %s type=|%s%s%s%s%s%s%s%s%s%s%s", path , (type & P_ABSOLUTE) ? "ABSOLUTE|" : state.null , (type & P_DOTDOT) ? "DOTDOT|" : state.null , (type & P_LSTAT) ? "LSTAT|" : state.null , st ? "MYSTAT|" : state.null , (type & P_NOOPAQUE) ? "NOOPAQUE|" : state.null , (type & P_NOSLASH) ? "NOSLASH|" : state.null , (type & P_PATHONLY) ? "PATHONLY|" : state.null , (type & P_READLINK) ? "READLINK|" : state.null , (type & P_SAFE) ? "SAFE|" : state.null , (type & P_SLASH) ? "SLASH|" : state.null , (type & P_TOP) ? "TOP|" : state.null )); path = _pathreal(path, type, st); message((-5, "pathreal: -- %s level=%d links=%d", path, state.path.level, state.path.nlinks)); return path; } #undef initialize #define initialize() #undef pathreal #define pathreal _pathreal static #endif char* pathreal(const char* apath, register int type, struct stat* st) { char* path = (char*)apath; register char* sp; register char* cp; register char* ip; Table_t* safe; int oerrno = errno; int opaqued = 0; int len; int vir; int safesize; int safe_dir; long visits; char buf[PATH_MAX + 1]; static struct stat stbuf; static struct stat tsbuf; state.path.level = state.path.synthesize = state.path.nlinks = 0; if (!path) { errno = EFAULT; return 0; } initialize(); if (state.in_2d) { if (!st || (!state.level || *path == '/') && !LSTAT(path, st)) return path; if (state.level && streq(path, ".") && !CHDIR(state.pwd)) { state.level = 0; return path; } return 0; } #if FS if (mounted() && (sp = fsreal(state.path.monitor, MSG_stat, state.path.mount))) apath = (char*)(path = sp); #endif /* * handle null path, . and / separately */ if (safe = state.safe ? &state.vsafe : (Table_t*)0) { type |= P_ABSOLUTE; if (!(safesize = state.safe->servicesize)) safesize = strlen(state.safe->service); } else type &= ~P_SAFE; again: if (!*path) { errno = ENOENT; return 0; } cp = sp = path; state.path.synthesize = state.path.linksize = 0; if (!st) st = &stbuf; /* * check if virtual dir has been created by another process * only P_PATHONLY|P_TOP calls (usually create or modify link) and * references to "." are checked for performance */ if (state.level > 0 && state.pwd && ((type & (P_PATHONLY|P_TOP)) && *sp != '/' || *sp == '.' && sp[1] == 0)) { if (!CHDIR(state.pwd)) state.level = 0; else if (!(type & (P_PATHONLY|P_TOP))) { len = 0; state.path.level += (state.path.synthesize = state.level); sp = strcpy(state.path.name, state.pwd); goto skip; } } if (!state.pwd || sp[1] == 0 && (*sp == '.' || *sp == '/' && !safe)) { if (st != &stbuf && LSTAT(sp, st)) return 0; if (*sp == '/' || !state.pwd && (type & P_PATHONLY)) strncpy(state.path.name, sp, PATH_MAX); else if (!state.pwd) { /* * treat the current directory as if were empty */ errno = ENOENT; return 0; } else strncpy(state.path.name, state.pwd, PATH_MAX); errno = oerrno; return state.path.name; } /* * put absolute pathname into state.path */ safe_dir = 0; if (*path != '/') { strcpy(state.path.name, state.pwd); sp = state.path.name + state.pwdsize; *sp++ = '/'; if (safe && state.pwdsize >= safesize && !strncmp(state.pwd, state.safe->service, safesize) && (!state.pwd[safesize] || state.pwd[safesize] == '/')) safe_dir = safesize; } else sp = state.path.name; ip = state.path.name + elementsof(state.path.name); while (sp < ip && (*sp = *cp++)) sp++; if (type & P_DOTDOT) strcpy(sp, "/.."); sp = state.path.name; if (!(ip = pathcanon(sp + safe_dir, sizeof(state.path.name) - safe_dir, 0))) { errno = ENOENT; return 0; } if (type & (P_DOTDOT|P_NOSLASH)) { /* * remove trailing slashes */ while (*--ip == '/'); *++ip = 0; } else if ((type & P_SLASH) && *(ip - 1) != '/') *ip++ = '/'; if (*(ip - 1) == '/' && ip - sp > 1) { /* * trailing slash is equivalent to trailing slash-dot * this forces the common-sense interpretation */ #if DEBUG if (!(state.test & 010)) #endif *ip++ = '.'; *ip = 0; } len = ip - sp; /* * try to use relative path */ if (!(type & (P_LSTAT|P_READLINK))) { for (ip = state.pwd; *ip && *ip == *sp++; ip++); if (*ip != 0 || *sp && *sp != '/' || state.level < 0) sp = state.path.name; else { state.path.level += (state.path.synthesize = state.level); if (state.level && !(type & P_PATHONLY) && st == &stbuf) { sp = state.path.name; len -= state.pwdsize; } else if (type & P_ABSOLUTE) sp = state.path.name; else if (*sp == '/') sp++; } if (*sp == 0) sp = state.dot; } skip: if ((type & P_NOOPAQUE) && !LSTAT(sp, st) && checkopaque(sp, st)) { message((-1, "%s: remove opaque", sp)); UNLINK(sp); opaqued = 1; } if (safe && *sp == '/') { state.path.table = safe; cp = pathnext(sp, NiL, NiL); state.path.table = safe = 0; if (cp) { state.path.level = 0; path = strcpy(buf, sp); message((-5, "pathreal: == safe map %s", path)); type &= ~(P_DOTDOT|P_SAFE); goto again; } if (!*(sp + 1)) { strncpy(sp, state.safe->service, safesize); sp[safesize] = 0; } else if (strncmp(sp, state.safe->service, safesize) || sp[safesize] && sp[safesize] != '/') { if (*path != '/' && safe_dir) { errno = EPERM; return 0; } if (sp[1]) strcpy(buf, sp); else *buf = 0; len = sfsprintf(sp, sizeof(state.path.name), "%-*s%s", safesize, state.safe->service, buf); message((-5, "pathreal: == safe next %s", sp)); if (!pathnext(sp, NiL, NiL)) { errno = EPERM; return 0; } } else type &= ~P_SAFE; } if ((type & P_SAFE) && state.path.level) { errno = EPERM; return 0; } if (type & P_PATHONLY) { errno = oerrno; return sp; } visits = 0; vir = 1; while (LSTAT(sp, st)) { if (vir) { if (apath[0] == '.' && apath[1] == '.' && apath[2] == '.' && !apath[3]) { if (state.level > 0) { message((-1, "pathreal: %s => %s", apath, sp)); LSTAT(".", st); return sp; } errno = ENOENT; return 0; } vir = 0; } if (errno == ENOTDIR) { /* * check for version instance */ cp = ip = sp + strlen(sp); while (ip > sp && *--ip != '/'); if (ip < sp) return 0; while (ip > sp && *--ip == '/'); if (ip < sp) return 0; while (ip > sp && *--ip != '/'); if (*ip == '/') ip++; while (cp >= ip) { cp[4] = *cp; cp--; } memcpy(ip, state.opaque, 4); if (!LSTAT(sp, st)) break; errno = ENOTDIR; return 0; } if (errno != ENOENT || opaqued) return 0; #if FS /* * check user mount */ if (visits) { Mount_t* mp; const char* up; if ((mp = getmount(sp, &up)) && (mp->fs->flags & FS_NAME) && (sp = fsreal(mp, MSG_open, (char*)up)) && !LSTAT(sp, st)) break; } #endif /* * search down the viewpath */ if (type & P_SAFE) { errno = EPERM; return 0; } if (!pathnext(state.path.name, NiL, &visits)) return 0; sp = state.path.name; if (!(type & P_ABSOLUTE)) { /* * try to use relative path */ for (ip = state.pwd; *ip && *ip == *sp++; ip++); if (*ip == 0 && *sp == '/') sp++; else sp = state.path.name; } if (*sp == 0) sp = state.dot; } if (st->st_nlink > 1 && checkopaque(sp, st)) return 0; if ((type & P_TOP) && state.path.level) { int rfd; int wfd; if ((rfd = OPEN(sp, O_RDONLY, 0)) < 0) sp = 0; else { tsbuf = *st; wfd = open(apath, O_WRONLY|O_CREAT|O_TRUNC|O_cloexec, st->st_mode & S_IPERM); *st = tsbuf; if (wfd < 0) sp = 0; else { if (fs3d_copy(rfd, wfd, st)) sp = 0; CLOSE(wfd); } CLOSE(rfd); } if (!sp) { errno = EROFS; return 0; } if (st == &stbuf) st = 0; return pathreal(apath, P_PATHONLY, st); } IVIEW(st, state.path.level); if (state.path.synthesize) { if (state.path.level < state.level) { if (len) { ip = state.path.name + strlen(state.path.name) - len; len = *ip; *ip = 0; } if (!CHDIR(state.path.name)) state.level = state.path.level; message((-1, "chdir=%s level=%d", state.path.name, state.level)); *ip = len; } else if (S_ISDIR(st->st_mode)) { int mask; static int uid = -1; static int gid; umask(mask = umask(0)); st->st_mode = (st->st_mode | (S_IRWXU|S_IRWXG|S_IRWXO)) & ~(mask & (S_IRWXU|S_IRWXG|S_IRWXO)); if (uid == -1) { uid = geteuid(); gid = getegid(); } st->st_uid = uid; st->st_gid = gid; } } ip = sp; /* * symbolic links handled specially * get filename from pathname */ if (S_ISLNK(st->st_mode) && (len = checklink(sp, st, type)) > 1 && !(type & (P_LSTAT|P_READLINK)) && state.path.nlinks++ < MAXSYMLINKS) { path = strcpy(buf, state.path.name); message((-1, "pathreal: == again %s", path)); if (*path != '/') state.path.level = 0; type &= ~(P_DOTDOT|P_SAFE); goto again; } #if VCS && defined(VCS_REAL) VCS_REAL(state.path.name, st); #endif errno = oerrno; return sp; } /* * check whether sp points to a version object and find version instance * sp is canonicalized and points into state.path * when called from unlink, (type & P_PATHONLY) is set * -1 for non-existent link * length of path for a relative link that is not a version object * 0 otherwise * state.path.linkname and state.path.linksize are set for version object */ int checklink(const char* asp, struct stat* st, int type) { register char* sp = (char*)asp; register char* ip; register int len; register int n; register char* bp; char buf[PATH_MAX + 1]; if (sp < state.path.name || sp >= state.path.name + sizeof(state.path.name)) { message((-1, "AHA#%d checklink bounds sp=%p state.path.name=%p sp=%s", __LINE__, sp, state.path.name, sp)); sp = strncpy(state.path.name, sp, sizeof(state.path.name) - 1); } while (S_ISLNK(st->st_mode)) { /* * go to the last component */ if (ip = strrchr(sp, '/')) ip++; else ip = sp; strcpy(buf, ip); len = (state.path.name + sizeof(state.path.name) - 1) - ip; if ((len = READLINK(sp, ip, len)) < 0) { message((-1, "%s: cannot readlink", sp)); return 0; } state.path.linkname = ip; state.path.linksize = len; /* * check for relative link */ if (*ip != '/') { ip[len] = 0; if (*ip == *state.opaque && !memcmp(ip, state.opaque, 4) && !memcmp(ip + 4, buf, n = strlen(buf))) { /* * version object */ ip += n + 4; if (instance(state.path.name, ip, st, 0)) { state.path.linksize = strlen(state.path.linkname); if (type & P_LSTAT) { st->st_size = state.path.linksize; st->st_mode &= S_IPERM; #ifdef S_IFLNK st->st_mode |= S_IFLNK; #endif return 0; } continue; } errno = ENOENT; return -1; } else if (!(type & (P_LSTAT|P_PATHONLY|P_READLINK)) && *ip == '.' && *(ip + 1) == '.' && (*(ip + 2) == '/' || *(ip + 2) == 0)) { memcpy(buf, ip, len + 1); bp = state.path.name; while (ip > bp && *(ip - 1) == '/') ip--; for (;;) { *(sp = ip) = 0; while (ip > bp && *--ip != '/'); while (ip > bp && *(ip - 1) == '/') ip--; if (*ip == '/') ip++; if ((n = READLINK(state.path.name, ip, PATH_MAX - (ip - state.path.name))) <= 0) { *sp++ = '/'; state.path.linkname = (char*)memcpy(sp, buf, len + 1); return sp + len - state.path.name; } if (*ip == '/') ip = (char*)memcpy(bp = state.path.name, ip, n); else if (ip > bp) *(ip - 1) = '/'; ip += n; } } } /* * restore last component */ if (!(type & P_READLINK)) strcpy(ip, buf); break; } return 0; }