static inline long do_sys_truncate(const char * 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.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 = permission(inode,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 = get_lease(inode, FMODE_WRITE); if (error) goto dput_and_out; error = get_write_access(inode); if (error) goto dput_and_out; error = locks_verify_truncate(inode, NULL, length); if (!error) { DQUOT_INIT(inode); error = do_truncate(nd.dentry, length); } put_write_access(inode); dput_and_out: path_release(&nd); out: return error; }
/* * Open an existing file or directory. * The wflag argument indicates write access. * N.B. After this call fhp needs an fh_put */ int nfsd_open(struct svc_rqst *rqstp, struct svc_fh *fhp, int type, int wflag, struct file *filp) { struct dentry *dentry; struct inode *inode; int access, err; access = wflag? MAY_WRITE : MAY_READ; err = fh_verify(rqstp, fhp, type, access); if (err) goto out; dentry = fhp->fh_dentry; inode = dentry->d_inode; /* Disallow access to files with the append-only bit set or * with mandatory locking enabled */ err = nfserr_perm; if (IS_APPEND(inode) || IS_ISMNDLK(inode)) goto out; if (!inode->i_op || !inode->i_op->default_file_ops) goto out; if (wflag && (err = get_write_access(inode)) != 0) goto out_nfserr; memset(filp, 0, sizeof(*filp)); filp->f_op = inode->i_op->default_file_ops; filp->f_count = 1; filp->f_flags = wflag? O_WRONLY : O_RDONLY; filp->f_mode = wflag? FMODE_WRITE : FMODE_READ; filp->f_dentry = dentry; if (wflag) DQUOT_INIT(inode); err = 0; if (filp->f_op && filp->f_op->open) { err = filp->f_op->open(inode, filp); if (err) { if (wflag) put_write_access(inode); /* I nearly added put_filp() call here, but this filp * is really on callers stack frame. -DaveM */ filp->f_count--; } } out_nfserr: if (err) err = nfserrno(-err); out: return err; }
/* * Truncate a file. * The calling routines must make sure to update the ctime * field and call notify_change. * * XXX Nobody calls this thing? -DaveM * N.B. After this call fhp needs an fh_put */ int nfsd_truncate(struct svc_rqst *rqstp, struct svc_fh *fhp, unsigned long size) { struct dentry *dentry; struct inode *inode; struct iattr newattrs; int err; kernel_cap_t saved_cap; err = fh_verify(rqstp, fhp, S_IFREG, MAY_WRITE | MAY_TRUNC); if (err) goto out; dentry = fhp->fh_dentry; inode = dentry->d_inode; err = get_write_access(inode); if (err) goto out_nfserr; /* Things look sane, lock and do it. */ fh_lock(fhp); DQUOT_INIT(inode); newattrs.ia_size = size; newattrs.ia_valid = ATTR_SIZE | ATTR_CTIME; if (current->fsuid != 0) { saved_cap = current->cap_effective; cap_clear(current->cap_effective); } err = notify_change(dentry, &newattrs); if (current->fsuid != 0) current->cap_effective = saved_cap; if (!err) { vmtruncate(inode, size); if (inode->i_op && inode->i_op->truncate) inode->i_op->truncate(inode); } put_write_access(inode); DQUOT_DROP(inode); fh_unlock(fhp); out_nfserr: if (err) err = nfserrno(-err); out: return err; }
void jfs_delete_inode(struct inode *inode) { jfs_info("In jfs_delete_inode, inode = 0x%p", inode); if (test_cflag(COMMIT_Freewmap, inode)) freeZeroLink(inode); diFree(inode); /* * Free the inode from the quota allocation. */ DQUOT_INIT(inode); DQUOT_FREE_INODE(inode); DQUOT_DROP(inode); clear_inode(inode); }
/* utility function that does setup for reiserfs_new_inode. ** DQUOT_INIT needs lots of credits so it's better to have it ** outside of a transaction, so we had to pull some bits of ** reiserfs_new_inode out into this func. */ static int new_inode_init(struct inode *inode, struct inode *dir, int mode) { /* the quota init calls have to know who to charge the quota to, so ** we have to set uid and gid here */ inode->i_uid = current->fsuid; inode->i_mode = mode; if (dir->i_mode & S_ISGID) { inode->i_gid = dir->i_gid; if (S_ISDIR(mode)) inode->i_mode |= S_ISGID; } else { inode->i_gid = current->fsgid; } DQUOT_INIT(inode); return 0 ; }
/* utility function that does setup for reiserfs_new_inode. ** DQUOT_INIT needs lots of credits so it's better to have it ** outside of a transaction, so we had to pull some bits of ** reiserfs_new_inode out into this func. */ static int new_inode_init(struct inode *inode, struct inode *dir, int mode) { /* the quota init calls have to know who to charge the quota to, so ** we have to set uid and gid here */ inode->i_uid = current->fsuid; inode->i_mode = mode; /* Make inode invalid - just in case we are going to drop it before * the initialization happens */ INODE_PKEY(inode)->k_objectid = 0; if (dir->i_mode & S_ISGID) { inode->i_gid = dir->i_gid; if (S_ISDIR(mode)) inode->i_mode |= S_ISGID; } else { inode->i_gid = current->fsgid; } DQUOT_INIT(inode); return 0; }
void jfs_delete_inode(struct inode *inode) { jfs_info("In jfs_delete_inode, inode = 0x%p", inode); if (!is_bad_inode(inode) && (JFS_IP(inode)->fileset == FILESYSTEM_I)) { truncate_inode_pages(&inode->i_data, 0); if (test_cflag(COMMIT_Freewmap, inode)) jfs_free_zero_link(inode); diFree(inode); /* * Free the inode from the quota allocation. */ DQUOT_INIT(inode); DQUOT_FREE_INODE(inode); DQUOT_DROP(inode); } clear_inode(inode); }
static long do_sys_truncate(const char __user *pathname, loff_t length) { struct path path; struct inode *inode; int error; error = -EINVAL; if (length < 0) /* sorry, but loff_t says... */ goto out; error = user_path(pathname, &path); if (error) goto out; inode = 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(path.mnt); if (error) goto dput_and_out; error = inode_permission(inode, MAY_WRITE); if (error) goto mnt_drop_write_and_out; error = -EPERM; if (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) error = security_path_truncate(&path, length, 0); if (!error) { DQUOT_INIT(inode); error = do_truncate(path.dentry, length, 0, NULL); } put_write_and_out: put_write_access(inode); mnt_drop_write_and_out: mnt_drop_write(path.mnt); dput_and_out: path_put(&path); out: return error; }
/* * Set various file attributes. * N.B. After this call fhp needs an fh_put */ int nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap, int check_guard, time_t guardtime) { struct dentry *dentry; struct inode *inode; int accmode = MAY_SATTR; int ftype = 0; int imode; int err; int size_change = 0; if (iap->ia_valid & (ATTR_ATIME | ATTR_MTIME | ATTR_SIZE)) accmode |= MAY_WRITE|MAY_OWNER_OVERRIDE; if (iap->ia_valid & ATTR_SIZE) ftype = S_IFREG; /* Get inode */ err = fh_verify(rqstp, fhp, ftype, accmode); if (err || !iap->ia_valid) goto out; dentry = fhp->fh_dentry; inode = dentry->d_inode; /* NFSv2 does not differentiate between "set-[ac]time-to-now" * which only requires access, and "set-[ac]time-to-X" which * requires ownership. * So if it looks like it might be "set both to the same time which * is close to now", and if inode_change_ok fails, then we * convert to "set to now" instead of "set to explicit time" * * We only call inode_change_ok as the last test as technically * it is not an interface that we should be using. It is only * valid if the filesystem does not define it's own i_op->setattr. */ #define BOTH_TIME_SET (ATTR_ATIME_SET | ATTR_MTIME_SET) #define MAX_TOUCH_TIME_ERROR (30*60) if ((iap->ia_valid & BOTH_TIME_SET) == BOTH_TIME_SET && iap->ia_mtime == iap->ia_atime ) { /* Looks probable. Now just make sure time is in the right ballpark. * Solaris, at least, doesn't seem to care what the time request is. * We require it be within 30 minutes of now. */ time_t delta = iap->ia_atime - CURRENT_TIME; if (delta<0) delta = -delta; if (delta < MAX_TOUCH_TIME_ERROR && inode_change_ok(inode, iap) != 0) { /* turn off ATTR_[AM]TIME_SET but leave ATTR_[AM]TIME * this will cause notify_change to set these times to "now" */ iap->ia_valid &= ~BOTH_TIME_SET; } } /* The size case is special. It changes the file as well as the attributes. */ if (iap->ia_valid & ATTR_SIZE) { if (iap->ia_size < inode->i_size) { err = nfsd_permission(fhp->fh_export, dentry, MAY_TRUNC|MAY_OWNER_OVERRIDE); if (err) goto out; } /* * If we are changing the size of the file, then * we need to break all leases. */ err = get_lease(inode, FMODE_WRITE); if (err) goto out_nfserr; err = get_write_access(inode); if (err) goto out_nfserr; err = locks_verify_truncate(inode, NULL, iap->ia_size); if (err) { put_write_access(inode); goto out_nfserr; } DQUOT_INIT(inode); } imode = inode->i_mode; if (iap->ia_valid & ATTR_MODE) { iap->ia_mode &= S_IALLUGO; imode = iap->ia_mode |= (imode & ~S_IALLUGO); } /* Revoke setuid/setgid bit on chown/chgrp */ if ((iap->ia_valid & ATTR_UID) && (imode & S_ISUID) && iap->ia_uid != inode->i_uid) { iap->ia_valid |= ATTR_MODE; iap->ia_mode = imode &= ~S_ISUID; } if ((iap->ia_valid & ATTR_GID) && (imode & S_ISGID) && iap->ia_gid != inode->i_gid) { iap->ia_valid |= ATTR_MODE; iap->ia_mode = imode &= ~S_ISGID; } /* Change the attributes. */ iap->ia_valid |= ATTR_CTIME; if (iap->ia_valid & ATTR_SIZE) { fh_lock(fhp); size_change = 1; } err = nfserr_notsync; if (!check_guard || guardtime == inode->i_ctime) { err = notify_change(dentry, iap); err = nfserrno(err); } if (size_change) { fh_unlock(fhp); put_write_access(inode); } if (!err) if (EX_ISSYNC(fhp->fh_export)) write_inode_now(inode, 1); out: return err; out_nfserr: err = nfserrno(err); goto out; }
static int may_handle_open(struct dentry *dentry, int open_flag) { int acc_mode; int error; struct inode *inode = dentry->d_inode; if((open_flag + 1) & O_ACCMODE) open_flag++; acc_mode = ACC_MODE(open_flag); /* O_TRUNC implies we need access checks for write permissions */ if(open_flag & O_TRUNC) acc_mode |= MAY_WRITE; /* Allow the LSM permission hook to distinguish append access from general write access. */ if(open_flag & O_APPEND) acc_mode |= MAY_APPEND; if(S_ISDIR(inode->i_mode) && (acc_mode & MAY_WRITE)) return -EISDIR; error = handle_permission(inode, acc_mode); if (error) return error; if(S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) { open_flag &= ~O_TRUNC; } else if(S_ISBLK(inode->i_mode) || S_ISCHR(inode->i_mode)) { #ifdef IN_KERNEL_CHANGE_NOT_SUPP if(nd->mnt->mnt_flags & MNT_NODEV) return -EACCES; #endif open_flag &= ~O_TRUNC; } else if(IS_RDONLY(inode) && (acc_mode & MAY_WRITE)) return -EROFS; /* * An append-only file must be opened in append mode for writing. */ if(IS_APPEND(inode)) { if((open_flag & FMODE_WRITE) && !(open_flag & O_APPEND)) return -EPERM; if(open_flag & O_TRUNC) return -EPERM; } /* O_NOATIME can only be set by the owner or superuser */ if(open_flag & O_NOATIME) if(current->fsuid != inode->i_uid && !capable(CAP_FOWNER)) return -EPERM; /* * Ensure there are no outstanding leases on the file. */ error = break_lease(inode, open_flag); if(error) return error; if(open_flag & O_TRUNC) { error = get_write_access(inode); if(error) return error; #ifdef IN_KERNEL_CHANGE_NOT_SUPP /* * Refuse to truncate files with mandatory locks held on them. */ error = locks_verify_locked(inode); #endif if(!error) { DQUOT_INIT(inode); error = handle_truncate(dentry, 0, ATTR_MTIME | ATTR_CTIME); } put_write_access(inode); if(error) return error; } else if(open_flag & FMODE_WRITE) DQUOT_INIT(inode); return 0; }
/* * NAME: jfs_unlink(dip, dentry) * * FUNCTION: remove a link to object <vp> named by <name> * from parent directory <dvp> * * PARAMETER: dip - inode of parent directory * dentry - dentry of object to be removed * * RETURN: errors from subroutines * * note: * temporary file: if one or more processes have the file open * when the last link is removed, the link will be removed before * unlink() returns, but the removal of the file contents will be * postponed until all references to the files are closed. * * JFS does NOT support unlink() on directories. * */ static int jfs_unlink(struct inode *dip, struct dentry *dentry) { int rc; tid_t tid; /* transaction id */ struct inode *ip = dentry->d_inode; ino_t ino; struct component_name dname; /* object name */ struct inode *iplist[2]; struct tblock *tblk; s64 new_size = 0; int commit_flag; jfs_info("jfs_unlink: dip:0x%p name:%s", dip, dentry->d_name.name); /* Init inode for quota operations. */ DQUOT_INIT(ip); if ((rc = get_UCSname(&dname, dentry))) goto out; IWRITE_LOCK(ip, RDWRLOCK_NORMAL); tid = txBegin(dip->i_sb, 0); mutex_lock_nested(&JFS_IP(dip)->commit_mutex, COMMIT_MUTEX_PARENT); mutex_lock_nested(&JFS_IP(ip)->commit_mutex, COMMIT_MUTEX_CHILD); iplist[0] = dip; iplist[1] = ip; /* * delete the entry of target file from parent directory */ ino = ip->i_ino; if ((rc = dtDelete(tid, dip, &dname, &ino, JFS_REMOVE))) { jfs_err("jfs_unlink: dtDelete returned %d", rc); if (rc == -EIO) txAbort(tid, 1); /* Marks FS Dirty */ txEnd(tid); mutex_unlock(&JFS_IP(ip)->commit_mutex); mutex_unlock(&JFS_IP(dip)->commit_mutex); IWRITE_UNLOCK(ip); goto out1; } ASSERT(ip->i_nlink); ip->i_ctime = dip->i_ctime = dip->i_mtime = CURRENT_TIME; mark_inode_dirty(dip); /* update target's inode */ inode_dec_link_count(ip); /* * commit zero link count object */ if (ip->i_nlink == 0) { assert(!test_cflag(COMMIT_Nolink, ip)); /* free block resources */ if ((new_size = commitZeroLink(tid, ip)) < 0) { txAbort(tid, 1); /* Marks FS Dirty */ txEnd(tid); mutex_unlock(&JFS_IP(ip)->commit_mutex); mutex_unlock(&JFS_IP(dip)->commit_mutex); IWRITE_UNLOCK(ip); rc = new_size; goto out1; } tblk = tid_to_tblock(tid); tblk->xflag |= COMMIT_DELETE; tblk->u.ip = ip; } /* * Incomplete truncate of file data can * result in timing problems unless we synchronously commit the * transaction. */ if (new_size) commit_flag = COMMIT_SYNC; else commit_flag = 0; /* * If xtTruncate was incomplete, commit synchronously to avoid * timing complications */ rc = txCommit(tid, 2, &iplist[0], commit_flag); txEnd(tid); mutex_unlock(&JFS_IP(ip)->commit_mutex); mutex_unlock(&JFS_IP(dip)->commit_mutex); while (new_size && (rc == 0)) { tid = txBegin(dip->i_sb, 0); mutex_lock(&JFS_IP(ip)->commit_mutex); new_size = xtTruncate_pmap(tid, ip, new_size); if (new_size < 0) { txAbort(tid, 1); /* Marks FS Dirty */ rc = new_size; } else rc = txCommit(tid, 2, &iplist[0], COMMIT_SYNC); txEnd(tid); mutex_unlock(&JFS_IP(ip)->commit_mutex); } if (ip->i_nlink == 0) set_cflag(COMMIT_Nolink, ip); IWRITE_UNLOCK(ip); /* * Truncating the directory index table is not guaranteed. It * may need to be done iteratively */ if (test_cflag(COMMIT_Stale, dip)) { if (dip->i_size > 1) jfs_truncate_nolock(dip, 0); clear_cflag(COMMIT_Stale, dip); } out1: free_UCSname(&dname); out: jfs_info("jfs_unlink: rc:%d", rc); return rc; }
/* * NAME: jfs_rename * * FUNCTION: rename a file or directory */ static int jfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) { struct btstack btstack; ino_t ino; struct component_name new_dname; struct inode *new_ip; struct component_name old_dname; struct inode *old_ip; int rc; tid_t tid; struct tlock *tlck; struct dt_lock *dtlck; struct lv *lv; int ipcount; struct inode *iplist[4]; struct tblock *tblk; s64 new_size = 0; int commit_flag; jfs_info("jfs_rename: %s %s", old_dentry->d_name.name, new_dentry->d_name.name); old_ip = old_dentry->d_inode; new_ip = new_dentry->d_inode; if ((rc = get_UCSname(&old_dname, old_dentry))) goto out1; if ((rc = get_UCSname(&new_dname, new_dentry))) goto out2; /* * Make sure source inode number is what we think it is */ rc = dtSearch(old_dir, &old_dname, &ino, &btstack, JFS_LOOKUP); if (rc || (ino != old_ip->i_ino)) { rc = -ENOENT; goto out3; } /* * Make sure dest inode number (if any) is what we think it is */ rc = dtSearch(new_dir, &new_dname, &ino, &btstack, JFS_LOOKUP); if (rc == 0) { if ((new_ip == 0) || (ino != new_ip->i_ino)) { rc = -ESTALE; goto out3; } } else if (rc != -ENOENT) goto out3; else if (new_ip) { /* no entry exists, but one was expected */ rc = -ESTALE; goto out3; } if (S_ISDIR(old_ip->i_mode)) { if (new_ip) { if (!dtEmpty(new_ip)) { rc = -ENOTEMPTY; goto out3; } } else if ((new_dir != old_dir) && (new_dir->i_nlink == JFS_LINK_MAX)) { rc = -EMLINK; goto out3; } } else if (new_ip) { IWRITE_LOCK(new_ip, RDWRLOCK_NORMAL); /* Init inode for quota operations. */ DQUOT_INIT(new_ip); } /* * The real work starts here */ tid = txBegin(new_dir->i_sb, 0); /* * How do we know the locking is safe from deadlocks? * The vfs does the hard part for us. Any time we are taking nested * commit_mutexes, the vfs already has i_mutex held on the parent. * Here, the vfs has already taken i_mutex on both old_dir and new_dir. */ mutex_lock_nested(&JFS_IP(new_dir)->commit_mutex, COMMIT_MUTEX_PARENT); mutex_lock_nested(&JFS_IP(old_ip)->commit_mutex, COMMIT_MUTEX_CHILD); if (old_dir != new_dir) mutex_lock_nested(&JFS_IP(old_dir)->commit_mutex, COMMIT_MUTEX_SECOND_PARENT); if (new_ip) { mutex_lock_nested(&JFS_IP(new_ip)->commit_mutex, COMMIT_MUTEX_VICTIM); /* * Change existing directory entry to new inode number */ ino = new_ip->i_ino; rc = dtModify(tid, new_dir, &new_dname, &ino, old_ip->i_ino, JFS_RENAME); if (rc) goto out4; drop_nlink(new_ip); if (S_ISDIR(new_ip->i_mode)) { drop_nlink(new_ip); if (new_ip->i_nlink) { mutex_unlock(&JFS_IP(new_ip)->commit_mutex); if (old_dir != new_dir) mutex_unlock(&JFS_IP(old_dir)->commit_mutex); mutex_unlock(&JFS_IP(old_ip)->commit_mutex); mutex_unlock(&JFS_IP(new_dir)->commit_mutex); if (!S_ISDIR(old_ip->i_mode) && new_ip) IWRITE_UNLOCK(new_ip); jfs_error(new_ip->i_sb, "jfs_rename: new_ip->i_nlink != 0"); return -EIO; } tblk = tid_to_tblock(tid); tblk->xflag |= COMMIT_DELETE; tblk->u.ip = new_ip; } else if (new_ip->i_nlink == 0) { assert(!test_cflag(COMMIT_Nolink, new_ip)); /* free block resources */ if ((new_size = commitZeroLink(tid, new_ip)) < 0) { txAbort(tid, 1); /* Marks FS Dirty */ rc = new_size; goto out4; } tblk = tid_to_tblock(tid); tblk->xflag |= COMMIT_DELETE; tblk->u.ip = new_ip; } else { new_ip->i_ctime = CURRENT_TIME; mark_inode_dirty(new_ip); } } else { /* * Add new directory entry */ rc = dtSearch(new_dir, &new_dname, &ino, &btstack, JFS_CREATE); if (rc) { jfs_err("jfs_rename didn't expect dtSearch to fail " "w/rc = %d", rc); goto out4; } ino = old_ip->i_ino; rc = dtInsert(tid, new_dir, &new_dname, &ino, &btstack); if (rc) { if (rc == -EIO) jfs_err("jfs_rename: dtInsert returned -EIO"); goto out4; } if (S_ISDIR(old_ip->i_mode)) inc_nlink(new_dir); } /* * Remove old directory entry */ ino = old_ip->i_ino; rc = dtDelete(tid, old_dir, &old_dname, &ino, JFS_REMOVE); if (rc) { jfs_err("jfs_rename did not expect dtDelete to return rc = %d", rc); txAbort(tid, 1); /* Marks Filesystem dirty */ goto out4; } if (S_ISDIR(old_ip->i_mode)) { drop_nlink(old_dir); if (old_dir != new_dir) { /* * Change inode number of parent for moved directory */ JFS_IP(old_ip)->i_dtroot.header.idotdot = cpu_to_le32(new_dir->i_ino); /* Linelock header of dtree */ tlck = txLock(tid, old_ip, (struct metapage *) &JFS_IP(old_ip)->bxflag, tlckDTREE | tlckBTROOT | tlckRELINK); dtlck = (struct dt_lock *) & tlck->lock; ASSERT(dtlck->index == 0); lv = & dtlck->lv[0]; lv->offset = 0; lv->length = 1; dtlck->index++; } } /* * Update ctime on changed/moved inodes & mark dirty */ old_ip->i_ctime = CURRENT_TIME; mark_inode_dirty(old_ip); new_dir->i_ctime = new_dir->i_mtime = current_fs_time(new_dir->i_sb); mark_inode_dirty(new_dir); /* Build list of inodes modified by this transaction */ ipcount = 0; iplist[ipcount++] = old_ip; if (new_ip) iplist[ipcount++] = new_ip; iplist[ipcount++] = old_dir; if (old_dir != new_dir) { iplist[ipcount++] = new_dir; old_dir->i_ctime = old_dir->i_mtime = CURRENT_TIME; mark_inode_dirty(old_dir); } /* * Incomplete truncate of file data can * result in timing problems unless we synchronously commit the * transaction. */ if (new_size) commit_flag = COMMIT_SYNC; else commit_flag = 0; rc = txCommit(tid, ipcount, iplist, commit_flag); out4: txEnd(tid); if (new_ip) mutex_unlock(&JFS_IP(new_ip)->commit_mutex); if (old_dir != new_dir) mutex_unlock(&JFS_IP(old_dir)->commit_mutex); mutex_unlock(&JFS_IP(old_ip)->commit_mutex); mutex_unlock(&JFS_IP(new_dir)->commit_mutex); while (new_size && (rc == 0)) { tid = txBegin(new_ip->i_sb, 0); mutex_lock(&JFS_IP(new_ip)->commit_mutex); new_size = xtTruncate_pmap(tid, new_ip, new_size); if (new_size < 0) { txAbort(tid, 1); rc = new_size; } else rc = txCommit(tid, 1, &new_ip, COMMIT_SYNC); txEnd(tid); mutex_unlock(&JFS_IP(new_ip)->commit_mutex); } if (new_ip && (new_ip->i_nlink == 0)) set_cflag(COMMIT_Nolink, new_ip); out3: free_UCSname(&new_dname); out2: free_UCSname(&old_dname); out1: if (new_ip && !S_ISDIR(new_ip->i_mode)) IWRITE_UNLOCK(new_ip); /* * Truncating the directory index table is not guaranteed. It * may need to be done iteratively */ if (test_cflag(COMMIT_Stale, old_dir)) { if (old_dir->i_size > 1) jfs_truncate_nolock(old_dir, 0); clear_cflag(COMMIT_Stale, old_dir); } jfs_info("jfs_rename: returning %d", rc); return rc; }
/* * Create a hardlink * N.B. After this call _both_ ffhp and tfhp need an fh_put */ int nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int len, struct svc_fh *tfhp) { struct dentry *ddir, *dnew, *dold; struct inode *dirp, *dest; int err; err = fh_verify(rqstp, ffhp, S_IFDIR, MAY_CREATE); if (err) goto out; err = fh_verify(rqstp, tfhp, S_IFREG, MAY_NOP); if (err) goto out; err = nfserr_perm; if (!len) goto out; ddir = ffhp->fh_dentry; dirp = ddir->d_inode; dnew = lookup_dentry(fname, dget(ddir), 0); err = PTR_ERR(dnew); if (IS_ERR(dnew)) goto out_nfserr; /* * Lock the parent before checking for existence */ err = fh_lock_parent(ffhp, dnew); if (err) goto out_dput; err = nfserr_exist; if (dnew->d_inode) goto out_unlock; dold = tfhp->fh_dentry; dest = dold->d_inode; err = nfserr_acces; if (nfsd_iscovered(ddir, ffhp->fh_export)) goto out_unlock; /* FIXME: nxdev for NFSv3 */ if (dirp->i_dev != dest->i_dev) goto out_unlock; err = nfserr_perm; if (IS_IMMUTABLE(dest) /* || IS_APPEND(dest) */ ) goto out_unlock; if (!dirp->i_op || !dirp->i_op->link) goto out_unlock; DQUOT_INIT(dirp); err = dirp->i_op->link(dold, dirp, dnew); DQUOT_DROP(dirp); if (!err) { if (EX_ISSYNC(ffhp->fh_export)) { write_inode_now(dirp); write_inode_now(dest); } } else err = nfserrno(-err); out_unlock: fh_unlock(ffhp); out_dput: dput(dnew); out: return err; out_nfserr: err = nfserrno(-err); goto out; }
/* * Create a symlink and look up its inode * N.B. After this call _both_ fhp and resfhp need an fh_put */ int nfsd_symlink(struct svc_rqst *rqstp, struct svc_fh *fhp, char *fname, int flen, char *path, int plen, struct svc_fh *resfhp) { struct dentry *dentry, *dnew; struct inode *dirp; int err; err = nfserr_noent; if (!flen || !plen) goto out; err = fh_verify(rqstp, fhp, S_IFDIR, MAY_CREATE); if (err) goto out; dentry = fhp->fh_dentry; err = nfserr_perm; if (nfsd_iscovered(dentry, fhp->fh_export)) goto out; dirp = dentry->d_inode; if (!dirp->i_op || !dirp->i_op->symlink) goto out; dnew = lookup_dentry(fname, dget(dentry), 0); err = PTR_ERR(dnew); if (IS_ERR(dnew)) goto out_nfserr; /* * Lock the parent before checking for existence */ err = fh_lock_parent(fhp, dnew); if (err) goto out_compose; err = nfserr_exist; if (!dnew->d_inode) { DQUOT_INIT(dirp); err = dirp->i_op->symlink(dirp, dnew, path); DQUOT_DROP(dirp); if (!err) { if (EX_ISSYNC(fhp->fh_export)) write_inode_now(dirp); } else err = nfserrno(-err); } fh_unlock(fhp); /* Compose the fh so the dentry will be freed ... */ out_compose: fh_compose(resfhp, fhp->fh_export, dnew); out: return err; out_nfserr: err = nfserrno(-err); goto out; }
/* * Create a file (regular, directory, device, fifo); UNIX sockets * not yet implemented. * If the response fh has been verified, the parent directory should * already be locked. Note that the parent directory is left locked. * * N.B. Every call to nfsd_create needs an fh_put for _both_ fhp and resfhp */ int nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp, char *fname, int flen, struct iattr *iap, int type, dev_t rdev, struct svc_fh *resfhp) { struct dentry *dentry, *dchild; struct inode *dirp; nfsd_dirop_t opfunc = NULL; int err; err = nfserr_perm; if (!flen) goto out; err = fh_verify(rqstp, fhp, S_IFDIR, MAY_CREATE); if (err) goto out; dentry = fhp->fh_dentry; dirp = dentry->d_inode; err = nfserr_notdir; if(!dirp->i_op || !dirp->i_op->lookup) goto out; /* * Check whether the response file handle has been verified yet. * If it has, the parent directory should already be locked. */ if (!resfhp->fh_dverified) { dchild = lookup_dentry(fname, dget(dentry), 0); err = PTR_ERR(dchild); if (IS_ERR(dchild)) goto out_nfserr; fh_compose(resfhp, fhp->fh_export, dchild); /* Lock the parent and check for errors ... */ err = fh_lock_parent(fhp, dchild); if (err) goto out; } else { dchild = resfhp->fh_dentry; if (!fhp->fh_locked) printk(KERN_ERR "nfsd_create: parent %s/%s not locked!\n", dentry->d_parent->d_name.name, dentry->d_name.name); } /* * Make sure the child dentry is still negative ... */ err = nfserr_exist; if (dchild->d_inode) { printk(KERN_WARNING "nfsd_create: dentry %s/%s not negative!\n", dentry->d_name.name, dchild->d_name.name); goto out; } /* * Get the dir op function pointer. */ err = nfserr_perm; switch (type) { case S_IFREG: opfunc = (nfsd_dirop_t) dirp->i_op->create; break; case S_IFDIR: opfunc = (nfsd_dirop_t) dirp->i_op->mkdir; break; case S_IFCHR: case S_IFBLK: /* The client is _NOT_ required to do security enforcement */ if(!capable(CAP_SYS_ADMIN)) { err = -EPERM; goto out; } case S_IFIFO: case S_IFSOCK: opfunc = dirp->i_op->mknod; break; } if (!opfunc) goto out; if (!(iap->ia_valid & ATTR_MODE)) iap->ia_mode = 0; /* * Call the dir op function to create the object. */ DQUOT_INIT(dirp); err = opfunc(dirp, dchild, iap->ia_mode, rdev); DQUOT_DROP(dirp); if (err < 0) goto out_nfserr; if (EX_ISSYNC(fhp->fh_export)) write_inode_now(dirp); /* * Update the file handle to get the new inode info. */ fh_update(resfhp); /* Set file attributes. Mode has already been set and * setting uid/gid works only for root. Irix appears to * send along the gid when it tries to implement setgid * directories via NFS. */ err = 0; if ((iap->ia_valid &= (ATTR_UID|ATTR_GID|ATTR_MODE)) != 0) err = nfsd_setattr(rqstp, resfhp, iap); out: return err; out_nfserr: err = nfserrno(-err); goto out; }
/* * NOTE! When we get the inode, we're the only people * that have access to it, and as such there are no * race conditions we have to worry about. The inode * is not on the hash-lists, and it cannot be reached * through the filesystem because the directory entry * has been deleted earlier. * * HOWEVER: we must make sure that we get no aliases, * which means that we have to call "clear_inode()" * _before_ we mark the inode not in use in the inode * bitmaps. Otherwise a newly created file might use * the same inode number (not actually the same pointer * though), and then we'd have two inodes sharing the * same inode number and space on the harddisk. */ void ext3_free_inode (handle_t *handle, struct inode * inode) { struct super_block * sb = inode->i_sb; int is_directory; unsigned long ino; struct buffer_head *bitmap_bh = NULL; struct buffer_head *bh2; unsigned long block_group; unsigned long bit; struct ext3_group_desc * gdp; struct ext3_super_block * es; struct ext3_sb_info *sbi; int fatal = 0, err; if (atomic_read(&inode->i_count) > 1) { printk ("ext3_free_inode: inode has count=%d\n", atomic_read(&inode->i_count)); return; } if (inode->i_nlink) { printk ("ext3_free_inode: inode has nlink=%d\n", inode->i_nlink); return; } if (!sb) { printk("ext3_free_inode: inode on nonexistent device\n"); return; } sbi = EXT3_SB(sb); ino = inode->i_ino; ext3_debug ("freeing inode %lu\n", ino); /* * Note: we must free any quota before locking the superblock, * as writing the quota to disk may need the lock as well. */ DQUOT_INIT(inode); ext3_xattr_delete_inode(handle, inode); DQUOT_FREE_INODE(inode); DQUOT_DROP(inode); is_directory = S_ISDIR(inode->i_mode); /* Do this BEFORE marking the inode not in use or returning an error */ clear_inode (inode); es = EXT3_SB(sb)->s_es; if (ino < EXT3_FIRST_INO(sb) || ino > le32_to_cpu(es->s_inodes_count)) { ext3_error (sb, "ext3_free_inode", "reserved or nonexistent inode %lu", ino); goto error_return; } block_group = (ino - 1) / EXT3_INODES_PER_GROUP(sb); bit = (ino - 1) % EXT3_INODES_PER_GROUP(sb); bitmap_bh = read_inode_bitmap(sb, block_group); if (!bitmap_bh) goto error_return; BUFFER_TRACE(bitmap_bh, "get_write_access"); fatal = ext3_journal_get_write_access(handle, bitmap_bh); if (fatal) goto error_return; /* Ok, now we can actually update the inode bitmaps.. */ if (!ext3_clear_bit_atomic(sb_bgl_lock(sbi, block_group), bit, bitmap_bh->b_data)) ext3_error (sb, "ext3_free_inode", "bit already cleared for inode %lu", ino); else { gdp = ext3_get_group_desc (sb, block_group, &bh2); BUFFER_TRACE(bh2, "get_write_access"); fatal = ext3_journal_get_write_access(handle, bh2); if (fatal) goto error_return; if (gdp) { spin_lock(sb_bgl_lock(sbi, block_group)); gdp->bg_free_inodes_count = cpu_to_le16( le16_to_cpu(gdp->bg_free_inodes_count) + 1); if (is_directory) gdp->bg_used_dirs_count = cpu_to_le16( le16_to_cpu(gdp->bg_used_dirs_count) - 1); spin_unlock(sb_bgl_lock(sbi, block_group)); percpu_counter_inc(&sbi->s_freeinodes_counter); if (is_directory) percpu_counter_dec(&sbi->s_dirs_counter); } BUFFER_TRACE(bh2, "call ext3_journal_dirty_metadata"); err = ext3_journal_dirty_metadata(handle, bh2); if (!fatal) fatal = err; } BUFFER_TRACE(bitmap_bh, "call ext3_journal_dirty_metadata"); err = ext3_journal_dirty_metadata(handle, bitmap_bh); if (!fatal) fatal = err; sb->s_dirt = 1; error_return: brelse(bitmap_bh); ext3_std_error(sb, fatal); }
/* * Set various file attributes. * N.B. After this call fhp needs an fh_put */ __be32 nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap, int check_guard, time_t guardtime) { struct dentry *dentry; struct inode *inode; int accmode = NFSD_MAY_SATTR; int ftype = 0; __be32 err; int host_err; int size_change = 0; if (iap->ia_valid & (ATTR_ATIME | ATTR_MTIME | ATTR_SIZE)) accmode |= NFSD_MAY_WRITE|NFSD_MAY_OWNER_OVERRIDE; if (iap->ia_valid & ATTR_SIZE) ftype = S_IFREG; /* Get inode */ err = fh_verify(rqstp, fhp, ftype, accmode); if (err) goto out; dentry = fhp->fh_dentry; inode = dentry->d_inode; /* Ignore any mode updates on symlinks */ if (S_ISLNK(inode->i_mode)) iap->ia_valid &= ~ATTR_MODE; if (!iap->ia_valid) goto out; /* * NFSv2 does not differentiate between "set-[ac]time-to-now" * which only requires access, and "set-[ac]time-to-X" which * requires ownership. * So if it looks like it might be "set both to the same time which * is close to now", and if inode_change_ok fails, then we * convert to "set to now" instead of "set to explicit time" * * We only call inode_change_ok as the last test as technically * it is not an interface that we should be using. It is only * valid if the filesystem does not define it's own i_op->setattr. */ #define BOTH_TIME_SET (ATTR_ATIME_SET | ATTR_MTIME_SET) #define MAX_TOUCH_TIME_ERROR (30*60) if ((iap->ia_valid & BOTH_TIME_SET) == BOTH_TIME_SET && iap->ia_mtime.tv_sec == iap->ia_atime.tv_sec) { /* * Looks probable. * * Now just make sure time is in the right ballpark. * Solaris, at least, doesn't seem to care what the time * request is. We require it be within 30 minutes of now. */ time_t delta = iap->ia_atime.tv_sec - get_seconds(); if (delta < 0) delta = -delta; if (delta < MAX_TOUCH_TIME_ERROR && inode_change_ok(inode, iap) != 0) { /* * Turn off ATTR_[AM]TIME_SET but leave ATTR_[AM]TIME. * This will cause notify_change to set these times * to "now" */ iap->ia_valid &= ~BOTH_TIME_SET; } } /* * The size case is special. * It changes the file as well as the attributes. */ if (iap->ia_valid & ATTR_SIZE) { if (iap->ia_size < inode->i_size) { err = nfsd_permission(rqstp, fhp->fh_export, dentry, NFSD_MAY_TRUNC|NFSD_MAY_OWNER_OVERRIDE); if (err) goto out; } /* * If we are changing the size of the file, then * we need to break all leases. */ host_err = break_lease(inode, FMODE_WRITE | O_NONBLOCK); if (host_err == -EWOULDBLOCK) host_err = -ETIMEDOUT; if (host_err) /* ENOMEM or EWOULDBLOCK */ goto out_nfserr; host_err = get_write_access(inode); if (host_err) goto out_nfserr; size_change = 1; host_err = locks_verify_truncate(inode, NULL, iap->ia_size); if (host_err) { put_write_access(inode); goto out_nfserr; } DQUOT_INIT(inode); } /* sanitize the mode change */ if (iap->ia_valid & ATTR_MODE) { iap->ia_mode &= S_IALLUGO; iap->ia_mode |= (inode->i_mode & ~S_IALLUGO); } /* Revoke setuid/setgid on chown */ if (((iap->ia_valid & ATTR_UID) && iap->ia_uid != inode->i_uid) || ((iap->ia_valid & ATTR_GID) && iap->ia_gid != inode->i_gid)) { iap->ia_valid |= ATTR_KILL_PRIV; if (iap->ia_valid & ATTR_MODE) { /* we're setting mode too, just clear the s*id bits */ iap->ia_mode &= ~S_ISUID; if (iap->ia_mode & S_IXGRP) iap->ia_mode &= ~S_ISGID; } else { /* set ATTR_KILL_* bits and let VFS handle it */ iap->ia_valid |= (ATTR_KILL_SUID | ATTR_KILL_SGID); } } /* Change the attributes. */ iap->ia_valid |= ATTR_CTIME; err = nfserr_notsync; if (!check_guard || guardtime == inode->i_ctime.tv_sec) { fh_lock(fhp); host_err = notify_change(dentry, iap); err = nfserrno(host_err); fh_unlock(fhp); } if (size_change) put_write_access(inode); if (!err) if (EX_ISSYNC(fhp->fh_export)) write_inode_now(inode, 1); out: return err; out_nfserr: err = nfserrno(host_err); goto out; }
/* * NAME: jfs_rmdir(dip, dentry) * * FUNCTION: remove a link to child directory * * PARAMETER: dip - parent inode * dentry - child directory dentry * * RETURN: -EINVAL - if name is . or .. * -EINVAL - if . or .. exist but are invalid. * errors from subroutines * * note: * if other threads have the directory open when the last link * is removed, the "." and ".." entries, if present, are removed before * rmdir() returns and no new entries may be created in the directory, * but the directory is not removed until the last reference to * the directory is released (cf.unlink() of regular file). */ static int jfs_rmdir(struct inode *dip, struct dentry *dentry) { int rc; tid_t tid; /* transaction id */ struct inode *ip = dentry->d_inode; ino_t ino; struct component_name dname; struct inode *iplist[2]; struct tblock *tblk; jfs_info("jfs_rmdir: dip:0x%p name:%s", dip, dentry->d_name.name); /* Init inode for quota operations. */ DQUOT_INIT(ip); /* directory must be empty to be removed */ if (!dtEmpty(ip)) { rc = -ENOTEMPTY; goto out; } if ((rc = get_UCSname(&dname, dentry))) { goto out; } tid = txBegin(dip->i_sb, 0); mutex_lock_nested(&JFS_IP(dip)->commit_mutex, COMMIT_MUTEX_PARENT); mutex_lock_nested(&JFS_IP(ip)->commit_mutex, COMMIT_MUTEX_CHILD); iplist[0] = dip; iplist[1] = ip; tblk = tid_to_tblock(tid); tblk->xflag |= COMMIT_DELETE; tblk->u.ip = ip; /* * delete the entry of target directory from parent directory */ ino = ip->i_ino; if ((rc = dtDelete(tid, dip, &dname, &ino, JFS_REMOVE))) { jfs_err("jfs_rmdir: dtDelete returned %d", rc); if (rc == -EIO) txAbort(tid, 1); txEnd(tid); mutex_unlock(&JFS_IP(ip)->commit_mutex); mutex_unlock(&JFS_IP(dip)->commit_mutex); goto out2; } /* update parent directory's link count corresponding * to ".." entry of the target directory deleted */ dip->i_ctime = dip->i_mtime = CURRENT_TIME; inode_dec_link_count(dip); /* * OS/2 could have created EA and/or ACL */ /* free EA from both persistent and working map */ if (JFS_IP(ip)->ea.flag & DXD_EXTENT) { /* free EA pages */ txEA(tid, ip, &JFS_IP(ip)->ea, NULL); } JFS_IP(ip)->ea.flag = 0; /* free ACL from both persistent and working map */ if (JFS_IP(ip)->acl.flag & DXD_EXTENT) { /* free ACL pages */ txEA(tid, ip, &JFS_IP(ip)->acl, NULL); } JFS_IP(ip)->acl.flag = 0; /* mark the target directory as deleted */ clear_nlink(ip); mark_inode_dirty(ip); rc = txCommit(tid, 2, &iplist[0], 0); txEnd(tid); mutex_unlock(&JFS_IP(ip)->commit_mutex); mutex_unlock(&JFS_IP(dip)->commit_mutex); /* * Truncating the directory index table is not guaranteed. It * may need to be done iteratively */ if (test_cflag(COMMIT_Stale, dip)) { if (dip->i_size > 1) jfs_truncate_nolock(dip, 0); clear_cflag(COMMIT_Stale, dip); } out2: free_UCSname(&dname); out: jfs_info("jfs_rmdir: rc:%d", rc); return rc; }