| /* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*- |
| * vim:expandtab:shiftwidth=8:tabstop=8: |
| * |
| * Original version: Copyright (C) 1996 P. Braam and M. Callahan |
| * Rewritten for Linux 2.1. Copyright (C) 1997 Carnegie Mellon University |
| * d_fsdata and NFS compatiblity fixes Copyright (C) 2001 Tacit Networks, Inc. |
| * |
| * This file is part of InterMezzo, http://www.inter-mezzo.org. |
| * |
| * InterMezzo is free software; you can redistribute it and/or |
| * modify it under the terms of version 2 of the GNU General Public |
| * License as published by the Free Software Foundation. |
| * |
| * InterMezzo 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. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with InterMezzo; if not, write to the Free Software |
| * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| * |
| * Directory operations for InterMezzo filesystem |
| */ |
| |
| /* inode dentry alias list walking code adapted from linux/fs/dcache.c |
| * |
| * fs/dcache.c |
| * |
| * (C) 1997 Thomas Schoebel-Theuer, |
| * with heavy changes by Linus Torvalds |
| */ |
| |
| #define __NO_VERSION__ |
| #include <linux/types.h> |
| #include <linux/kernel.h> |
| #include <linux/sched.h> |
| #include <linux/fs.h> |
| #include <linux/stat.h> |
| #include <linux/errno.h> |
| #include <linux/locks.h> |
| #include <linux/slab.h> |
| #include <asm/segment.h> |
| #include <asm/uaccess.h> |
| #include <linux/string.h> |
| #include <linux/smp_lock.h> |
| #include <linux/vmalloc.h> |
| |
| #include <linux/intermezzo_fs.h> |
| |
| kmem_cache_t * presto_dentry_slab; |
| |
| /* called when a cache lookup succeeds */ |
| static int presto_d_revalidate(struct dentry *de, int flag) |
| { |
| struct inode *inode = de->d_inode; |
| struct presto_file_set * root_fset; |
| |
| ENTRY; |
| if (!inode) { |
| EXIT; |
| return 0; |
| } |
| |
| if (is_bad_inode(inode)) { |
| EXIT; |
| return 0; |
| } |
| |
| if (!presto_d2d(de)) { |
| presto_set_dd(de); |
| } |
| |
| if (!presto_d2d(de)) { |
| EXIT; |
| return 0; |
| } |
| |
| root_fset = presto_d2d(de->d_inode->i_sb->s_root)->dd_fset; |
| if (root_fset->fset_flags & FSET_FLAT_BRANCH && |
| (presto_d2d(de)->dd_fset != root_fset )) { |
| presto_d2d(de)->dd_fset = root_fset; |
| } |
| |
| EXIT; |
| return 1; |
| |
| #if 0 |
| /* The following is needed for metadata on demand. */ |
| if ( S_ISDIR(inode->i_mode) ) { |
| EXIT; |
| return (presto_chk(de, PRESTO_DATA) && |
| (presto_chk(de, PRESTO_ATTR))); |
| } else { |
| EXIT; |
| return presto_chk(de, PRESTO_ATTR); |
| } |
| #endif |
| } |
| |
| static void presto_d_release(struct dentry *dentry) |
| { |
| if (!presto_d2d(dentry)) { |
| /* This can happen for dentries from NFSd */ |
| return; |
| } |
| presto_d2d(dentry)->dd_count--; |
| |
| if (!presto_d2d(dentry)->dd_count) { |
| kmem_cache_free(presto_dentry_slab, presto_d2d(dentry)); |
| dentry->d_fsdata = NULL; |
| } |
| } |
| |
| struct dentry_operations presto_dentry_ops = |
| { |
| .d_revalidate = presto_d_revalidate, |
| .d_release = presto_d_release |
| }; |
| |
| static inline int presto_is_dentry_ROOT (struct dentry *dentry) |
| { |
| return(dentry_name_cmp(dentry,"ROOT") && |
| !dentry_name_cmp(dentry->d_parent,".intermezzo")); |
| } |
| |
| static struct presto_file_set* presto_try_find_fset(struct dentry* dentry, |
| int *is_under_d_intermezzo) |
| { |
| struct dentry* temp_dentry; |
| struct presto_dentry_data *d_data; |
| int found_root=0; |
| |
| ENTRY; |
| CDEBUG(D_FSDATA, "finding fileset for %p:%s\n", dentry, |
| dentry->d_name.name); |
| |
| *is_under_d_intermezzo = 0; |
| |
| /* walk up through the branch to get the fileset */ |
| /* The dentry we are passed presumably does not have the correct |
| * fset information. However, we still want to start walking up |
| * the branch from this dentry to get our found_root and |
| * is_under_d_intermezzo decisions correct |
| */ |
| for (temp_dentry = dentry ; ; temp_dentry = temp_dentry->d_parent) { |
| CDEBUG(D_FSDATA, "--->dentry %p:%*s\n", temp_dentry, |
| temp_dentry->d_name.len,temp_dentry->d_name.name); |
| if (presto_is_dentry_ROOT(temp_dentry)) |
| found_root = 1; |
| if (!found_root && |
| dentry_name_cmp(temp_dentry, ".intermezzo")) { |
| *is_under_d_intermezzo = 1; |
| } |
| d_data = presto_d2d(temp_dentry); |
| if (d_data) { |
| /* If we found a "ROOT" dentry while walking up the |
| * branch, we will journal regardless of whether |
| * we are under .intermezzo or not. |
| * If we are already under d_intermezzo don't reverse |
| * the decision here...even if we found a "ROOT" |
| * dentry above .intermezzo (if we were ever to |
| * modify the directory structure). |
| */ |
| if (!*is_under_d_intermezzo) |
| *is_under_d_intermezzo = !found_root && |
| (d_data->dd_flags & PRESTO_DONT_JOURNAL); |
| EXIT; |
| return d_data->dd_fset; |
| } |
| if (temp_dentry->d_parent == temp_dentry) { |
| break; |
| } |
| } |
| EXIT; |
| return NULL; |
| } |
| |
| /* Only call this function on positive dentries */ |
| static struct presto_dentry_data* presto_try_find_alias_with_dd ( |
| struct dentry* dentry) |
| { |
| struct inode *inode=dentry->d_inode; |
| struct list_head *head, *next, *tmp; |
| struct dentry *tmp_dentry; |
| |
| /* Search through the alias list for dentries with d_fsdata */ |
| spin_lock(&dcache_lock); |
| head = &inode->i_dentry; |
| next = inode->i_dentry.next; |
| while (next != head) { |
| tmp = next; |
| next = tmp->next; |
| tmp_dentry = list_entry(tmp, struct dentry, d_alias); |
| if (!presto_d2d(tmp_dentry)) { |
| spin_unlock(&dcache_lock); |
| return presto_d2d(tmp_dentry); |
| } |
| } |
| spin_unlock(&dcache_lock); |
| return NULL; |
| } |
| |
| /* Only call this function on positive dentries */ |
| static void presto_set_alias_dd (struct dentry *dentry, |
| struct presto_dentry_data* dd) |
| { |
| struct inode *inode=dentry->d_inode; |
| struct list_head *head, *next, *tmp; |
| struct dentry *tmp_dentry; |
| |
| /* Set d_fsdata for this dentry */ |
| dd->dd_count++; |
| dentry->d_fsdata = dd; |
| |
| /* Now set d_fsdata for all dentries in the alias list. */ |
| spin_lock(&dcache_lock); |
| head = &inode->i_dentry; |
| next = inode->i_dentry.next; |
| while (next != head) { |
| tmp = next; |
| next = tmp->next; |
| tmp_dentry = list_entry(tmp, struct dentry, d_alias); |
| if (!presto_d2d(tmp_dentry)) { |
| dd->dd_count++; |
| tmp_dentry->d_fsdata = dd; |
| } |
| } |
| spin_unlock(&dcache_lock); |
| return; |
| } |
| |
| inline struct presto_dentry_data *izo_alloc_ddata(void) |
| { |
| struct presto_dentry_data *dd; |
| |
| dd = kmem_cache_alloc(presto_dentry_slab, SLAB_KERNEL); |
| if (dd == NULL) { |
| CERROR("IZO: out of memory trying to allocate presto_dentry_data\n"); |
| return NULL; |
| } |
| memset(dd, 0, sizeof(*dd)); |
| dd->dd_count = 1; |
| |
| return dd; |
| } |
| |
| /* This uses the BKL! */ |
| int presto_set_dd(struct dentry * dentry) |
| { |
| struct presto_file_set *fset = NULL; |
| struct presto_dentry_data *dd; |
| int is_under_d_izo; |
| int error=0; |
| |
| ENTRY; |
| |
| if (!dentry) |
| BUG(); |
| |
| lock_kernel(); |
| |
| /* Did we lose a race? */ |
| if (dentry->d_fsdata) { |
| CERROR("dentry %p already has d_fsdata set\n", dentry); |
| if (dentry->d_inode) |
| CERROR(" inode: %ld\n", dentry->d_inode->i_ino); |
| EXIT; |
| goto out_unlock; |
| } |
| |
| if (dentry->d_inode != NULL) { |
| /* NFSd runs find_fh_dentry which instantiates disconnected |
| * dentries which are then connected without a lookup(). |
| * So it is possible to have connected dentries that do not |
| * have d_fsdata set. So we walk the list trying to find |
| * an alias which has its d_fsdata set and then use that |
| * for all the other dentries as well. |
| * - SHP,Vinny. |
| */ |
| |
| /* If there is an alias with d_fsdata use it. */ |
| if ((dd = presto_try_find_alias_with_dd (dentry))) { |
| presto_set_alias_dd (dentry, dd); |
| EXIT; |
| goto out_unlock; |
| } |
| } else { |
| /* Negative dentry */ |
| CDEBUG(D_FSDATA,"negative dentry %p: %*s\n", dentry, |
| dentry->d_name.len, dentry->d_name.name); |
| } |
| |
| /* No pre-existing d_fsdata, we need to construct one. |
| * First, we must walk up the tree to find the fileset |
| * If a fileset can't be found, we leave a null fsdata |
| * and return EROFS to indicate that we can't journal |
| * updates. |
| */ |
| fset = presto_try_find_fset (dentry, &is_under_d_izo); |
| if (!fset) { |
| #ifdef PRESTO_NO_NFS |
| CERROR("No fileset for dentry %p: %*s\n", dentry, |
| dentry->d_name.len, dentry->d_name.name); |
| #endif |
| error = -EROFS; |
| EXIT; |
| goto out_unlock; |
| } |
| |
| dentry->d_fsdata = izo_alloc_ddata(); |
| if (!presto_d2d(dentry)) { |
| CERROR ("InterMezzo: out of memory allocating d_fsdata\n"); |
| error = -ENOMEM; |
| goto out_unlock; |
| } |
| presto_d2d(dentry)->dd_fset = fset; |
| if (is_under_d_izo) |
| presto_d2d(dentry)->dd_flags |= PRESTO_DONT_JOURNAL; |
| EXIT; |
| |
| out_unlock: |
| CDEBUG(D_FSDATA,"presto_set_dd dentry %p: %*s, d_fsdata %p\n", |
| dentry, dentry->d_name.len, dentry->d_name.name, |
| dentry->d_fsdata); |
| unlock_kernel(); |
| |
| if (fset) { |
| filter_setup_dentry_ops(fset->fset_cache->cache_filter, |
| dentry->d_op, &presto_dentry_ops); |
| dentry->d_op = filter_c2udops(fset->fset_cache->cache_filter); |
| } |
| |
| return error; |
| } |
| |
| int presto_init_ddata_cache(void) |
| { |
| ENTRY; |
| presto_dentry_slab = |
| kmem_cache_create("presto_cache", |
| sizeof(struct presto_dentry_data), 0, |
| SLAB_HWCACHE_ALIGN, NULL, |
| NULL); |
| EXIT; |
| return (presto_dentry_slab != NULL); |
| } |
| |
| void presto_cleanup_ddata_cache(void) |
| { |
| kmem_cache_destroy(presto_dentry_slab); |
| } |