minix/servers/vfs/time.c

214 lines
6.3 KiB
C
Raw Normal View History

2005-04-21 16:53:53 +02:00
/* This file takes care of those system calls that deal with time.
*
* The entry points into this file are
* do_utime: perform the UTIME system call
* do_utimens: perform the UTIMENS system call
2005-04-21 16:53:53 +02:00
*/
#include "fs.h"
#include <minix/callnr.h>
#include <minix/com.h>
#include <time.h>
#include <sys/stat.h>
#include <fcntl.h>
2005-04-21 16:53:53 +02:00
#include "file.h"
#include "fproc.h"
2012-02-13 16:28:04 +01:00
#include "path.h"
2005-04-21 16:53:53 +02:00
#include "param.h"
2007-08-07 14:52:47 +02:00
#include "vnode.h"
#include <minix/vfsif.h>
#include "vmnt.h"
#define UTIMENS_STYLE 0 /* utimes(2)/utimensat(2) style, named file */
#define FUTIMENS_STYLE 1 /* futimens(2)/futimes(2) style, file desc. */
2005-04-21 16:53:53 +02:00
/*===========================================================================*
* do_utime *
*===========================================================================*/
int do_utime(void)
2005-04-21 16:53:53 +02:00
{
/* Perform the utime(name, timep) system call. */
int r;
struct timespec actim, modtim, newactim, newmodtim;
2007-08-07 14:52:47 +02:00
struct vnode *vp;
2012-02-13 16:28:04 +01:00
struct vmnt *vmp;
char fullpath[PATH_MAX];
struct lookup resolve;
vir_bytes vname;
size_t vname_length, len;
2012-02-13 16:28:04 +01:00
vname = (vir_bytes) job_m_in.utime_file;
vname_length = (size_t) job_m_in.utime_length;
actim.tv_sec = job_m_in.utime_actime;
modtim.tv_sec = job_m_in.utime_modtime;
actim.tv_nsec = modtim.tv_nsec = 0;
2012-02-13 16:28:04 +01:00
2005-04-21 16:53:53 +02:00
/* Adjust for case of 'timep' being NULL;
* utime_strlen then holds the actual size: strlen(name)+1 */
len = vname_length;
if (len == 0) len = (size_t) job_m_in.utime_strlen;
lookup_init(&resolve, fullpath, PATH_NOFLAGS, &vmp, &vp);
VFS: fix locking bugs .sync and fsync used unnecessarily restrictive locking type .fsync violated locking order by obtaining a vmnt lock after a filp lock .fsync contained a TOCTOU bug .new_node violated locking rules (didn't upgrade lock upon file creation) .do_pipe used unnecessarily restrictive locking type .always lock pipes exclusively; even a read operation might require to do a write on a vnode object (update pipe size) .when opening a file with O_TRUNC, upgrade vnode lock when truncating .utime used unnecessarily restrictive locking type .path parsing: .always acquire VMNT_WRITE or VMNT_EXCL on vmnt and downgrade to VMNT_READ if that was what was actually requested. This prevents the following deadlock scenario: thread A: lock_vmnt(vmp, TLL_READSER); lock_vnode(vp, TLL_READSER); upgrade_vmnt_lock(vmp, TLL_WRITE); thread B: lock_vmnt(vmp, TLL_READ); lock_vnode(vp, TLL_READSER); thread A will be stuck in upgrade_vmnt_lock and thread B is stuck in lock_vnode. This happens when, for example, thread A tries create a new node (open.c:new_node) and thread B tries to do eat_path to change dir (stadir.c:do_chdir). When the path is being resolved, a vnode is always locked with VNODE_OPCL (TLL_READSER) and then downgraded to VNODE_READ if read-only is actually requested. Thread A locks the vmnt with VMNT_WRITE (TLL_READSER) which still allows VMNT_READ locks. Thread B can't acquire a lock on the vnode because thread A has it; Thread A can't upgrade its vmnt lock to VMNT_WRITE (TLL_WRITE) because thread B has a VMNT_READ lock on it. By serializing vmnt locks during path parsing, thread B can only acquire a lock on vmp when thread A has completely finished its operation.
2012-11-30 13:49:53 +01:00
resolve.l_vmnt_lock = VMNT_READ;
resolve.l_vnode_lock = VNODE_READ;
2005-04-21 16:53:53 +02:00
/* Temporarily open the file */
if (fetch_name(vname, len, fullpath) != OK) return(err_code);
2012-02-13 16:28:04 +01:00
if ((vp = eat_path(&resolve, fp)) == NULL) return(err_code);
/* Only the owner of a file or the super user can change timestamps. */
r = OK;
if (vp->v_uid != fp->fp_effuid && fp->fp_effuid != SU_UID) r = EPERM;
if (vname_length == 0 && r != OK) r = forbidden(fp, vp, W_BIT);
2012-02-13 16:28:04 +01:00
if (read_only(vp) != OK) r = EROFS; /* Not even su can touch if R/O */
if (r == OK) {
/* Issue request */
if (vname_length == 0) {
newactim = newmodtim = clock_timespec();
} else {
newactim = actim;
newmodtim = modtim;
}
r = req_utime(vp->v_fs_e, vp->v_inode_nr, &newactim, &newmodtim);
2005-04-21 16:53:53 +02:00
}
2012-02-13 16:28:04 +01:00
unlock_vnode(vp);
unlock_vmnt(vmp);
2007-08-07 14:52:47 +02:00
put_vnode(vp);
return(r);
2005-04-21 16:53:53 +02:00
}
/*===========================================================================*
* do_utimens *
*===========================================================================*/
int do_utimens(void)
{
/* Perform the utimens(name, times, flag) system call, and its friends.
* Implement a very large but not complete subset of the utimensat()
* Posix:2008/XOpen-7 function.
* Are handled all the following cases:
* . utimensat(AT_FDCWD, "/some/absolute/path", , )
* . utimensat(AT_FDCWD, "some/path", , )
* . utimens("anything", ) really special case of the above two
* . lutimens("anything", ) also really special case of the above
* . utimensat(fd, "/some/absolute/path", , ) although fd is useless here
* . futimens(fd, )
* Are not handled the following cases:
* . utimensat(fd, "some/path", , ) path to a file relative to some open fd
*/
int r, kind, lookup_flags;
struct vnode *vp;
struct filp *filp = NULL; /* initialization required by clueless GCC */
struct vmnt *vmp;
struct timespec actim, modtim, now, newactim, newmodtim;
char fullpath[PATH_MAX];
struct lookup resolve;
vir_bytes vname;
size_t vname_length;
/* The case times==NULL is handled by the caller, replaced with UTIME_NOW */
actim.tv_sec = job_m_in.utime_actime;
actim.tv_nsec = job_m_in.utimens_ansec;
modtim.tv_sec = job_m_in.utime_modtime;
modtim.tv_nsec = job_m_in.utimens_mnsec;
if (job_m_in.utime_file != NULL) {
kind = UTIMENS_STYLE;
if (job_m_in.utimens_flags & ~AT_SYMLINK_NOFOLLOW)
return EINVAL; /* unknown flag */
/* Temporarily open the file */
vname = (vir_bytes) job_m_in.utime_file;
vname_length = (size_t) job_m_in.utime_length;
if (job_m_in.utimens_flags & AT_SYMLINK_NOFOLLOW)
lookup_flags = PATH_RET_SYMLINK;
else
lookup_flags = PATH_NOFLAGS;
lookup_init(&resolve, fullpath, lookup_flags, &vmp, &vp);
resolve.l_vmnt_lock = VMNT_READ;
resolve.l_vnode_lock = VNODE_READ;
/* Temporarily open the file */
if (fetch_name(vname, vname_length, fullpath) != OK) return(err_code);
if ((vp = eat_path(&resolve, fp)) == NULL) return(err_code);
}
else {
kind = FUTIMENS_STYLE;
/* Change timestamps on already-opened fd. Is it valid? */
if (job_m_in.utimens_flags != 0)
return EINVAL; /* unknown flag */
if ((filp = get_filp(job_m_in.utimens_fd, VNODE_READ)) == NULL)
return err_code;
vp = filp->filp_vno;
}
r = OK;
/* Only the owner of a file or the super user can change timestamps. */
if (vp->v_uid != fp->fp_effuid && fp->fp_effuid != SU_UID) r = EPERM;
/* Need write permission (or super user) to 'touch' the file */
if (r != OK && actim.tv_nsec == UTIME_NOW
&& modtim.tv_nsec == UTIME_NOW) r = forbidden(fp, vp, W_BIT);
if (read_only(vp) != OK) r = EROFS; /* Not even su can touch if R/O */
if (r == OK) {
/* Do we need to ask for current time? */
if (actim.tv_nsec == UTIME_NOW
|| actim.tv_nsec == UTIME_OMIT
|| modtim.tv_nsec == UTIME_NOW
|| modtim.tv_nsec == UTIME_OMIT) {
now = clock_timespec();
}
/* Build the request */
switch (actim.tv_nsec) {
case UTIME_NOW:
newactim = now;
break;
case UTIME_OMIT:
newactim.tv_nsec = UTIME_OMIT;
/* Be nice with old FS, put a sensible value in
* otherwise not used field for seconds
*/
newactim.tv_sec = now.tv_sec;
break;
default:
if ( (unsigned)actim.tv_nsec >= 1000000000)
r = EINVAL;
else
newactim = actim;
break;
}
switch (modtim.tv_nsec) {
case UTIME_NOW:
newmodtim = now;
break;
case UTIME_OMIT:
newmodtim.tv_nsec = UTIME_OMIT;
/* Be nice with old FS, put a sensible value */
newmodtim.tv_sec = now.tv_sec;
break;
default:
if ( (unsigned)modtim.tv_nsec >= 1000000000)
r = EINVAL;
else
newmodtim = modtim;
break;
}
}
if (r == OK)
/* Issue request */
r = req_utime(vp->v_fs_e, vp->v_inode_nr, &newactim, &newmodtim);
if (kind == UTIMENS_STYLE) {
/* Close the temporary */
unlock_vnode(vp);
unlock_vmnt(vmp);
put_vnode(vp);
}
else { /* Change timestamps on opened fd. */
unlock_filp(filp);
}
return r;
}