/* * 2007+ Copyright (c) Evgeniy Polyakov * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include "netfs.h" static int pohmelfs_cmp_hash(struct pohmelfs_name *n, u32 hash) { if (n->hash > hash) return -1; if (n->hash < hash) return 1; return 0; } static struct pohmelfs_name *pohmelfs_search_hash_unprecise(struct pohmelfs_inode *pi, u32 hash) { struct rb_node *n = pi->hash_root.rb_node; struct pohmelfs_name *tmp = NULL; int cmp; while (n) { tmp = rb_entry(n, struct pohmelfs_name, hash_node); cmp = pohmelfs_cmp_hash(tmp, hash); if (cmp < 0) n = n->rb_left; else if (cmp > 0) n = n->rb_right; else break; } return tmp; } struct pohmelfs_name *pohmelfs_search_hash(struct pohmelfs_inode *pi, u32 hash) { struct pohmelfs_name *tmp; tmp = pohmelfs_search_hash_unprecise(pi, hash); if (tmp && (tmp->hash == hash)) return tmp; return NULL; } static void __pohmelfs_name_del(struct pohmelfs_inode *parent, struct pohmelfs_name *node) { rb_erase(&node->hash_node, &parent->hash_root); } /* * Remove name cache entry from its caches and free it. */ static void pohmelfs_name_free(struct pohmelfs_inode *parent, struct pohmelfs_name *node) { __pohmelfs_name_del(parent, node); list_del(&node->sync_create_entry); kfree(node); } static struct pohmelfs_name *pohmelfs_insert_hash(struct pohmelfs_inode *pi, struct pohmelfs_name *new) { struct rb_node **n = &pi->hash_root.rb_node, *parent = NULL; struct pohmelfs_name *ret = NULL, *tmp; int cmp; while (*n) { parent = *n; tmp = rb_entry(parent, struct pohmelfs_name, hash_node); cmp = pohmelfs_cmp_hash(tmp, new->hash); if (cmp < 0) n = &parent->rb_left; else if (cmp > 0) n = &parent->rb_right; else { ret = tmp; break; } } if (ret) { printk("%s: exist: parent: %llu, ino: %llu, hash: %x, len: %u, data: '%s', " "new: ino: %llu, hash: %x, len: %u, data: '%s'.\n", __func__, pi->ino, ret->ino, ret->hash, ret->len, ret->data, new->ino, new->hash, new->len, new->data); ret->ino = new->ino; return ret; } rb_link_node(&new->hash_node, parent, n); rb_insert_color(&new->hash_node, &pi->hash_root); return NULL; } /* * Free name cache for given inode. */ void pohmelfs_free_names(struct pohmelfs_inode *parent) { struct rb_node *rb_node; struct pohmelfs_name *n; for (rb_node = rb_first(&parent->hash_root); rb_node;) { n = rb_entry(rb_node, struct pohmelfs_name, hash_node); rb_node = rb_next(rb_node); pohmelfs_name_free(parent, n); } } static void pohmelfs_fix_offset(struct pohmelfs_inode *parent, struct pohmelfs_name *node) { parent->total_len -= node->len; } /* * Free name cache entry helper. */ void pohmelfs_name_del(struct pohmelfs_inode *parent, struct pohmelfs_name *node) { pohmelfs_fix_offset(parent, node); pohmelfs_name_free(parent, node); } /* * Insert new name cache entry into all hash cache. */ static int pohmelfs_insert_name(struct pohmelfs_inode *parent, struct pohmelfs_name *n) { struct pohmelfs_name *name; name = pohmelfs_insert_hash(parent, n); if (name) return -EEXIST; parent->total_len += n->len; list_add_tail(&n->sync_create_entry, &parent->sync_create_list); return 0; } /* * Allocate new name cache entry. */ static struct pohmelfs_name *pohmelfs_name_alloc(unsigned int len) { struct pohmelfs_name *n; n = kzalloc(sizeof(struct pohmelfs_name) + len, GFP_KERNEL); if (!n) return NULL; INIT_LIST_HEAD(&n->sync_create_entry); n->data = (char *)(n+1); return n; } /* * Add new name entry into directory's cache. */ static int pohmelfs_add_dir(struct pohmelfs_sb *psb, struct pohmelfs_inode *parent, struct pohmelfs_inode *npi, struct qstr *str, unsigned int mode, int link) { int err = -ENOMEM; struct pohmelfs_name *n; n = pohmelfs_name_alloc(str->len + 1); if (!n) goto err_out_exit; n->ino = npi->ino; n->mode = mode; n->len = str->len; n->hash = str->hash; sprintf(n->data, "%s", str->name); mutex_lock(&parent->offset_lock); err = pohmelfs_insert_name(parent, n); mutex_unlock(&parent->offset_lock); if (err) { if (err != -EEXIST) goto err_out_free; kfree(n); } return 0; err_out_free: kfree(n); err_out_exit: return err; } /* * Create new inode for given parameters (name, inode info, parent). * This does not create object on the server, it will be synced there during writeback. */ struct pohmelfs_inode *pohmelfs_new_inode(struct pohmelfs_sb *psb, struct pohmelfs_inode *parent, struct qstr *str, struct netfs_inode_info *info, int link) { struct inode *new = NULL; struct pohmelfs_inode *npi; int err = -EEXIST; dprintk("%s: creating inode: parent: %llu, ino: %llu, str: %p.\n", __func__, (parent) ? parent->ino : 0, info->ino, str); err = -ENOMEM; new = iget_locked(psb->sb, info->ino); if (!new) goto err_out_exit; npi = POHMELFS_I(new); npi->ino = info->ino; err = 0; if (new->i_state & I_NEW) { dprintk("%s: filling VFS inode: %lu/%llu.\n", __func__, new->i_ino, info->ino); pohmelfs_fill_inode(new, info); if (S_ISDIR(info->mode)) { struct qstr s; s.name = "."; s.len = 1; s.hash = jhash(s.name, s.len, 0); err = pohmelfs_add_dir(psb, npi, npi, &s, info->mode, 0); if (err) goto err_out_put; s.name = ".."; s.len = 2; s.hash = jhash(s.name, s.len, 0); err = pohmelfs_add_dir(psb, npi, (parent) ? parent : npi, &s, (parent) ? parent->vfs_inode.i_mode : npi->vfs_inode.i_mode, 0); if (err) goto err_out_put; } } if (str) { if (parent) { err = pohmelfs_add_dir(psb, parent, npi, str, info->mode, link); dprintk("%s: %s inserted name: '%s', new_offset: %llu, ino: %llu, parent: %llu.\n", __func__, (err) ? "unsuccessfully" : "successfully", str->name, parent->total_len, info->ino, parent->ino); if (err && err != -EEXIST) goto err_out_put; } } if (new->i_state & I_NEW) { if (parent) mark_inode_dirty(&parent->vfs_inode); mark_inode_dirty(new); } set_bit(NETFS_INODE_OWNED, &npi->state); npi->lock_type = POHMELFS_WRITE_LOCK; unlock_new_inode(new); return npi; err_out_put: printk("%s: putting inode: %p, npi: %p, error: %d.\n", __func__, new, npi, err); iput(new); err_out_exit: return ERR_PTR(err); } static int pohmelfs_remote_sync_complete(struct page **pages, unsigned int page_num, void *private, int err) { struct pohmelfs_inode *pi = private; struct pohmelfs_sb *psb = POHMELFS_SB(pi->vfs_inode.i_sb); dprintk("%s: ino: %llu, err: %d.\n", __func__, pi->ino, err); if (err) pi->error = err; wake_up(&psb->wait); pohmelfs_put_inode(pi); return err; } /* * Receive directory content from the server. * This should be only done for objects, which were not created locally, * and which were not synced previously. */ static int pohmelfs_sync_remote_dir(struct pohmelfs_inode *pi) { struct inode *inode = &pi->vfs_inode; struct pohmelfs_sb *psb = POHMELFS_SB(inode->i_sb); long ret = psb->wait_on_page_timeout; int err; dprintk("%s: dir: %llu, state: %lx: remote_synced: %d.\n", __func__, pi->ino, pi->state, test_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state)); if (test_bit(NETFS_INODE_REMOTE_DIR_SYNCED, &pi->state)) return 0; if (!igrab(inode)) { err = -ENOENT; goto err_out_exit; } err = pohmelfs_meta_command(pi, NETFS_READDIR, NETFS_TRANS_SINGLE_DST, pohmelfs_remote_sync_complete, pi, 0); if (err) goto err_out_exit; pi->error = 0; ret = wait_event_interruptible_timeout(psb->wait, test_bit(NETFS_INODE_REMOTE_DIR_SYNCED, &pi->state) || pi->error, ret); dprintk("%s: awake dir: %llu, ret: %ld, err: %d.\n", __func__, pi->ino, ret, pi->error); if (ret <= 0) { err = ret; if (!err) err = -ETIMEDOUT; goto err_out_exit; } if (pi->error) return pi->error; return 0; err_out_exit: clear_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state); return err; } static int pohmelfs_dir_open(struct inode *inode, struct file *file) { file->private_data = NULL; return 0; } /* * VFS readdir callback. Syncs directory content from server if needed, * and provides direntry info to the userspace. */ static int pohmelfs_readdir(struct file *file, void *dirent, filldir_t filldir) { struct inode *inode = file->f_path.dentry->d_inode; struct pohmelfs_inode *pi = POHMELFS_I(inode); struct pohmelfs_name *n; struct rb_node *rb_node; int err = 0, mode; u64 len; dprintk("%s: parent: %llu, fpos: %llu, hash: %08lx.\n", __func__, pi->ino, (u64)file->f_pos, (unsigned long)file->private_data); #if 0 err = pohmelfs_data_lock(pi, 0, ~0, POHMELFS_READ_LOCK); if (err) return err; #endif err = pohmelfs_sync_remote_dir(pi); if (err) return err; if (file->private_data && (file->private_data == (void *)(unsigned long)file->f_pos)) return 0; mutex_lock(&pi->offset_lock); n = pohmelfs_search_hash_unprecise(pi, (unsigned long)file->private_data); while (n) { mode = (n->mode >> 12) & 15; dprintk("%s: offset: %llu, parent ino: %llu, name: '%s', len: %u, ino: %llu, " "mode: %o/%o, fpos: %llu, hash: %08x.\n", __func__, file->f_pos, pi->ino, n->data, n->len, n->ino, n->mode, mode, file->f_pos, n->hash); file->private_data = (void *)(unsigned long)n->hash; len = n->len; err = filldir(dirent, n->data, n->len, file->f_pos, n->ino, mode); if (err < 0) { dprintk("%s: err: %d.\n", __func__, err); err = 0; break; } file->f_pos += len; rb_node = rb_next(&n->hash_node); if (!rb_node || (rb_node == &n->hash_node)) { file->private_data = (void *)(unsigned long)file->f_pos; break; } n = rb_entry(rb_node, struct pohmelfs_name, hash_node); } mutex_unlock(&pi->offset_lock); return err; } static loff_t pohmelfs_dir_lseek(struct file *file, loff_t offset, int origin) { file->f_pos = offset; file->private_data = NULL; return offset; } const struct file_operations pohmelfs_dir_fops = { .open = pohmelfs_dir_open, .read = generic_read_dir, .llseek = pohmelfs_dir_lseek, .readdir = pohmelfs_readdir, }; /* * Lookup single object on server. */ static int pohmelfs_lookup_single(struct pohmelfs_inode *parent, struct qstr *str, u64 ino) { struct pohmelfs_sb *psb = POHMELFS_SB(parent->vfs_inode.i_sb); long ret = msecs_to_jiffies(5000); int err; set_bit(NETFS_COMMAND_PENDING, &parent->state); err = pohmelfs_meta_command_data(parent, parent->ino, NETFS_LOOKUP, (char *)str->name, NETFS_TRANS_SINGLE_DST, NULL, NULL, ino); if (err) goto err_out_exit; err = 0; ret = wait_event_interruptible_timeout(psb->wait, !test_bit(NETFS_COMMAND_PENDING, &parent->state), ret); if (ret <= 0) { err = ret; if (!err) err = -ETIMEDOUT; } if (err) goto err_out_exit; return 0; err_out_exit: clear_bit(NETFS_COMMAND_PENDING, &parent->state); printk("%s: failed: parent: %llu, ino: %llu, name: '%s', err: %d.\n", __func__, parent->ino, ino, str->name, err); return err; } /* * VFS lookup callback. * We first try to get inode number from local name cache, if we have one, * then inode can be found in inode cache. If there is no inode or no object in * local cache, try to lookup it on server. This only should be done for directories, * which were not created locally, otherwise remote server does not know about dir at all, * so no need to try to know that. */ struct dentry *pohmelfs_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) { struct pohmelfs_inode *parent = POHMELFS_I(dir); struct pohmelfs_name *n; struct inode *inode = NULL; unsigned long ino = 0; int err, lock_type = POHMELFS_READ_LOCK, need_lock = 1; struct qstr str = dentry->d_name; if ((nd->intent.open.flags & O_ACCMODE) != O_RDONLY) lock_type = POHMELFS_WRITE_LOCK; if (test_bit(NETFS_INODE_OWNED, &parent->state)) { if (lock_type == parent->lock_type) need_lock = 0; if ((lock_type == POHMELFS_READ_LOCK) && (parent->lock_type == POHMELFS_WRITE_LOCK)) need_lock = 0; } if ((lock_type == POHMELFS_READ_LOCK) && !test_bit(NETFS_INODE_REMOTE_DIR_SYNCED, &parent->state)) need_lock = 1; str.hash = jhash(dentry->d_name.name, dentry->d_name.len, 0); mutex_lock(&parent->offset_lock); n = pohmelfs_search_hash(parent, str.hash); if (n) ino = n->ino; mutex_unlock(&parent->offset_lock); dprintk("%s: start ino: %lu, inode: %p, name: '%s', hash: %x, parent_state: %lx, need_lock: %d.\n", __func__, ino, inode, str.name, str.hash, parent->state, need_lock); if (ino) { inode = ilookup(dir->i_sb, ino); if (inode) goto out; } dprintk("%s: no inode dir: %p, dir_ino: %llu, name: '%s', len: %u, dir_state: %lx, ino: %lu.\n", __func__, dir, parent->ino, str.name, str.len, parent->state, ino); if (!ino) { if (!need_lock) goto out; } err = pohmelfs_data_lock(parent, 0, ~0, lock_type); if (err) goto out; err = pohmelfs_lookup_single(parent, &str, ino); if (err) goto out; if (!ino) { mutex_lock(&parent->offset_lock); n = pohmelfs_search_hash(parent, str.hash); if (n) ino = n->ino; mutex_unlock(&parent->offset_lock); } if (ino) { inode = ilookup(dir->i_sb, ino); dprintk("%s: second lookup ino: %lu, inode: %p, name: '%s', hash: %x.\n", __func__, ino, inode, str.name, str.hash); if (!inode) { dprintk("%s: No inode for ino: %lu, name: '%s', hash: %x.\n", __func__, ino, str.name, str.hash); /* return NULL; */ return ERR_PTR(-EACCES); } } else { printk("%s: No inode number : name: '%s', hash: %x.\n", __func__, str.name, str.hash); } out: return d_splice_alias(inode, dentry); } /* * Create new object in local cache. Object will be synced to server * during writeback for given inode. */ struct pohmelfs_inode *pohmelfs_create_entry_local(struct pohmelfs_sb *psb, struct pohmelfs_inode *parent, struct qstr *str, u64 start, umode_t mode) { struct pohmelfs_inode *npi; int err = -ENOMEM; struct netfs_inode_info info; dprintk("%s: name: '%s', mode: %ho, start: %llu.\n", __func__, str->name, mode, start); info.mode = mode; info.ino = start; if (!start) info.ino = pohmelfs_new_ino(psb); info.nlink = S_ISDIR(mode) ? 2 : 1; info.uid = current_fsuid(); info.gid = current_fsgid(); info.size = 0; info.blocksize = 512; info.blocks = 0; info.rdev = 0; info.version = 0; npi = pohmelfs_new_inode(psb, parent, str, &info, !!start); if (IS_ERR(npi)) { err = PTR_ERR(npi); goto err_out_unlock; } return npi; err_out_unlock: dprintk("%s: err: %d.\n", __func__, err); return ERR_PTR(err); } /* * Create local object and bind it to dentry. */ static int pohmelfs_create_entry(struct inode *dir, struct dentry *dentry, u64 start, umode_t mode) { struct pohmelfs_sb *psb = POHMELFS_SB(dir->i_sb); struct pohmelfs_inode *npi, *parent; struct qstr str = dentry->d_name; int err; parent = POHMELFS_I(dir); err = pohmelfs_data_lock(parent, 0, ~0, POHMELFS_WRITE_LOCK); if (err) return err; str.hash = jhash(dentry->d_name.name, dentry->d_name.len, 0); npi = pohmelfs_create_entry_local(psb, parent, &str, start, mode); if (IS_ERR(npi)) return PTR_ERR(npi); d_instantiate(dentry, &npi->vfs_inode); dprintk("%s: parent: %llu, inode: %llu, name: '%s', parent_nlink: %d, nlink: %d.\n", __func__, parent->ino, npi->ino, dentry->d_name.name, (signed)dir->i_nlink, (signed)npi->vfs_inode.i_nlink); return 0; } /* * VFS create and mkdir callbacks. */ static int pohmelfs_create(struct inode *dir, struct dentry *dentry, umode_t mode, struct nameidata *nd) { return pohmelfs_create_entry(dir, dentry, 0, mode); } static int pohmelfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) { int err; inode_inc_link_count(dir); err = pohmelfs_create_entry(dir, dentry, 0, mode | S_IFDIR); if (err) inode_dec_link_count(dir); return err; } static int pohmelfs_remove_entry(struct inode *dir, struct dentry *dentry) { struct pohmelfs_sb *psb = POHMELFS_SB(dir->i_sb); struct inode *inode = dentry->d_inode; struct pohmelfs_inode *parent = POHMELFS_I(dir), *pi = POHMELFS_I(inode); struct pohmelfs_name *n; int err = -ENOENT; struct qstr str = dentry->d_name; err = pohmelfs_data_lock(parent, 0, ~0, POHMELFS_WRITE_LOCK); if (err) return err; str.hash = jhash(dentry->d_name.name, dentry->d_name.len, 0); dprintk("%s: dir_ino: %llu, inode: %llu, name: '%s', nlink: %d.\n", __func__, parent->ino, pi->ino, str.name, (signed)inode->i_nlink); BUG_ON(!inode); mutex_lock(&parent->offset_lock); n = pohmelfs_search_hash(parent, str.hash); if (n) { pohmelfs_fix_offset(parent, n); if (test_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state)) pohmelfs_remove_child(pi, n); pohmelfs_name_free(parent, n); err = 0; } mutex_unlock(&parent->offset_lock); if (!err) { psb->avail_size += inode->i_size; pohmelfs_inode_del_inode(psb, pi); mark_inode_dirty(dir); inode->i_ctime = dir->i_ctime; if (inode->i_nlink) inode_dec_link_count(inode); } return err; } /* * Unlink and rmdir VFS callbacks. */ static int pohmelfs_unlink(struct inode *dir, struct dentry *dentry) { return pohmelfs_remove_entry(dir, dentry); } static int pohmelfs_rmdir(struct inode *dir, struct dentry *dentry) { int err; struct inode *inode = dentry->d_inode; dprintk("%s: parent: %llu, inode: %llu, name: '%s', parent_nlink: %d, nlink: %d.\n", __func__, POHMELFS_I(dir)->ino, POHMELFS_I(inode)->ino, dentry->d_name.name, (signed)dir->i_nlink, (signed)inode->i_nlink); err = pohmelfs_remove_entry(dir, dentry); if (!err) { inode_dec_link_count(dir); inode_dec_link_count(inode); } return err; } /* * Link creation is synchronous. * I'm lazy. * Earth is somewhat round. */ static int pohmelfs_create_link(struct pohmelfs_inode *parent, struct qstr *obj, struct pohmelfs_inode *target, struct qstr *tstr) { struct super_block *sb = parent->vfs_inode.i_sb; struct pohmelfs_sb *psb = POHMELFS_SB(sb); struct netfs_cmd *cmd; struct netfs_trans *t; void *data; int err, parent_len, target_len = 0, cur_len, path_size = 0; err = pohmelfs_data_lock(parent, 0, ~0, POHMELFS_WRITE_LOCK); if (err) return err; err = sb->s_op->write_inode(&parent->vfs_inode, 0); if (err) goto err_out_exit; if (tstr) target_len = tstr->len; parent_len = pohmelfs_path_length(parent); if (target) target_len += pohmelfs_path_length(target); if (parent_len < 0) { err = parent_len; goto err_out_exit; } if (target_len < 0) { err = target_len; goto err_out_exit; } t = netfs_trans_alloc(psb, parent_len + target_len + obj->len + 2, 0, 0); if (!t) { err = -ENOMEM; goto err_out_exit; } cur_len = netfs_trans_cur_len(t); cmd = netfs_trans_current(t); if (IS_ERR(cmd)) { err = PTR_ERR(cmd); goto err_out_free; } data = (void *)(cmd + 1); cur_len -= sizeof(struct netfs_cmd); err = pohmelfs_construct_path_string(parent, data, parent_len); if (err > 0) { /* Do not place null-byte before the slash */ path_size = err - 1; cur_len -= path_size; err = snprintf(data + path_size, cur_len, "/%s|", obj->name); path_size += err; cur_len -= err; cmd->ext = path_size - 1; /* No | symbol */ if (target) { err = pohmelfs_construct_path_string(target, data + path_size, target_len); if (err > 0) { path_size += err; cur_len -= err; } } } if (err < 0) goto err_out_free; cmd->start = 0; if (!target && tstr) { if (tstr->len > cur_len - 1) { err = -ENAMETOOLONG; goto err_out_free; } err = snprintf(data + path_size, cur_len, "%s", tstr->name) + 1; /* 0-byte */ path_size += err; cur_len -= err; cmd->start = 1; } dprintk("%s: parent: %llu, obj: '%s', target_inode: %llu, target_str: '%s', full: '%s'.\n", __func__, parent->ino, obj->name, (target) ? target->ino : 0, (tstr) ? tstr->name : NULL, (char *)data); cmd->cmd = NETFS_LINK; cmd->size = path_size; cmd->id = parent->ino; netfs_convert_cmd(cmd); netfs_trans_update(cmd, t, path_size); err = netfs_trans_finish(t, psb); if (err) goto err_out_exit; return 0; err_out_free: t->result = err; netfs_trans_put(t); err_out_exit: return err; } /* * VFS hard and soft link callbacks. */ static int pohmelfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry) { struct inode *inode = old_dentry->d_inode; struct pohmelfs_inode *pi = POHMELFS_I(inode); int err; struct qstr str = dentry->d_name; str.hash = jhash(dentry->d_name.name, dentry->d_name.len, 0); err = inode->i_sb->s_op->write_inode(inode, 0); if (err) return err; err = pohmelfs_create_link(POHMELFS_I(dir), &str, pi, NULL); if (err) return err; return pohmelfs_create_entry(dir, dentry, pi->ino, inode->i_mode); } static int pohmelfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname) { struct qstr sym_str; struct qstr str = dentry->d_name; struct inode *inode; int err; str.hash = jhash(dentry->d_name.name, dentry->d_name.len, 0); sym_str.name = symname; sym_str.len = strlen(symname); err = pohmelfs_create_link(POHMELFS_I(dir), &str, NULL, &sym_str); if (err) goto err_out_exit; err = pohmelfs_create_entry(dir, dentry, 0, S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO); if (err) goto err_out_exit; inode = dentry->d_inode; err = page_symlink(inode, symname, sym_str.len + 1); if (err) goto err_out_put; return 0; err_out_put: iput(inode); err_out_exit: return err; } static int pohmelfs_send_rename(struct pohmelfs_inode *pi, struct pohmelfs_inode *parent, struct qstr *str) { int path_len, err, total_len = 0, inode_len, parent_len; char *path; struct netfs_trans *t; struct netfs_cmd *cmd; struct pohmelfs_sb *psb = POHMELFS_SB(pi->vfs_inode.i_sb); parent_len = pohmelfs_path_length(parent); inode_len = pohmelfs_path_length(pi); if (parent_len < 0 || inode_len < 0) return -EINVAL; path_len = parent_len + inode_len + str->len + 3; t = netfs_trans_alloc(psb, path_len, 0, 0); if (!t) return -ENOMEM; cmd = netfs_trans_current(t); path = (char *)(cmd + 1); err = pohmelfs_construct_path_string(pi, path, inode_len); if (err < 0) goto err_out_unlock; cmd->ext = err; path += err; total_len += err; path_len -= err; *path = '|'; path++; total_len++; path_len--; err = pohmelfs_construct_path_string(parent, path, parent_len); if (err < 0) goto err_out_unlock; /* * Do not place a null-byte before the final slash and the name. */ err--; path += err; total_len += err; path_len -= err; err = snprintf(path, path_len - 1, "/%s", str->name); total_len += err + 1; /* 0 symbol */ path_len -= err + 1; cmd->cmd = NETFS_RENAME; cmd->id = pi->ino; cmd->start = parent->ino; cmd->size = total_len; netfs_convert_cmd(cmd); netfs_trans_update(cmd, t, total_len); return netfs_trans_finish(t, psb); err_out_unlock: netfs_trans_free(t); return err; } static int pohmelfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) { struct inode *inode = old_dentry->d_inode; struct pohmelfs_inode *old_parent, *pi, *new_parent; struct qstr str = new_dentry->d_name; struct pohmelfs_name *n; unsigned int old_hash; int err = -ENOENT; pi = POHMELFS_I(inode); old_parent = POHMELFS_I(old_dir); if (new_dir) new_dir->i_sb->s_op->write_inode(new_dir, 0); old_hash = jhash(old_dentry->d_name.name, old_dentry->d_name.len, 0); str.hash = jhash(new_dentry->d_name.name, new_dentry->d_name.len, 0); str.len = new_dentry->d_name.len; str.name = new_dentry->d_name.name; str.hash = jhash(new_dentry->d_name.name, new_dentry->d_name.len, 0); if (new_dir) { new_parent = POHMELFS_I(new_dir); err = -ENOTEMPTY; if (S_ISDIR(inode->i_mode) && new_parent->total_len <= 3) goto err_out_exit; } else { new_parent = old_parent; } dprintk("%s: ino: %llu, parent: %llu, name: '%s' -> parent: %llu, name: '%s', i_size: %llu.\n", __func__, pi->ino, old_parent->ino, old_dentry->d_name.name, new_parent->ino, new_dentry->d_name.name, inode->i_size); if (test_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state) && test_bit(NETFS_INODE_OWNED, &pi->state)) { err = pohmelfs_send_rename(pi, new_parent, &str); if (err) goto err_out_exit; } n = pohmelfs_name_alloc(str.len + 1); if (!n) goto err_out_exit; mutex_lock(&new_parent->offset_lock); n->ino = pi->ino; n->mode = inode->i_mode; n->len = str.len; n->hash = str.hash; sprintf(n->data, "%s", str.name); err = pohmelfs_insert_name(new_parent, n); mutex_unlock(&new_parent->offset_lock); if (err) goto err_out_exit; mutex_lock(&old_parent->offset_lock); n = pohmelfs_search_hash(old_parent, old_hash); if (n) pohmelfs_name_del(old_parent, n); mutex_unlock(&old_parent->offset_lock); mark_inode_dirty(inode); mark_inode_dirty(&new_parent->vfs_inode); WARN_ON_ONCE(list_empty(&inode->i_dentry)); return 0; err_out_exit: clear_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state); return err; } /* * POHMELFS directory inode operations. */ const struct inode_operations pohmelfs_dir_inode_ops = { .link = pohmelfs_link, .symlink = pohmelfs_symlink, .unlink = pohmelfs_unlink, .mkdir = pohmelfs_mkdir, .rmdir = pohmelfs_rmdir, .create = pohmelfs_create, .lookup = pohmelfs_lookup, .setattr = pohmelfs_setattr, .rename = pohmelfs_rename, };