POSIX compliance.
VFS changes:
* truncate() on a file system mounted read-only no longer panics MFS.
* ftruncate() and fcntl(F_FREESP) now check for write permission on
the file descriptor instead of the file, write().
* utime(), chown() and fchown() now check for file system read-only
status.
MFS changes:
* link() and rename() no longer return the internal EENTERMOUNT and
ELEAVEMOUNT errors to the application as part of a check on the
source path.
* rename() now treats EENTERMOUNT from the destination path check as
an error, preventing file system corruption from renaming a normal
directory to an existing mountpoint directory.
* mountpoints (mounted-on dirs) are hidden better during lookups:
- if a lookup starts from a mountpoint, the first component has to
be ".." (anything else being a VFS-FS protocol violation).
- in that case, the permissions of the mountpoint are not checked.
- in all other cases, visiting a mountpoint always results in
EENTERMOUNT.
* a lookup on ".." from a mount root or chroot(2) root no longer
succeeds if the caller does not have search permission on that
directory.
* POSIX: getdents() now updates directory access times.
* POSIX: readlink() now returns partial results instead of ERANGE.
Miscellaneous changes:
* semaphore file handling bug (leading to hangs) fixed in test 32.
The VFS changes should now put the burden of checking for read-only
status of file systems entirely on VFS, and limit the access
permission checks that file systems have to perform, to checking
search permission on directories during lookups. From this point on,
any deviation from that spceification should be considered a bug.
Note that for legacy reasons, the root partition is assumed to be
mounted read-write.
if ( (new_ip = advance_o(&ip, string)) == NIL_INODE) {
r = err_code;
if (r == ENOENT) r = OK;
+ else if (r == EENTERMOUNT || r == ELEAVEMOUNT) r = EEXIST;
} else {
put_inode(new_ip);
r = EEXIST;
if ( (new_ip = advance_nocheck(&ip, string)) == NIL_INODE) {
r = err_code;
if (r == ENOENT) r = OK;
+ else if (r == EENTERMOUNT || r == ELEAVEMOUNT) r = EEXIST;
} else {
put_inode(new_ip);
r = EEXIST;
if (!S_ISLNK(rip->i_mode))
r = EACCES;
- else if (copylen < rip->i_size)
- r = ERANGE;
else if ((b = read_map(rip, (off_t) 0)) == NO_BLOCK)
r = EIO;
else {
/* Passed all checks */
- copylen = rip->i_size;
+ if (copylen > rip->i_size)
+ copylen = rip->i_size;
bp = get_block(rip->i_dev, b, NORMAL);
r = sys_safecopyto(FS_PROC_NR, fs_m_in.REQ_GRANT, 0,
(vir_bytes) bp->b_data, (vir_bytes) copylen, D);
if ( (old_dirp = get_inode(fs_dev, fs_m_in.REQ_OLD_DIR)) == NIL_INODE)
return(err_code);
- if ( (old_ip = advance_o(&old_dirp, old_name)) == NIL_INODE) r = err_code;
+ if ( (old_ip = advance_o(&old_dirp, old_name)) == NIL_INODE) {
+ r = err_code;
+ if (r == EENTERMOUNT) r = EBUSY; /* should this fail at all? */
+ else if (r == ELEAVEMOUNT) r = EINVAL; /* rename on dot-dot */
+ }
/* Get new dir inode */
if ( (new_dirp = get_inode(fs_dev, fs_m_in.REQ_NEW_DIR)) == NIL_INODE)
r = err_code;
new_ip = advance_o(&new_dirp, new_name); /* not required to exist */
+ /* However, if the check failed because the file does exist, don't continue.
+ * Note that ELEAVEMOUNT is covered by the dot-dot check later.
+ */
+ if (new_ip == NIL_INODE && err_code == EENTERMOUNT)
+ r = EBUSY;
+
if (old_ip != NIL_INODE)
odir = ((old_ip->i_mode & I_TYPE) == I_DIRECTORY); /* TRUE iff dir */
if ( (old_dirp = get_inode(fs_dev, fs_m_in.REQ_REN_OLD_DIR)) == NIL_INODE)
return(err_code);
- if ( (old_ip = advance_nocheck(&old_dirp, old_name)) == NIL_INODE)
+ if ( (old_ip = advance_nocheck(&old_dirp, old_name)) == NIL_INODE) {
r = err_code;
+ if (r == EENTERMOUNT) r = EBUSY; /* should this fail at all? */
+ else if (r == ELEAVEMOUNT) r = EINVAL; /* rename on dot-dot */
+ }
/* Get new dir inode */
if ( (new_dirp = get_inode(fs_dev, fs_m_in.REQ_REN_NEW_DIR)) == NIL_INODE)
r = err_code;
new_ip = advance_nocheck(&new_dirp, new_name); /* not required to exist */
+ /* However, if the check failed because the file does exist, don't continue.
+ * Note that ELEAVEMOUNT is covered by the dot-dot check later.
+ */
+ if (new_ip == NIL_INODE && err_code == EENTERMOUNT)
+ r = EBUSY;
+
if (old_ip != NIL_INODE)
odir = ((old_ip->i_mode & I_TYPE) == I_DIRECTORY); /* TRUE iff dir */
* just look up the first (or only) component in path after skipping any
* leading slashes.
*/
- int r;
+ int r, leaving_mount;
struct inode *rip, *dir_ip;
char *cp, *ncp;
char string[NAME_MAX+1];
return ENOENT;
}
+ /* If the given start inode is a mountpoint, we must be here because the file
+ * system mounted on top returned an ELEAVEMOUNT error. In this case, we must
+ * only accept ".." as the first path component.
+ */
+ leaving_mount = rip->i_mountpoint;
+
/* Scan the path component by component. */
while (TRUE) {
if (cp[0] == '\0')
/* Special code for '..'. A process is not allowed to leave a chrooted
* environment. A lookup of '..' at the root of a mounted filesystem
- * has to return ELEAVEMOUNT.
+ * has to return ELEAVEMOUNT. In both cases, the caller needs search
+ * permission for the current inode, as it is used as directory.
*/
if (strcmp(string, "..") == 0)
{
if (rip->i_num == root_ino)
{
+ /* 'rip' is now accessed as directory */
+ if ((r = forbidden(rip, X_BIT)) != OK) {
+ put_inode(rip);
+ return r;
+ }
+
cp= ncp;
continue; /* Just ignore the '..' at a process'
* root.
}
if (rip->i_num == ROOT_INODE && !rip->i_sp->s_is_root) {
/* Climbing up mountpoint */
+
+ /* 'rip' is now accessed as directory */
+ if ((r = forbidden(rip, X_BIT)) != OK) {
+ put_inode(rip);
+ return r;
+ }
+
put_inode(rip);
- *res_inop= NULL;
*offsetp += cp-user_path;
return ELEAVEMOUNT;
}
}
else
{
- /* Only check for a mount point if we are not looking for '..'.
- */
- if (rip->i_mountpoint)
- {
- *res_inop= rip;
- *offsetp += cp-user_path;
- return EENTERMOUNT;
+ /* Check for misbehaving child file systems. */
+ if (leaving_mount) {
+ printf("mfs:parse_path_s: first component after "
+ "leaving mount is '%s'\n", string);
+
+ /* DO NOT pass back EENTERMOUNT. We supposedly got here
+ * because the child file system treated the last path
+ * component as "..". It is likely to do that again.
+ */
+ put_inode(rip);
+ return EINVAL;
}
}
- /* There is more path. Keep parsing. */
+ /* Only check for a mount point if we are not coming from one. */
+ if (!leaving_mount && rip->i_mountpoint)
+ {
+ *res_inop= rip;
+ *offsetp += cp-user_path;
+ return EENTERMOUNT;
+ }
+
+ /* There is more path. Keep parsing.
+ * If we're leaving a mountpoint, skip directory permission checks.
+ */
dir_ip = rip;
- r = advance_s1(dir_ip, string, &rip);
+ r = advance_s1(dir_ip, leaving_mount ? dot2 : string, &rip);
if (r != OK)
{
return r;
}
+ leaving_mount = 0;
+
/* The call to advance() succeeded. Fetch next component. */
if (S_ISLNK(rip->i_mode)) {
{
put_inode(dir_ip);
put_inode(rip);
- *res_inop= NULL;
*offsetp= 0;
return ESYMLINK;
}
fs_m_out.RES_GDE_POS_CHANGE= new_pos-pos;
else
fs_m_out.RES_GDE_POS_CHANGE= 0;
+
+ rip->i_update |= ATIME;
+ rip->i_dirt = DIRTY;
r= OK;
}
/* Request lookup */
if ((r = lookup_vp(0 /*flags*/, 0 /*!use_realuid*/, &vp)) != OK) return r;
- r= truncate_vn(vp, m_in.m2_l1);
+ if ((r = forbidden(vp, W_BIT, 0 /*!use_realuid*/)) == OK)
+ r = truncate_vn(vp, m_in.m2_l1);
put_vnode(vp);
if ( (rfilp = get_filp(m_in.m2_i1)) == NIL_FILP)
return err_code;
- if ( (r = forbidden(rfilp->filp_vno, W_BIT, 0 /*!use_realuid*/)) != OK)
- return r;
+ if (!(rfilp->filp_mode & W_BIT))
+ return EBADF;
return truncate_vn(rfilp->filp_vno, m_in.m2_l1);
}
return EINVAL;
}
- if ( (r = forbidden(f->filp_vno, W_BIT, 0 /*!use_realuid*/)) != OK)
- return r;
+ if (!(f->filp_mode & W_BIT))
+ return EBADF;
/* Copy flock data from userspace. */
if((r = sys_datacopy(who_e, (vir_bytes) m_in.name1,
if (gid != m_in.group)
r = EPERM; /* only change to the current gid */
}
+
+ if (r == OK)
+ r = read_only(vp);
+
if (r != OK) {
put_vnode(vp);
return r;
r = forbidden(vp, W_BIT, 0 /*!use_realuid*/);
}
+ if (r == OK)
+ r = read_only(vp);
+
if (r != OK)
{
put_vnode(vp);
alarm(20);
if (chdir("newdir") != 0) e(15);
Creat("/tmp/sema.11a");
- while (stat("/tmp/sema.11a", &st1) == -1) sleep(1);
+ while (stat("/tmp/sema.11a", &st1) == 0) sleep(1);
exit(0);
default:
wait(&stat_loc);
/* Child B. */
if (chdir("olddir") != 0) e(17);
Creat("/tmp/sema.11b");
- while (stat("/tmp/sema.11b", &st1) == -1) sleep(1);
+ while (stat("/tmp/sema.11b", &st1) == 0) sleep(1);
exit(0);
default:
/* Wait for child A. It will keep ``newdir'' bussy. */