ssize_t unionfs_read(struct file * file, char *buf, size_t count, loff_t * ppos) { int err = -EINVAL; struct file *hidden_file = NULL; loff_t pos = *ppos; print_entry_location(); if ((err = unionfs_file_revalidate(file, 0))) goto out; fist_print_file("entering read()", file); PASSERT(ftopd(file)); hidden_file = ftohf(file); PASSERT(hidden_file); if (!hidden_file->f_op || !hidden_file->f_op->read) goto out; err = hidden_file->f_op->read(hidden_file, buf, count, &pos); *ppos = pos; if (err >= 0) { /* atime should also be updated for reads of size zero or more */ fist_copy_attr_atime(file->f_dentry->d_inode, hidden_file->f_dentry->d_inode); } memcpy(&(file->f_ra), &(hidden_file->f_ra), sizeof(struct file_ra_state)); out: fist_print_file("leaving read()", file); print_exit_status(err); return err; }
static int unionfs_fsync(struct file *file, struct dentry *dentry, int datasync) { int err; struct file *hidden_file = NULL; print_entry_location(); if ((err = unionfs_file_revalidate(file, 1))) goto out; PASSERT(ftopd(file)); hidden_file = ftohf(file); err = -EINVAL; if (!hidden_file->f_op || !hidden_file->f_op->fsync) goto out; down(&hidden_file->f_dentry->d_inode->i_sem); err = hidden_file->f_op->fsync(hidden_file, hidden_file->f_dentry, datasync); up(&hidden_file->f_dentry->d_inode->i_sem); out: print_exit_status(err); return err; }
static ssize_t unionfs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { int err = 0; struct file *lower_file; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); err = unionfs_file_revalidate(file, parent, true); if (unlikely(err)) goto out; lower_file = unionfs_lower_file(file); err = vfs_write(lower_file, buf, count, ppos); /* update our inode times+sizes upon a successful lower write */ if (err >= 0) { fsstack_copy_inode_size(dentry->d_inode, lower_file->f_path.dentry->d_inode); fsstack_copy_attr_times(dentry->d_inode, lower_file->f_path.dentry->d_inode); UNIONFS_F(file)->wrote_to_file = true; /* for delayed copyup */ unionfs_check_file(file); } out: unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
/* FIST-LITE special version of mmap */ static int unionfs_mmap(struct file *file, struct vm_area_struct *vma) { int err = 0; struct file *hidden_file = NULL; int willwrite; print_entry_location(); /* This might could be deferred to mmap's writepage. */ willwrite = ((vma->vm_flags | VM_SHARED | VM_WRITE) == vma->vm_flags); if ((err = unionfs_file_revalidate(file, willwrite))) goto out; PASSERT(ftopd(file)); hidden_file = ftohf(file); err = -ENODEV; if (!hidden_file->f_op || !hidden_file->f_op->mmap) goto out; PASSERT(hidden_file); PASSERT(hidden_file->f_op); PASSERT(hidden_file->f_op->mmap); vma->vm_file = hidden_file; err = hidden_file->f_op->mmap(hidden_file, vma); get_file(hidden_file); /* make sure it doesn't get freed on us */ fput(file); /* no need to keep extra ref on ours */ out: print_exit_status(err); return err; }
static unsigned int unionfs_poll(struct file *file, poll_table * wait) { unsigned int mask = DEFAULT_POLLMASK; struct file *hidden_file = NULL; print_entry_location(); if (unionfs_file_revalidate(file, 0)) { /* We should pretend an error happend. */ mask = POLLERR | POLLIN | POLLOUT; goto out; } if (ftopd(file) != NULL) hidden_file = ftohf(file); if (!hidden_file->f_op || !hidden_file->f_op->poll) goto out; mask = hidden_file->f_op->poll(hidden_file, wait); out: print_exit_status(mask); return mask; }
static ssize_t unionfs_splice_write(struct pipe_inode_info *pipe, struct file *file, loff_t *ppos, size_t len, unsigned int flags) { ssize_t err = 0; struct file *lower_file; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); err = unionfs_file_revalidate(file, parent, true); if (unlikely(err)) goto out; lower_file = unionfs_lower_file(file); err = vfs_splice_from(pipe, lower_file, ppos, len, flags); /* update our inode times+sizes upon a successful lower write */ if (err >= 0) { fsstack_copy_inode_size(dentry->d_inode, lower_file->f_path.dentry->d_inode); fsstack_copy_attr_times(dentry->d_inode, lower_file->f_path.dentry->d_inode); unionfs_check_file(file); } out: unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
static ssize_t unionfs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { int err; struct file *lower_file; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); err = unionfs_file_revalidate(file, parent, false); if (unlikely(err)) goto out; lower_file = unionfs_lower_file(file); err = vfs_read(lower_file, buf, count, ppos); /* update our inode atime upon a successful lower read */ if (err >= 0) { fsstack_copy_attr_atime(dentry->d_inode, lower_file->f_path.dentry->d_inode); unionfs_check_file(file); } out: unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
int unionfs_fsync(struct file *file, struct dentry *dentry, int datasync) { int bindex, bstart, bend; struct file *lower_file; struct dentry *lower_dentry; struct dentry *parent; struct inode *lower_inode, *inode; int err = -EINVAL; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); err = unionfs_file_revalidate(file, parent, true); if (unlikely(err)) goto out; unionfs_check_file(file); bstart = fbstart(file); bend = fbend(file); if (bstart < 0 || bend < 0) goto out; inode = dentry->d_inode; if (unlikely(!inode)) { printk(KERN_ERR "unionfs: null lower inode in unionfs_fsync\n"); goto out; } for (bindex = bstart; bindex <= bend; bindex++) { lower_inode = unionfs_lower_inode_idx(inode, bindex); if (!lower_inode || !lower_inode->i_fop->fsync) continue; lower_file = unionfs_lower_file_idx(file, bindex); lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); mutex_lock(&lower_inode->i_mutex); err = lower_inode->i_fop->fsync(lower_file, lower_dentry, datasync); if (!err && bindex == bstart) fsstack_copy_attr_times(inode, lower_inode); mutex_unlock(&lower_inode->i_mutex); if (err) goto out; } out: if (!err) unionfs_check_file(file); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
/* this unionfs_write() does not modify data pages! */ ssize_t unionfs_write(struct file * file, const char *buf, size_t count, loff_t * ppos) { int err = -EINVAL; struct file *hidden_file = NULL; struct inode *inode; struct inode *hidden_inode; loff_t pos = *ppos; int bstart, bend; print_entry_location(); if ((err = unionfs_file_revalidate(file, 1))) goto out; inode = file->f_dentry->d_inode; bstart = fbstart(file); bend = fbend(file); ASSERT(bstart != -1); PASSERT(ftopd(file)); PASSERT(ftohf(file)); hidden_file = ftohf(file); hidden_inode = hidden_file->f_dentry->d_inode; if (!hidden_file->f_op || !hidden_file->f_op->write) goto out; /* adjust for append -- seek to the end of the file */ if (file->f_flags & O_APPEND) pos = inode->i_size; err = hidden_file->f_op->write(hidden_file, buf, count, &pos); /* * copy ctime and mtime from lower layer attributes * atime is unchanged for both layers */ if (err >= 0) fist_copy_attr_times(inode, hidden_inode); *ppos = pos; /* update this inode's size */ if (pos > inode->i_size) inode->i_size = pos; out: print_exit_status(err); return err; }
static int unionfs_fasync(int fd, struct file *file, int flag) { int err = 0; struct file *hidden_file = NULL; print_entry_location(); if ((err = unionfs_file_revalidate(file, 1))) goto out; hidden_file = ftohf(file); if (hidden_file->f_op && hidden_file->f_op->fasync) err = hidden_file->f_op->fasync(fd, hidden_file, flag); out: print_exit_status(err); return err; }
long unionfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { long err; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); err = unionfs_file_revalidate(file, parent, true); if (unlikely(err)) goto out; /* check if asked for local commands */ switch (cmd) { case UNIONFS_IOCTL_INCGEN: /* Increment the superblock generation count */ pr_info("unionfs: incgen ioctl deprecated; " "use \"-o remount,incgen\"\n"); err = -ENOSYS; break; case UNIONFS_IOCTL_QUERYFILE: /* Return list of branches containing the given file */ err = unionfs_ioctl_queryfile(file, parent, cmd, arg); break; default: /* pass the ioctl down */ err = do_ioctl(file, cmd, arg); break; } out: unionfs_check_file(file); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
int unionfs_flush(struct file *file, fl_owner_t id) { int err = 0; struct file *lower_file = NULL; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; int bindex, bstart, bend; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); err = unionfs_file_revalidate(file, parent, UNIONFS_F(file)->wrote_to_file); if (unlikely(err)) goto out; unionfs_check_file(file); bstart = fbstart(file); bend = fbend(file); for (bindex = bstart; bindex <= bend; bindex++) { lower_file = unionfs_lower_file_idx(file, bindex); if (lower_file && lower_file->f_op && lower_file->f_op->flush) { err = lower_file->f_op->flush(lower_file, id); if (err) goto out; } } out: if (!err) unionfs_check_file(file); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
static loff_t unionfs_llseek(struct file *file, loff_t offset, int origin) { loff_t err; struct file *hidden_file = NULL; print_entry_location(); fist_dprint(6, "unionfs_llseek: file=%p, offset=0x%llx, origin=%d\n", file, offset, origin); if ((err = unionfs_file_revalidate(file, 0))) goto out; PASSERT(ftopd(file)); hidden_file = ftohf(file); PASSERT(hidden_file); /* always set hidden position to this one */ hidden_file->f_pos = file->f_pos; memcpy(&(hidden_file->f_ra), &(file->f_ra), sizeof(struct file_ra_state)); if (hidden_file->f_op && hidden_file->f_op->llseek) err = hidden_file->f_op->llseek(hidden_file, offset, origin); else err = generic_file_llseek(hidden_file, offset, origin); if (err < 0) goto out; if (err != file->f_pos) { file->f_pos = err; // ION maybe this? // file->f_pos = hidden_file->f_pos; file->f_version++; } out: print_exit_status((int)err); return err; }
static ssize_t unionfs_sendfile(struct file *file, loff_t * ppos, size_t count, read_actor_t actor, void *target) { ssize_t err; struct file *hidden_file = NULL; print_entry_location(); if ((err = unionfs_file_revalidate(file, 0))) goto out; hidden_file = ftohf(file); err = -EINVAL; if (!hidden_file->f_op || !hidden_file->f_op->sendfile) goto out; err = hidden_file->f_op->sendfile(hidden_file, ppos, count, actor, target); out: print_exit_status(err); return err; }
static int unionfs_readdir(struct file *file, void *dirent, filldir_t filldir) { int err = 0; struct file *lower_file = NULL; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; struct inode *inode = NULL; struct unionfs_getdents_callback buf; struct unionfs_dir_state *uds; int bend; loff_t offset; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); err = unionfs_file_revalidate(file, parent, false); if (unlikely(err)) goto out; inode = dentry->d_inode; uds = UNIONFS_F(file)->rdstate; if (!uds) { if (file->f_pos == DIREOF) { goto out; } else if (file->f_pos > 0) { uds = find_rdstate(inode, file->f_pos); if (unlikely(!uds)) { err = -ESTALE; goto out; } UNIONFS_F(file)->rdstate = uds; } else { init_rdstate(file); uds = UNIONFS_F(file)->rdstate; } } bend = fbend(file); while (uds->bindex <= bend) { lower_file = unionfs_lower_file_idx(file, uds->bindex); if (!lower_file) { uds->bindex++; uds->dirpos = 0; continue; } /* prepare callback buffer */ buf.filldir_called = 0; buf.filldir_error = 0; buf.entries_written = 0; buf.dirent = dirent; buf.filldir = filldir; buf.rdstate = uds; buf.sb = inode->i_sb; /* Read starting from where we last left off. */ offset = vfs_llseek(lower_file, uds->dirpos, SEEK_SET); if (offset < 0) { err = offset; goto out; } err = vfs_readdir(lower_file, unionfs_filldir, &buf); /* Save the position for when we continue. */ offset = vfs_llseek(lower_file, 0, SEEK_CUR); if (offset < 0) { err = offset; goto out; } uds->dirpos = offset; /* Copy the atime. */ fsstack_copy_attr_atime(inode, lower_file->f_path.dentry->d_inode); if (err < 0) goto out; if (buf.filldir_error) break; if (!buf.entries_written) { uds->bindex++; uds->dirpos = 0; } } if (!buf.filldir_error && uds->bindex >= bend) { /* Save the number of hash entries for next time. */ UNIONFS_I(inode)->hashsize = uds->hashentries; free_rdstate(uds); UNIONFS_F(file)->rdstate = NULL; file->f_pos = DIREOF; } else { file->f_pos = rdstate2offset(uds); } out: if (!err) unionfs_check_file(file); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
/* * This is not meant to be a generic repositioning function. If you do * things that aren't supported, then we return EINVAL. * * What is allowed: * (1) seeking to the same position that you are currently at * This really has no effect, but returns where you are. * (2) seeking to the beginning of the file * This throws out all state, and lets you begin again. */ static loff_t unionfs_dir_llseek(struct file *file, loff_t offset, int origin) { struct unionfs_dir_state *rdstate; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; loff_t err; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); err = unionfs_file_revalidate(file, parent, false); if (unlikely(err)) goto out; rdstate = UNIONFS_F(file)->rdstate; /* * we let users seek to their current position, but not anywhere * else. */ if (!offset) { switch (origin) { case SEEK_SET: if (rdstate) { free_rdstate(rdstate); UNIONFS_F(file)->rdstate = NULL; } init_rdstate(file); err = 0; break; case SEEK_CUR: err = file->f_pos; break; case SEEK_END: /* Unsupported, because we would break everything. */ err = -EINVAL; break; } } else { switch (origin) { case SEEK_SET: if (rdstate) { if (offset == rdstate2offset(rdstate)) err = offset; else if (file->f_pos == DIREOF) err = DIREOF; else err = -EINVAL; } else { struct inode *inode; inode = dentry->d_inode; rdstate = find_rdstate(inode, offset); if (rdstate) { UNIONFS_F(file)->rdstate = rdstate; err = rdstate->offset; } else { err = -EINVAL; } } break; case SEEK_CUR: case SEEK_END: /* Unsupported, because we would break everything. */ err = -EINVAL; break; } } out: if (!err) unionfs_check_file(file); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
/* * release all lower object references & free the file info structure * * No need to grab sb info's rwsem. */ int unionfs_file_release(struct inode *inode, struct file *file) { struct file *lower_file = NULL; struct unionfs_file_info *fileinfo; struct unionfs_inode_info *inodeinfo; struct super_block *sb = inode->i_sb; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; int bindex, bstart, bend; int fgen, err = 0; /* * Since mm/memory.c:might_fault() (under PROVE_LOCKING) was * modified in 2.6.29-rc1 to call might_lock_read on mmap_sem, this * has been causing false positives in file system stacking layers. * In particular, our ->mmap is called after sys_mmap2 already holds * mmap_sem, then we lock our own mutexes; but earlier, it's * possible for lockdep to have locked our mutexes first, and then * we call a lower ->readdir which could call might_fault. The * different ordering of the locks is what lockdep complains about * -- unnecessarily. Therefore, we have no choice but to tell * lockdep to temporarily turn off lockdep here. Note: the comments * inside might_sleep also suggest that it would have been * nicer to only annotate paths that needs that might_lock_read. */ lockdep_off(); unionfs_read_lock(sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); /* * We try to revalidate, but the VFS ignores return return values * from file->release, so we must always try to succeed here, * including to do the kfree and dput below. So if revalidation * failed, all we can do is print some message and keep going. */ err = unionfs_file_revalidate(file, parent, UNIONFS_F(file)->wrote_to_file); if (!err) unionfs_check_file(file); fileinfo = UNIONFS_F(file); BUG_ON(file->f_path.dentry->d_inode != inode); inodeinfo = UNIONFS_I(inode); /* fput all the lower files */ fgen = atomic_read(&fileinfo->generation); bstart = fbstart(file); bend = fbend(file); for (bindex = bstart; bindex <= bend; bindex++) { lower_file = unionfs_lower_file_idx(file, bindex); if (lower_file) { unionfs_set_lower_file_idx(file, bindex, NULL); fput(lower_file); branchput(sb, bindex); } /* if there are no more refs to the dentry, dput it */ if (d_deleted(dentry)) { dput(unionfs_lower_dentry_idx(dentry, bindex)); unionfs_set_lower_dentry_idx(dentry, bindex, NULL); } } kfree(fileinfo->lower_files); kfree(fileinfo->saved_branch_ids); if (fileinfo->rdstate) { fileinfo->rdstate->access = jiffies; spin_lock(&inodeinfo->rdlock); inodeinfo->rdcount++; list_add_tail(&fileinfo->rdstate->cache, &inodeinfo->readdircache); mark_inode_dirty(inode); spin_unlock(&inodeinfo->rdlock); fileinfo->rdstate = NULL; } kfree(fileinfo); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(sb); lockdep_on(); return err; }
static int unionfs_mmap(struct file *file, struct vm_area_struct *vma) { int err = 0; bool willwrite; struct file *lower_file; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; struct vm_operations_struct *saved_vm_ops = NULL; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); /* This might be deferred to mmap's writepage */ willwrite = ((vma->vm_flags | VM_SHARED | VM_WRITE) == vma->vm_flags); err = unionfs_file_revalidate(file, parent, willwrite); if (unlikely(err)) goto out; unionfs_check_file(file); /* * File systems which do not implement ->writepage may use * generic_file_readonly_mmap as their ->mmap op. If you call * generic_file_readonly_mmap with VM_WRITE, you'd get an -EINVAL. * But we cannot call the lower ->mmap op, so we can't tell that * writeable mappings won't work. Therefore, our only choice is to * check if the lower file system supports the ->writepage, and if * not, return EINVAL (the same error that * generic_file_readonly_mmap returns in that case). */ lower_file = unionfs_lower_file(file); if (willwrite && !lower_file->f_mapping->a_ops->writepage) { err = -EINVAL; printk(KERN_ERR "unionfs: branch %d file system does not " "support writeable mmap\n", fbstart(file)); goto out; } /* * find and save lower vm_ops. * * XXX: the VFS should have a cleaner way of finding the lower vm_ops */ if (!UNIONFS_F(file)->lower_vm_ops) { err = lower_file->f_op->mmap(lower_file, vma); if (err) { printk(KERN_ERR "unionfs: lower mmap failed %d\n", err); goto out; } saved_vm_ops = vma->vm_ops; err = do_munmap(current->mm, vma->vm_start, vma->vm_end - vma->vm_start); if (err) { printk(KERN_ERR "unionfs: do_munmap failed %d\n", err); goto out; } } file->f_mapping->a_ops = &unionfs_dummy_aops; err = generic_file_mmap(file, vma); file->f_mapping->a_ops = &unionfs_aops; if (err) { printk(KERN_ERR "unionfs: generic_file_mmap failed %d\n", err); goto out; } vma->vm_ops = &unionfs_vm_ops; if (!UNIONFS_F(file)->lower_vm_ops) UNIONFS_F(file)->lower_vm_ops = saved_vm_ops; out: if (!err) { /* copyup could cause parent dir times to change */ unionfs_copy_attr_times(parent->d_inode); unionfs_check_file(file); } unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }