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.
This commit is contained in:
parent
c89aaf7a87
commit
e6c98c3c55
3 changed files with 168 additions and 51 deletions
|
@ -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);
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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;
|
||||
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)
|
||||
return(NULL);
|
||||
else if (r == OK)
|
||||
*resolve->l_vmp = 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);
|
||||
return(start_dir);
|
||||
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';
|
||||
}
|
||||
|
||||
} 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);
|
||||
}
|
||||
|
||||
/*===========================================================================*
|
||||
|
|
Loading…
Reference in a new issue