From: Thomas Veerman Date: Thu, 12 Jan 2012 11:32:31 +0000 (+0000) Subject: AVFS: Return actual last dir when path is named by a symlink X-Git-Tag: v3.2.0~121 X-Git-Url: http://zhaoyanbai.com/repos/%22http:/www.isc.org/icons/Bv9ARM.ch10.html?a=commitdiff_plain;h=e6c98c3c55fedb3e40e23c6a01afa9efd2c730f2;p=minix.git AVFS: Return actual last dir when path is named by a symlink Last_dir didn't consider paths that end in a symlink and hence didn't actually return the last_dir when provided with one. For example, /var/log is a symlink to /usr/log. Issuing `>/var/log' would trigger an assert in AVFS, because /var/ is not the actual last directory; /usr/ is. Last_dir now verifies the final component is not a symlink. If it is, it follows the symlink and restarts finding of the last the directory. --- diff --git a/servers/avfs/link.c b/servers/avfs/link.c index a4efae564..dc51c2d57 100644 --- a/servers/avfs/link.c +++ b/servers/avfs/link.c @@ -97,7 +97,7 @@ PUBLIC int do_unlink() char fullpath[PATH_MAX]; struct lookup resolve; - lookup_init(&resolve, fullpath, PATH_NOFLAGS, &vmp, &dirp); + lookup_init(&resolve, fullpath, PATH_RET_SYMLINK, &vmp, &dirp); resolve.l_vmnt_lock = VMNT_WRITE; resolve.l_vnode_lock = VNODE_READ; @@ -106,6 +106,7 @@ PUBLIC int do_unlink() return(err_code); if ((dirp = last_dir(&resolve, fp)) == NULL) return(err_code); + assert(vmp != NULL); /* Make sure that the object is a directory */ if ((dirp->v_mode & I_TYPE) != I_DIRECTORY) { @@ -149,9 +150,10 @@ PUBLIC int do_unlink() } } + assert(vmp != NULL); tll_upgrade(&vmp->m_lock); - if(call_nr == UNLINK) + if (call_nr == UNLINK) r = req_unlink(dirp->v_fs_e, dirp->v_inode_nr, fullpath); else r = req_rmdir(dirp->v_fs_e, dirp->v_inode_nr, fullpath); diff --git a/servers/avfs/open.c b/servers/avfs/open.c index dc8948ca6..a236107fd 100644 --- a/servers/avfs/open.c +++ b/servers/avfs/open.c @@ -322,7 +322,9 @@ PRIVATE struct vnode *new_node(struct lookup *resolve, int oflags, mode_t bits) findnode.l_vnode_lock = (oflags & O_TRUNC) ? VNODE_WRITE : VNODE_OPCL; vp = advance(dirp, &findnode, fp); assert(vp_vmp == NULL); /* Lookup to last dir should have yielded lock - * on vmp or final component does not exist. */ + * on vmp or final component does not exist. + * Either way, vp_vmp ought to be not set. + */ /* The combination of a symlink with absolute path followed by a danglink * symlink results in a new path that needs to be re-resolved entirely. */ diff --git a/servers/avfs/path.c b/servers/avfs/path.c index 80fe01889..84e9b389b 100644 --- a/servers/avfs/path.c +++ b/servers/avfs/path.c @@ -133,7 +133,6 @@ struct fproc *rfp; return(vp); } - /*===========================================================================* * eat_path * *===========================================================================*/ @@ -148,7 +147,6 @@ struct fproc *rfp; return advance(start_dir, resolve, rfp); } - /*===========================================================================* * last_dir * *===========================================================================*/ @@ -168,72 +166,187 @@ struct fproc *rfp; size_t len; char *cp; char dir_entry[NAME_MAX+1]; - struct vnode *start_dir, *res; - int r; + struct vnode *start_dir, *res_vp, *sym_vp, *new_res_vp, *loop_start; + struct vmnt *sym_vmp = NULL; + int r, symloop = 0, ret_on_symlink = 0; + struct lookup symlink; *resolve->l_vnode = NULL; *resolve->l_vmp = NULL; + loop_start = NULL; + sym_vp = NULL; - /* Is the path absolute or relative? Initialize 'start_dir' accordingly. */ - start_dir = (resolve->l_path[0] == '/' ? rfp->fp_rd : rfp->fp_wd); + ret_on_symlink = !!(resolve->l_flags & PATH_RET_SYMLINK); - len = strlen(resolve->l_path); + do { + /* Is the path absolute or relative? Initialize 'start_dir' + * accordingly. Use loop_start in case we're looping. + */ + if (loop_start != NULL) + start_dir = loop_start; + else + start_dir = (resolve->l_path[0] == '/' ? rfp->fp_rd:rfp->fp_wd); - /* If path is empty, return ENOENT. */ - if (len == 0) { - err_code = ENOENT; - return(NULL); - } + len = strlen(resolve->l_path); + + /* If path is empty, return ENOENT. */ + if (len == 0) { + err_code = ENOENT; + res_vp = NULL; + break; + } #if !DO_POSIX_PATHNAME_RES - /* Remove trailing slashes */ - while (len > 1 && resolve->l_path[len-1] == '/') { - len--; - resolve->l_path[len]= '\0'; - } + /* Remove trailing slashes */ + while (len > 1 && resolve->l_path[len-1] == '/') { + len--; + resolve->l_path[len]= '\0'; + } #endif - cp = strrchr(resolve->l_path, '/'); - if (cp == NULL) { - /* Just one entry in the current working directory */ - struct vmnt *vmp; - - vmp = find_vmnt(start_dir->v_fs_e); - r = lock_vmnt(vmp, resolve->l_vmnt_lock); - if (r == EDEADLK) - return(NULL); - else if (r == OK) - *resolve->l_vmp = vmp; - - lock_vnode(start_dir, resolve->l_vnode_lock); - *resolve->l_vnode = start_dir; - dup_vnode(start_dir); - return(start_dir); - - } else if (cp[1] == '\0') { - /* Path ends in a slash. The directory entry is '.' */ - strcpy(dir_entry, "."); - } else { - /* A path name for the directory and a directory entry */ - strncpy(dir_entry, cp+1, NAME_MAX); - cp[1] = '\0'; - dir_entry[NAME_MAX] = '\0'; + cp = strrchr(resolve->l_path, '/'); + if (cp == NULL) { + /* Just an entry in the current working directory */ + struct vmnt *vmp; + + vmp = find_vmnt(start_dir->v_fs_e); + r = lock_vmnt(vmp, resolve->l_vmnt_lock); + if (r == EDEADLK) { + res_vp = NULL; + break; + } else if (r == OK) + *resolve->l_vmp = vmp; + + lock_vnode(start_dir, resolve->l_vnode_lock); + *resolve->l_vnode = start_dir; + dup_vnode(start_dir); + if (loop_start != NULL) { + unlock_vnode(loop_start); + put_vnode(loop_start); + } + return(start_dir); + } else if (cp[1] == '\0') { + /* Path ends in a slash. The directory entry is '.' */ + strcpy(dir_entry, "."); + } else { + /* A path name for the directory and a directory entry */ + strncpy(dir_entry, cp+1, NAME_MAX); + cp[1] = '\0'; + dir_entry[NAME_MAX] = '\0'; + } + + /* Remove trailing slashes */ + while (cp > resolve->l_path && cp[0] == '/') { + cp[0]= '\0'; + cp--; + } + + /* Resolve up to and including the last directory of the path. Turn off + * PATH_RET_SYMLINK, because we do want to follow the symlink in this + * case. That is, the flag is meant for the actual filename of the path, + * not the last directory. + */ + resolve->l_flags &= ~PATH_RET_SYMLINK; + if ((res_vp = advance(start_dir, resolve, rfp)) == NULL) { + break; + } + + /* If the directory entry is not a symlink we're done now. If it is a + * symlink, then we're not at the last directory, yet. */ + + /* Copy the directory entry back to user_fullpath */ + strncpy(resolve->l_path, dir_entry, NAME_MAX + 1); + + /* Look up the directory entry, but do not follow the symlink when it + * is one. + */ + lookup_init(&symlink, resolve->l_path, + resolve->l_flags|PATH_RET_SYMLINK, &sym_vmp, &sym_vp); + symlink.l_vnode_lock = VNODE_READ; + symlink.l_vmnt_lock = VMNT_READ; + sym_vp = advance(res_vp, &symlink, rfp); + + /* Advance caused us to either switch to a different vmnt or we're + * still at the same vmnt. The former might've yielded a new vmnt lock, + * the latter should not have. Verify. */ + if (sym_vmp != NULL) { + /* We got a vmnt lock, so the endpoints of the vnodes must + * differ. + */ + assert(sym_vp->v_fs_e != res_vp->v_fs_e); + } + + if (sym_vp != NULL && S_ISLNK(sym_vp->v_mode)) { + /* Last component is a symlink, but if we've been asked to not + * resolve it, return now. + */ + if (ret_on_symlink) { + break; + } + + r = req_rdlink(sym_vp->v_fs_e, sym_vp->v_inode_nr, NONE, + resolve->l_path, PATH_MAX - 1, 1); + + if (r < 0) { + /* Failed to read link */ + err_code = r; + unlock_vnode(res_vp); + unlock_vmnt(*resolve->l_vmp); + put_vnode(res_vp); + *resolve->l_vmp = NULL; + *resolve->l_vnode = NULL; + res_vp = NULL; + break; + } + resolve->l_path[r] = '\0'; + + if (strrchr(resolve->l_path, '/') != NULL) { + unlock_vnode(sym_vp); + unlock_vmnt(*resolve->l_vmp); + if (sym_vmp != NULL) + unlock_vmnt(sym_vmp); + *resolve->l_vmp = NULL; + put_vnode(sym_vp); + sym_vp = NULL; + + symloop++; + + /* Relative symlinks are relative to res_vp, not cwd */ + if (resolve->l_path[0] != '/') { + loop_start = res_vp; + } else { + /* Absolute symlink, forget about res_vp */ + unlock_vnode(res_vp); + put_vnode(res_vp); + } + + continue; + } + } + break; + } while (symloop < SYMLOOP_MAX); + + if (symloop >= SYMLOOP_MAX) { + err_code = ELOOP; + res_vp = NULL; } - /* Remove trailing slashes */ - while(cp > resolve->l_path && cp[0] == '/') { - cp[0]= '\0'; - cp--; + if (sym_vp != NULL) { + unlock_vnode(sym_vp); + if (sym_vmp != NULL) { + unlock_vmnt(sym_vmp); + } + put_vnode(sym_vp); } - resolve->l_flags = PATH_NOFLAGS; - res = advance(start_dir, resolve, rfp); - if (res == NULL) return(NULL); + if (loop_start != NULL) { + unlock_vnode(loop_start); + put_vnode(loop_start); + } /* Copy the directory entry back to user_fullpath */ strncpy(resolve->l_path, dir_entry, NAME_MAX + 1); - - return(res); + return(res_vp); } /*===========================================================================*