static int esdfs_create(struct inode *dir, struct dentry *dentry, umode_t mode, struct nameidata *nd) { int err = 0; struct dentry *lower_dentry; struct dentry *lower_parent_dentry = NULL; struct path lower_path, saved_path; struct inode *lower_inode; int mask; const struct cred *creds; /* * Need to recheck derived permissions unified mode to prevent certain * applications from creating files at the root. */ if (test_opt(ESDFS_SB(dir->i_sb), DERIVE_UNIFIED) && esdfs_check_derived_permission(dir, ESDFS_MAY_CREATE) != 0) return -EACCES; creds = esdfs_override_creds(ESDFS_SB(dir->i_sb), &mask); if (!creds) return -ENOMEM; esdfs_get_lower_path(dentry, &lower_path); lower_dentry = lower_path.dentry; lower_parent_dentry = lock_parent(lower_dentry); err = mnt_want_write(lower_path.mnt); if (err) goto out_unlock; esdfs_set_lower_mode(ESDFS_SB(dir->i_sb), &mode); lower_inode = esdfs_lower_inode(dir); pathcpy(&saved_path, &nd->path); pathcpy(&nd->path, &lower_path); err = vfs_create(lower_inode, lower_dentry, mode, nd); pathcpy(&nd->path, &saved_path); if (err) goto out; err = esdfs_interpose(dentry, dir->i_sb, &lower_path); if (err) goto out; fsstack_copy_attr_times(dir, esdfs_lower_inode(dir)); fsstack_copy_inode_size(dir, lower_parent_dentry->d_inode); out: mnt_drop_write(lower_path.mnt); out_unlock: unlock_dir(lower_parent_dentry); esdfs_put_lower_path(dentry, &lower_path); esdfs_revert_creds(creds, &mask); return err; }
static int esdfs_statfs(struct dentry *dentry, struct kstatfs *buf) { int err; struct path lower_path; esdfs_get_lower_path(dentry, &lower_path); err = vfs_statfs(&lower_path, buf); esdfs_put_lower_path(dentry, &lower_path); /* set return buf to our f/s to avoid confusing user-level utils */ buf->f_type = ESDFS_SUPER_MAGIC; return err; }
static int esdfs_unlink(struct inode *dir, struct dentry *dentry) { int err; struct dentry *lower_dentry; struct inode *lower_dir_inode = esdfs_lower_inode(dir); struct dentry *lower_dir_dentry; struct path lower_path; const struct cred *creds; creds = esdfs_override_creds(ESDFS_SB(dir->i_sb), NULL); if (!creds) return -ENOMEM; esdfs_get_lower_path(dentry, &lower_path); lower_dentry = lower_path.dentry; dget(lower_dentry); lower_dir_dentry = lock_parent(lower_dentry); err = mnt_want_write(lower_path.mnt); if (err) goto out_unlock; err = vfs_unlink(lower_dir_inode, lower_dentry); /* * Note: unlinking on top of NFS can cause silly-renamed files. * Trying to delete such files results in EBUSY from NFS * below. Silly-renamed files will get deleted by NFS later on, so * we just need to detect them here and treat such EBUSY errors as * if the upper file was successfully deleted. */ if (err == -EBUSY && lower_dentry->d_flags & DCACHE_NFSFS_RENAMED) err = 0; if (err) goto out; fsstack_copy_attr_times(dir, lower_dir_inode); fsstack_copy_inode_size(dir, lower_dir_inode); set_nlink(dentry->d_inode, esdfs_lower_inode(dentry->d_inode)->i_nlink); dentry->d_inode->i_ctime = dir->i_ctime; d_drop(dentry); /* this is needed, else LTP fails (VFS won't do it) */ out: mnt_drop_write(lower_path.mnt); out_unlock: unlock_dir(lower_dir_dentry); dput(lower_dentry); esdfs_put_lower_path(dentry, &lower_path); esdfs_revert_creds(creds, NULL); return err; }
static int esdfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) { int err = 0; struct dentry *lower_dentry; struct dentry *lower_parent_dentry = NULL; struct path lower_path; int mask; const struct cred *creds = esdfs_override_creds(ESDFS_SB(dir->i_sb), &mask); if (!creds) return -ENOMEM; esdfs_get_lower_path(dentry, &lower_path); lower_dentry = lower_path.dentry; lower_parent_dentry = lock_parent(lower_dentry); mode |= S_IFDIR; esdfs_set_lower_mode(ESDFS_SB(dir->i_sb), &mode); err = mnt_want_write(lower_path.mnt); if (err) goto out_unlock; err = vfs_mkdir(lower_parent_dentry->d_inode, lower_dentry, mode); if (err) goto out; err = esdfs_interpose(dentry, dir->i_sb, &lower_path); if (err) goto out; fsstack_copy_attr_times(dir, esdfs_lower_inode(dir)); fsstack_copy_inode_size(dir, lower_parent_dentry->d_inode); /* update number of links on parent directory */ set_nlink(dir, esdfs_lower_inode(dir)->i_nlink); if (ESDFS_DERIVE_PERMS(ESDFS_SB(dir->i_sb))) err = esdfs_derive_mkdir_contents(dentry); out: mnt_drop_write(lower_path.mnt); out_unlock: unlock_dir(lower_parent_dentry); esdfs_put_lower_path(dentry, &lower_path); esdfs_revert_creds(creds, &mask); return err; }
static int esdfs_rmdir(struct inode *dir, struct dentry *dentry) { struct dentry *lower_dentry; struct dentry *lower_dir_dentry; int err; struct path lower_path; const struct cred *creds = esdfs_override_creds(ESDFS_SB(dir->i_sb), NULL); if (!creds) return -ENOMEM; /* Never remove a pseudo link target. Only the source. */ if (ESDFS_DENTRY_HAS_STUB(dentry)) esdfs_get_lower_stub_path(dentry, &lower_path); else esdfs_get_lower_path(dentry, &lower_path); lower_dentry = lower_path.dentry; lower_dir_dentry = lock_parent(lower_dentry); err = mnt_want_write(lower_path.mnt); if (err) goto out_unlock; err = vfs_rmdir(lower_dir_dentry->d_inode, lower_dentry); if (err) goto out; d_drop(dentry); /* drop our dentry on success (why not VFS's job?) */ if (dentry->d_inode) clear_nlink(dentry->d_inode); fsstack_copy_attr_times(dir, lower_dir_dentry->d_inode); fsstack_copy_inode_size(dir, lower_dir_dentry->d_inode); set_nlink(dir, lower_dir_dentry->d_inode->i_nlink); out: mnt_drop_write(lower_path.mnt); out_unlock: unlock_dir(lower_dir_dentry); esdfs_put_lower_path(dentry, &lower_path); esdfs_revert_creds(creds, NULL); return err; }
static int esdfs_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat) { int err; struct path lower_path; struct vfsmount *lower_mount; struct dentry *lower_dentry; struct kstat lower_stat; struct inode *lower_inode; struct inode *inode = dentry->d_inode; const struct cred *creds = esdfs_override_creds(ESDFS_SB(inode->i_sb), NULL); if (!creds) return -ENOMEM; esdfs_get_lower_path(dentry, &lower_path); lower_mount = lower_path.mnt; lower_dentry = lower_path.dentry; /* We need the lower getattr to calculate stat->blocks for us. */ err = vfs_getattr(lower_mount, lower_dentry, &lower_stat); if (err) goto out; lower_inode = esdfs_lower_inode(inode); esdfs_copy_attr(inode, lower_inode); fsstack_copy_inode_size(inode, lower_inode); generic_fillattr(inode, stat); stat->blocks = lower_stat.blocks; out: esdfs_put_lower_path(dentry, &lower_path); esdfs_revert_creds(creds, NULL); return err; }
/* * returns: -ERRNO if error (returned to user) * 0: tell VFS to invalidate dentry * 1: dentry is valid */ static int esdfs_d_revalidate(struct dentry *dentry, struct nameidata *nd) { struct path lower_path; struct path lower_parent_path; struct dentry *parent_dentry = NULL; struct dentry *lower_dentry = NULL; struct dentry *lower_parent_dentry = NULL; int err = 1; if (nd && (nd->flags & LOOKUP_RCU)) return -ECHILD; /* short-circuit if it's root */ spin_lock(&dentry->d_lock); if (IS_ROOT(dentry)) { spin_unlock(&dentry->d_lock); return 1; } spin_unlock(&dentry->d_lock); esdfs_get_lower_path(dentry, &lower_path); lower_dentry = lower_path.dentry; esdfs_get_lower_parent(dentry, lower_dentry, &lower_parent_dentry); parent_dentry = dget_parent(dentry); esdfs_get_lower_path(parent_dentry, &lower_parent_path); if (lower_parent_path.dentry != lower_parent_dentry) goto drop; /* can't do strcmp if lower is hashed */ spin_lock(&lower_dentry->d_lock); if (d_unhashed(lower_dentry)) { spin_unlock(&lower_dentry->d_lock); goto drop; } spin_lock(&dentry->d_lock); if (lower_dentry->d_name.len != dentry->d_name.len || strncasecmp(lower_dentry->d_name.name, dentry->d_name.name, dentry->d_name.len) != 0) { err = 0; __d_drop(dentry); /* already holding spin lock */ } spin_unlock(&dentry->d_lock); spin_unlock(&lower_dentry->d_lock); esdfs_revalidate_perms(dentry); goto out; drop: d_drop(dentry); err = 0; out: esdfs_put_lower_path(parent_dentry, &lower_parent_path); dput(parent_dentry); esdfs_put_lower_parent(dentry, &lower_parent_dentry); esdfs_put_lower_path(dentry, &lower_path); return err; }
static int esdfs_setattr(struct dentry *dentry, struct iattr *ia) { int err = 0; struct dentry *lower_dentry; struct inode *inode; struct inode *lower_inode; struct path lower_path; struct iattr lower_ia; const struct cred *creds; /* We don't allow chmod or chown, so skip those */ ia->ia_valid &= ~(ATTR_UID | ATTR_GID | ATTR_MODE); if (!ia->ia_valid) return 0; inode = dentry->d_inode; /* * Check if user has permission to change inode. We don't check if * this user can change the lower inode: that should happen when * calling notify_change on the lower inode. */ err = inode_change_ok(inode, ia); if (err) return err; creds = esdfs_override_creds(ESDFS_SB(dentry->d_inode->i_sb), NULL); if (!creds) return -ENOMEM; esdfs_get_lower_path(dentry, &lower_path); lower_dentry = lower_path.dentry; lower_inode = esdfs_lower_inode(inode); /* prepare our own lower struct iattr (with the lower file) */ memcpy(&lower_ia, ia, sizeof(lower_ia)); if (ia->ia_valid & ATTR_FILE) lower_ia.ia_file = esdfs_lower_file(ia->ia_file); /* * If shrinking, first truncate upper level to cancel writing dirty * pages beyond the new eof; and also if its' maxbytes is more * limiting (fail with -EFBIG before making any change to the lower * level). There is no need to vmtruncate the upper level * afterwards in the other cases: we fsstack_copy_inode_size from * the lower level. */ if (ia->ia_valid & ATTR_SIZE) { err = inode_newsize_ok(inode, ia->ia_size); if (err) goto out; truncate_setsize(inode, ia->ia_size); } /* * mode change is for clearing setuid/setgid bits. Allow lower fs * to interpret this in its own way. */ if (lower_ia.ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID)) lower_ia.ia_valid &= ~ATTR_MODE; /* notify the (possibly copied-up) lower inode */ /* * Note: we use lower_dentry->d_inode, because lower_inode may be * unlinked (no inode->i_sb and i_ino==0. This happens if someone * tries to open(), unlink(), then ftruncate() a file. */ mutex_lock(&lower_dentry->d_inode->i_mutex); err = notify_change(lower_dentry, &lower_ia); /* note: lower_ia */ mutex_unlock(&lower_dentry->d_inode->i_mutex); if (err) goto out; /* get attributes from the lower inode */ esdfs_copy_attr(inode, lower_inode); /* * Not running fsstack_copy_inode_size(inode, lower_inode), because * VFS should update our inode size, and notify_change on * lower_inode should update its size. */ out: esdfs_put_lower_path(dentry, &lower_path); esdfs_revert_creds(creds, NULL); return err; }
/* * The locking rules in esdfs_rename are complex. We could use a simpler * superblock-level name-space lock for renames and copy-ups. */ static int esdfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) { int err = 0; struct dentry *lower_old_dentry = NULL; struct dentry *lower_new_dentry = NULL; struct dentry *lower_old_dir_dentry = NULL; struct dentry *lower_new_dir_dentry = NULL; struct dentry *trap = NULL; struct path lower_old_path, lower_new_path; int mask; const struct cred *creds = esdfs_override_creds(ESDFS_SB(old_dir->i_sb), &mask); if (!creds) return -ENOMEM; /* Never rename to or from a pseudo hard link target. */ if (ESDFS_DENTRY_HAS_STUB(old_dentry)) esdfs_get_lower_stub_path(old_dentry, &lower_old_path); else esdfs_get_lower_path(old_dentry, &lower_old_path); if (ESDFS_DENTRY_HAS_STUB(new_dentry)) esdfs_get_lower_stub_path(new_dentry, &lower_new_path); else esdfs_get_lower_path(new_dentry, &lower_new_path); lower_old_dentry = lower_old_path.dentry; lower_new_dentry = lower_new_path.dentry; esdfs_get_lower_parent(old_dentry, lower_old_dentry, &lower_old_dir_dentry); esdfs_get_lower_parent(new_dentry, lower_new_dentry, &lower_new_dir_dentry); trap = lock_rename(lower_old_dir_dentry, lower_new_dir_dentry); /* source should not be ancestor of target */ if (trap == lower_old_dentry) { err = -EINVAL; goto out; } /* target should not be ancestor of source */ if (trap == lower_new_dentry) { err = -ENOTEMPTY; goto out; } err = mnt_want_write(lower_old_path.mnt); if (err) goto out; err = mnt_want_write(lower_new_path.mnt); if (err) goto out_drop_old_write; err = vfs_rename(lower_old_dir_dentry->d_inode, lower_old_dentry, lower_new_dir_dentry->d_inode, lower_new_dentry); if (err) goto out_err; esdfs_copy_attr(new_dir, lower_new_dir_dentry->d_inode); fsstack_copy_inode_size(new_dir, lower_new_dir_dentry->d_inode); if (new_dir != old_dir) { esdfs_copy_attr(old_dir, lower_old_dir_dentry->d_inode); fsstack_copy_inode_size(old_dir, lower_old_dir_dentry->d_inode); } /* Drop any old links */ if (ESDFS_DENTRY_HAS_STUB(old_dentry)) d_drop(old_dentry); if (ESDFS_DENTRY_HAS_STUB(new_dentry)) d_drop(new_dentry); out_err: mnt_drop_write(lower_new_path.mnt); out_drop_old_write: mnt_drop_write(lower_old_path.mnt); out: unlock_rename(lower_old_dir_dentry, lower_new_dir_dentry); esdfs_put_lower_parent(old_dentry, &lower_old_dir_dentry); esdfs_put_lower_parent(new_dentry, &lower_new_dir_dentry); esdfs_put_lower_path(old_dentry, &lower_old_path); esdfs_put_lower_path(new_dentry, &lower_new_path); esdfs_revert_creds(creds, &mask); return err; }