/* * Check permission. The two basic access models of FUSE are: * * 1) Local access checking ('default_permissions' mount option) based * on file mode. This is the plain old disk filesystem permission * modell. * * 2) "Remote" access checking, where server is responsible for * checking permission in each inode operation. An exception to this * is if ->permission() was invoked from sys_access() in which case an * access request is sent. Execute permission is still checked * locally based on file mode. */ static int fuse_permission(struct inode *inode, int mask, struct nameidata *nd) { struct fuse_conn *fc = get_fuse_conn(inode); if (!fuse_allow_task(fc, current)) return -EACCES; else if (fc->flags & FUSE_DEFAULT_PERMISSIONS) { #ifdef KERNEL_2_6_10_PLUS int err = generic_permission(inode, mask, NULL); #else int err = vfs_permission(inode, mask); #endif /* If permission is denied, try to refresh file attributes. This is also needed, because the root node will at first have no permissions */ if (err == -EACCES) { err = fuse_do_getattr(inode); if (!err) #ifdef KERNEL_2_6_10_PLUS err = generic_permission(inode, mask, NULL); #else err = vfs_permission(inode, mask); #endif } /* Note: the opposite of the above test does not exist. So if permissions are revoked this won't be noticed immediately, only after the attribute timeout has expired */ return err; } else { int mode = inode->i_mode; #ifndef KERNEL_2_6_11_PLUS if ((mask & MAY_WRITE) && IS_RDONLY(inode) && (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) return -EROFS; #endif if ((mask & MAY_EXEC) && !S_ISDIR(mode) && !(mode & S_IXUGO)) return -EACCES; #ifndef LOOKUP_CHDIR #define LOOKUP_CHDIR 0 #endif if (nd && (nd->flags & (LOOKUP_ACCESS | LOOKUP_CHDIR))) return fuse_access(inode, mask); return 0; } }
/* Basically copied from the kernel vfs permission(), but we've changed * the following: (1) the IS_RDONLY check is skipped, and (2) if you set * the mount option `nfsperms=insceure', we assume that -EACCES means that * the export is read-only and we should check standard Unix permissions. * This means that NFS ACL checks (or other advanced permission features) * are bypassed. */ static int inode_permission(struct inode *inode, int mask, struct nameidata *nd, int bindex) { int retval, submask; if (mask & MAY_WRITE) { /* The first branch is allowed to be really readonly. */ if (bindex == 0) { umode_t mode = inode->i_mode; if (IS_RDONLY(inode) && (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) return -EROFS; } /* * Nobody gets write access to an immutable file. */ if (IS_IMMUTABLE(inode)) return -EACCES; } /* Ordinary permission routines do not understand MAY_APPEND. */ submask = mask & ~MAY_APPEND; if (inode->i_op && inode->i_op->permission) { retval = inode->i_op->permission(inode, submask, nd); if ((retval == -EACCES) && (submask & MAY_WRITE) && (!strcmp("nfs", (inode)->i_sb->s_type->name)) && (nd) && (nd->mnt) && (nd->mnt->mnt_sb) && (branchperms(nd->mnt->mnt_sb, bindex) & MAY_NFSRO)) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,10) retval = vfs_permission(inode, submask); #else retval = generic_permission(inode, submask, NULL); #endif } } else { #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,10) retval = vfs_permission(inode, submask); #else retval = generic_permission(inode, submask, NULL); #endif } if (retval && retval != -EROFS) /* ignore EROFS */ return retval; retval = security_inode_permission(inode, mask, nd); return ((retval == -EROFS) ? 0 : retval); /* ignore EROFS */ }
int nfs_permission(struct inode *inode, int mask) { int error = vfs_permission(inode, mask); if (!NFS_PROTO(inode)->access) goto out; if (error == -EROFS) goto out; /* * Trust UNIX mode bits except: * * 1) When override capabilities may have been invoked * 2) When root squashing may be involved * 3) When ACLs may overturn a negative answer */ if (!capable(CAP_DAC_OVERRIDE) && !capable(CAP_DAC_READ_SEARCH) && (current->fsuid != 0) && (current->fsgid != 0) && error != -EACCES) goto out; error = NFS_PROTO(inode)->access(inode, mask, 0); if (error == -EACCES && NFS_CLIENT(inode)->cl_droppriv && current->uid != 0 && current->gid != 0 && (current->fsuid != current->uid || current->fsgid != current->gid)) error = NFS_PROTO(inode)->access(inode, mask, 1); out: return error; }
asmlinkage long sys_chroot(const char __user * filename) { struct nameidata nd; int error; error = __user_walk(filename, LOOKUP_FOLLOW | LOOKUP_DIRECTORY | LOOKUP_NOALT, &nd); if (error) goto out; error = vfs_permission(&nd, MAY_EXEC); if (error) goto dput_and_out; error = -EPERM; if (!capable(CAP_SYS_CHROOT)) goto dput_and_out; set_fs_root(current->fs, &nd.path); set_fs_altroot(); error = 0; dput_and_out: path_put(&nd.path); out: return error; }
asmlinkage long sys_chroot(const char __user * filename) { struct nameidata nd; struct fs_struct *fs; int error; error = __user_walk(filename, LOOKUP_FOLLOW | LOOKUP_DIRECTORY | LOOKUP_NOALT, &nd); if (error) goto out; error = vfs_permission(d_get_inode_ro(nd.dentry), &nd, MAY_EXEC); if (error) goto dput_and_out; error = -EPERM; if (!capable(CAP_SYS_CHROOT)) goto dput_and_out; fs = tx_cache_get_fs(current); set_fs_root(fs, nd.mnt, parent(nd.dentry)); set_fs_altroot(); error = 0; dput_and_out: path_release(&nd); out: return error; }
asmlinkage long sys_fchdir(unsigned int fd) { struct nameidata nd; struct file *file; int error; error = -EBADF; file = fget(fd); if (!file) goto out; nd.dentry = file->f_path.dentry; nd.mnt = file->f_path.mnt; nd.flags = 0; error = -ENOTDIR; if (!S_ISDIR(nd.dentry->d_inode->i_mode)) goto out_putf; error = vfs_permission(&nd, MAY_EXEC); if (!error) set_fs_pwd(current->fs, nd.mnt, nd.dentry); out_putf: fput(file); out: return error; }
static long do_sys_truncate(const char __user * path, loff_t length) { struct nameidata nd; struct _inode * inode; int error; error = -EINVAL; if (length < 0) /* sorry, but loff_t says... */ goto out; error = user_path_walk(path, &nd); if (error) goto out; inode = d_get_inode(nd.dentry); /* For directories it's -EISDIR, for other non-regulars - -EINVAL */ error = -EISDIR; if (S_ISDIR(inode->i_mode)) goto dput_and_out; error = -EINVAL; if (!S_ISREG(inode->i_mode)) goto dput_and_out; error = vfs_permission(inode, &nd, MAY_WRITE); if (error) goto dput_and_out; error = -EROFS; if (IS_RDONLY(inode)) goto dput_and_out; error = -EPERM; if (IS_IMMUTABLE(inode) || IS_APPEND(inode)) goto dput_and_out; /* * Make sure that there are no leases. */ error = break_lease(parent(inode), FMODE_WRITE); if (error) goto dput_and_out; error = get_write_access(parent(inode)); if (error) goto dput_and_out; error = locks_verify_truncate(parent(inode), NULL, length); if (!error) { DQUOT_INIT(inode); error = do_truncate(nd.dentry, length, 0, NULL); } put_write_access(parent(inode)); dput_and_out: path_release(&nd); out: return error; }
/* * Note that a shared library must be both readable and executable due to * security reasons. * * Also note that we take the address to load from from the file itself. */ asmlinkage long sys_uselib(const char __user * library) { struct file * file; struct nameidata nd; const struct _inode *inode; int error; error = __user_path_lookup_open(library, LOOKUP_FOLLOW, &nd, FMODE_READ|FMODE_EXEC); if (error) goto out; error = -EACCES; if (nd.mnt->mnt_flags & MNT_NOEXEC) goto exit; error = -EINVAL; inode = d_get_inode_ro(nd.dentry); if (!S_ISREG(inode->i_mode)) goto exit; error = vfs_permission(inode, &nd, MAY_READ | MAY_EXEC); if (error) goto exit; file = nameidata_to_filp(&nd, O_RDONLY); error = PTR_ERR(file); if (IS_ERR(file)) goto out; error = -ENOEXEC; if(file->f_op) { struct linux_binfmt * fmt; read_lock(&binfmt_lock); for (fmt = formats ; fmt ; fmt = fmt->next) { if (!fmt->load_shlib) continue; if (!try_module_get(fmt->module)) continue; read_unlock(&binfmt_lock); error = fmt->load_shlib(file); read_lock(&binfmt_lock); put_binfmt(fmt); if (error != -ENOEXEC) break; } read_unlock(&binfmt_lock); } fput(file); out: return error; exit: release_open_intent(&nd); path_release(&nd); goto out; }
/* * access() needs to use the real uid/gid, not the effective uid/gid. * We do this by temporarily clearing all FS-related capabilities and * switching the fsuid/fsgid around to the real ones. */ asmlinkage long sys_faccessat(int dfd, const char __user *filename, int mode) { struct nameidata nd; int old_fsuid, old_fsgid; kernel_cap_t old_cap; int res; const struct _inode *inode; if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */ return -EINVAL; old_fsuid = current->fsuid; old_fsgid = current->fsgid; old_cap = current->cap_effective; current->fsuid = current->uid; current->fsgid = current->gid; /* * Clear the capabilities if we switch to a non-root user * * FIXME: There is a race here against sys_capset. The * capabilities can change yet we will restore the old * value below. We should hold task_capabilities_lock, * but we cannot because user_path_walk can sleep. */ if (current->uid) cap_clear(current->cap_effective); else current->cap_effective = current->cap_permitted; res = __user_walk_fd(dfd, filename, LOOKUP_FOLLOW|LOOKUP_ACCESS, &nd); if (res) goto out; inode = d_get_inode_ro(nd.dentry); res = vfs_permission(inode, &nd, mode); /* SuS v2 requires we report a read only fs too */ if(res || !(mode & S_IWOTH) || special_file(inode->i_mode)) goto out_path_release; if(IS_RDONLY(inode)) res = -EROFS; out_path_release: path_release(&nd); out: current->fsuid = old_fsuid; current->fsgid = old_fsgid; current->cap_effective = old_cap; return res; }
/* If times==NULL, set access and modification to current time, * must be owner or have write permission. * Else, update from *times, must be owner or super user. */ asmlinkage long sys_utime(char __user * filename, struct utimbuf __user * times) { int error; struct nameidata nd; struct inode * inode; struct iattr newattrs; error = user_path_walk(filename, &nd); if (error) goto out; inode = nd.dentry->d_inode; error = -EROFS; if (IS_RDONLY(inode)) goto dput_and_out; /* Don't worry, the checks are done in inode_change_ok() */ newattrs.ia_valid = ATTR_CTIME | ATTR_MTIME | ATTR_ATIME; if (times) { error = -EPERM; if (IS_APPEND(inode) || IS_IMMUTABLE(inode)) goto dput_and_out; error = get_user(newattrs.ia_atime.tv_sec, ×->actime); newattrs.ia_atime.tv_nsec = 0; if (!error) error = get_user(newattrs.ia_mtime.tv_sec, ×->modtime); newattrs.ia_mtime.tv_nsec = 0; if (error) goto dput_and_out; newattrs.ia_valid |= ATTR_ATIME_SET | ATTR_MTIME_SET; } else { error = -EACCES; if (IS_IMMUTABLE(inode)) goto dput_and_out; if (current->fsuid != inode->i_uid && (error = vfs_permission(&nd, MAY_WRITE)) != 0) goto dput_and_out; } mutex_lock(&inode->i_mutex); error = notify_change(nd.dentry, &newattrs); mutex_unlock(&inode->i_mutex); dput_and_out: path_release(&nd); out: return error; }
/* If times==NULL, set access and modification to current time, * must be owner or have write permission. * Else, update from *times, must be owner or super user. */ long do_utimes(int dfd, char __user *filename, struct timeval *times) { int error; struct nameidata nd; struct inode * inode; struct iattr newattrs; error = __user_walk_fd(dfd, filename, LOOKUP_FOLLOW, &nd); if (error) goto out; inode = nd.dentry->d_inode; error = -EROFS; if (IS_RDONLY(inode)) goto dput_and_out; /* Don't worry, the checks are done in inode_change_ok() */ newattrs.ia_valid = ATTR_CTIME | ATTR_MTIME | ATTR_ATIME; if (times) { error = -EPERM; if (IS_APPEND(inode) || IS_IMMUTABLE(inode)) goto dput_and_out; newattrs.ia_atime.tv_sec = times[0].tv_sec; newattrs.ia_atime.tv_nsec = times[0].tv_usec * 1000; newattrs.ia_mtime.tv_sec = times[1].tv_sec; newattrs.ia_mtime.tv_nsec = times[1].tv_usec * 1000; newattrs.ia_valid |= ATTR_ATIME_SET | ATTR_MTIME_SET; } else { error = -EACCES; if (IS_IMMUTABLE(inode)) goto dput_and_out; if (current->fsuid != inode->i_uid && (error = vfs_permission(&nd, MAY_WRITE)) != 0) goto dput_and_out; } mutex_lock(&inode->i_mutex); error = notify_change(nd.dentry, &newattrs); mutex_unlock(&inode->i_mutex); dput_and_out: path_release(&nd); out: return error; }
extern int vnode_shadow_iop_permission( INODE_T *inode, int mask #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) , struct nameidata *nd #endif #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,32) , unsigned int flags #endif ) { INODE_T *real_inode; DENT_T *dp; int err; /* we don't have multiple dentries per shadow inode, so this is OK */ dp = vnlayer_inode2dentry_internal(inode, NULL, NULL, NULL); ASSERT(dp != NULL); real_inode = REALDENTRY(dp)->d_inode; if (real_inode == NULL) { /* just in case; not expected! */ VNODE_DPUT(dp); return -EACCES; } #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) err = permission(real_inode, mask, nd); #else err = inode_permission(real_inode, mask); #endif if (err == 0) { /* don't call permission() on inode, it will call back to us! */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,10) err = vfs_permission(inode, mask); #elif LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38) err = generic_permission(inode, mask, NULL); #else err = generic_permission(inode, mask, flags, NULL); #endif } VNODE_DPUT(dp); return(err); }
asmlinkage long sys_chdir(const char __user * filename) { struct nameidata nd; int error; error = __user_walk(filename, LOOKUP_FOLLOW|LOOKUP_DIRECTORY, &nd); if (error) goto out; error = vfs_permission(&nd, MAY_EXEC); if (error) goto dput_and_out; set_fs_pwd(current->fs, nd.mnt, nd.dentry); dput_and_out: path_release(&nd); out: return error; }
asmlinkage long sys_chdir(const char __user * filename) { struct nameidata nd; int error; struct fs_struct *fs; error = __user_walk(filename, LOOKUP_FOLLOW|LOOKUP_DIRECTORY|LOOKUP_CHDIR, &nd); if (error) goto out; error = vfs_permission(d_get_inode_ro(nd.dentry), &nd, MAY_EXEC); if (error) goto dput_and_out; fs = tx_cache_get_fs(current); set_fs_pwd(fs, nd.mnt, parent(nd.dentry)); dput_and_out: path_release(&nd); out: return error; }
int nfs_permission(struct inode *inode, int mask) { struct nfs_server *server = NFS_SERVER(inode); struct nfs_access_cache *cache = &NFS_I(inode)->cache_access; struct rpc_cred *cred; int mode = inode->i_mode; int error; if (mask & MAY_WRITE) { /* * * Nobody gets write access to a read-only fs. * */ if (IS_RDONLY(inode) && (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) return -EROFS; /* * * Nobody gets write access to an immutable file. * */ if (IS_IMMUTABLE(inode)) return -EACCES; } if ((server->flags & NFS_MOUNT_NOACL) || !NFS_PROTO(inode)->access) goto out_notsup; cred = rpcauth_lookupcred(NFS_CLIENT(inode)->cl_auth, 0); if (cache->cred == cred && time_before(jiffies, cache->jiffies + NFS_ATTRTIMEO(inode))) { if (!cache->err) { /* Is the mask a subset of an accepted mask? */ if ((cache->mask & mask) == mask) goto out_cached; } else { /* ...or is it a superset of a rejected mask? */ if ((cache->mask & mask) == cache->mask) goto out_cached; } } error = NFS_PROTO(inode)->access(inode, cred, mask); if (!error || error == -EACCES) { cache->jiffies = jiffies; if (cache->cred) put_rpccred(cache->cred); cache->cred = cred; cache->mask = mask; cache->err = error; return error; } put_rpccred(cred); out_notsup: nfs_revalidate_inode(NFS_SERVER(inode), inode); return vfs_permission(inode, mask); out_cached: put_rpccred(cred); return cache->err; }
static long do_sys_truncate(const char __user * path, loff_t length) { struct nameidata nd; struct inode * inode; int error; error = -EINVAL; if (length < 0) /* sorry, but loff_t says... */ goto out; error = user_path_walk(path, &nd); if (error) goto out; inode = nd.path.dentry->d_inode; /* For directories it's -EISDIR, for other non-regulars - -EINVAL */ error = -EISDIR; if (S_ISDIR(inode->i_mode)) goto dput_and_out; error = -EINVAL; if (!S_ISREG(inode->i_mode)) goto dput_and_out; error = mnt_want_write(nd.path.mnt); if (error) goto dput_and_out; error = vfs_permission(&nd, MAY_WRITE); if (error) goto mnt_drop_write_and_out; error = -EPERM; if (IS_IMMUTABLE(inode) || IS_APPEND(inode)) goto mnt_drop_write_and_out; error = get_write_access(inode); if (error) goto mnt_drop_write_and_out; /* * Make sure that there are no leases. get_write_access() protects * against the truncate racing with a lease-granting setlease(). */ error = break_lease(inode, FMODE_WRITE); if (error) goto put_write_and_out; error = locks_verify_truncate(inode, NULL, length); if (!error) { DQUOT_INIT(inode); error = do_truncate(nd.path.dentry, length, 0, NULL); } put_write_and_out: put_write_access(inode); mnt_drop_write_and_out: mnt_drop_write(nd.path.mnt); dput_and_out: path_put(&nd.path); out: return error; }
/* * access() needs to use the real uid/gid, not the effective uid/gid. * We do this by temporarily clearing all FS-related capabilities and * switching the fsuid/fsgid around to the real ones. */ asmlinkage long sys_faccessat(int dfd, const char __user *filename, int mode) { struct nameidata nd; int old_fsuid, old_fsgid; kernel_cap_t uninitialized_var(old_cap); /* !SECURE_NO_SETUID_FIXUP */ int res; if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */ return -EINVAL; old_fsuid = current->fsuid; old_fsgid = current->fsgid; current->fsuid = current->uid; current->fsgid = current->gid; if (!issecure(SECURE_NO_SETUID_FIXUP)) { /* * Clear the capabilities if we switch to a non-root user */ #ifndef CONFIG_SECURITY_FILE_CAPABILITIES /* * FIXME: There is a race here against sys_capset. The * capabilities can change yet we will restore the old * value below. We should hold task_capabilities_lock, * but we cannot because user_path_walk can sleep. */ #endif /* ndef CONFIG_SECURITY_FILE_CAPABILITIES */ if (current->uid) old_cap = cap_set_effective(__cap_empty_set); else old_cap = cap_set_effective(current->cap_permitted); } res = __user_walk_fd(dfd, filename, LOOKUP_FOLLOW|LOOKUP_ACCESS, &nd); if (res) goto out; res = vfs_permission(&nd, mode); /* SuS v2 requires we report a read only fs too */ if(res || !(mode & S_IWOTH) || special_file(nd.path.dentry->d_inode->i_mode)) goto out_path_release; /* * This is a rare case where using __mnt_is_readonly() * is OK without a mnt_want/drop_write() pair. Since * no actual write to the fs is performed here, we do * not need to telegraph to that to anyone. * * By doing this, we accept that this access is * inherently racy and know that the fs may change * state before we even see this result. */ if (__mnt_is_readonly(nd.path.mnt)) res = -EROFS; out_path_release: path_put(&nd.path); out: current->fsuid = old_fsuid; current->fsgid = old_fsgid; if (!issecure(SECURE_NO_SETUID_FIXUP)) cap_set_effective(old_cap); return res; }
int nfs_permission(struct inode *inode, int mask, struct nameidata *nd) { struct nfs_access_cache *cache = &NFS_I(inode)->cache_access; struct rpc_cred *cred; int mode = inode->i_mode; int res; if (mask == 0) return 0; if (mask & MAY_WRITE) { /* * * Nobody gets write access to a read-only fs. * */ if (IS_RDONLY(inode) && (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) return -EROFS; /* * * Nobody gets write access to an immutable file. * */ if (IS_IMMUTABLE(inode)) return -EACCES; } /* Are we checking permissions on anything other than lookup/execute? */ if ((mask & MAY_EXEC) == 0) { /* We only need to check permissions on file open() and access() */ if (!nd || !(nd->flags & (LOOKUP_OPEN|LOOKUP_ACCESS))) return 0; /* NFSv4 has atomic_open... */ if (NFS_PROTO(inode)->version > 3 && (nd->flags & LOOKUP_OPEN)) return 0; } lock_kernel(); if (!NFS_PROTO(inode)->access) goto out_notsup; cred = rpcauth_lookupcred(NFS_CLIENT(inode)->cl_auth, 0); if (cache->cred == cred && time_before(jiffies, cache->jiffies + NFS_ATTRTIMEO(inode)) && !(NFS_FLAGS(inode) & NFS_INO_INVALID_ATTR)) { if (!(res = cache->err)) { /* Is the mask a subset of an accepted mask? */ if ((cache->mask & mask) == mask) goto out; } else { /* ...or is it a superset of a rejected mask? */ if ((cache->mask & mask) == cache->mask) goto out; } } res = NFS_PROTO(inode)->access(inode, cred, mask); if (!res || res == -EACCES) goto add_cache; out: put_rpccred(cred); unlock_kernel(); return res; out_notsup: nfs_revalidate_inode(NFS_SERVER(inode), inode); res = vfs_permission(inode, mask); unlock_kernel(); return res; add_cache: cache->jiffies = jiffies; if (cache->cred) put_rpccred(cache->cred); cache->cred = cred; cache->mask = mask; cache->err = res; unlock_kernel(); return res; }
/* If times==NULL, set access and modification to current time, * must be owner or have write permission. * Else, update from *times, must be owner or super user. */ long do_utimes(int dfd, char __user *filename, struct timespec *times, int flags) { int error; struct nameidata nd; struct dentry *dentry; struct inode *inode; struct iattr newattrs; struct file *f = NULL; error = -EINVAL; if (times && (!nsec_valid(times[0].tv_nsec) || !nsec_valid(times[1].tv_nsec))) { goto out; } if (flags & ~AT_SYMLINK_NOFOLLOW) goto out; if (filename == NULL && dfd != AT_FDCWD) { error = -EINVAL; if (flags & AT_SYMLINK_NOFOLLOW) goto out; error = -EBADF; f = fget(dfd); if (!f) goto out; dentry = f->f_path.dentry; } else { error = __user_walk_fd(dfd, filename, (flags & AT_SYMLINK_NOFOLLOW) ? 0 : LOOKUP_FOLLOW, &nd); if (error) goto out; dentry = nd.dentry; } inode = dentry->d_inode; error = -EROFS; if (IS_RDONLY(inode)) goto dput_and_out; /* Don't worry, the checks are done in inode_change_ok() */ newattrs.ia_valid = ATTR_CTIME | ATTR_MTIME | ATTR_ATIME; if (times) { error = -EPERM; if (IS_APPEND(inode) || IS_IMMUTABLE(inode)) goto dput_and_out; if (times[0].tv_nsec == UTIME_OMIT) newattrs.ia_valid &= ~ATTR_ATIME; else if (times[0].tv_nsec != UTIME_NOW) { newattrs.ia_atime.tv_sec = times[0].tv_sec; newattrs.ia_atime.tv_nsec = times[0].tv_nsec; newattrs.ia_valid |= ATTR_ATIME_SET; } if (times[1].tv_nsec == UTIME_OMIT) newattrs.ia_valid &= ~ATTR_MTIME; else if (times[1].tv_nsec != UTIME_NOW) { newattrs.ia_mtime.tv_sec = times[1].tv_sec; newattrs.ia_mtime.tv_nsec = times[1].tv_nsec; newattrs.ia_valid |= ATTR_MTIME_SET; } } else { error = -EACCES; if (IS_IMMUTABLE(inode)) goto dput_and_out; if (!is_owner_or_cap(inode)) { if (f) { if (!(f->f_mode & FMODE_WRITE)) goto dput_and_out; } else { error = vfs_permission(&nd, MAY_WRITE); if (error) goto dput_and_out; } } } mutex_lock(&inode->i_mutex); error = notify_change(dentry, &newattrs); mutex_unlock(&inode->i_mutex); dput_and_out: if (f) fput(f); else path_release(&nd); out: return error; }