aboutsummaryrefslogtreecommitdiff
path: root/ufs/inode.c
diff options
context:
space:
mode:
Diffstat (limited to 'ufs/inode.c')
-rw-r--r--ufs/inode.c713
1 files changed, 713 insertions, 0 deletions
diff --git a/ufs/inode.c b/ufs/inode.c
new file mode 100644
index 00000000..1901788e
--- /dev/null
+++ b/ufs/inode.c
@@ -0,0 +1,713 @@
+/* Inode management routines
+ Copyright (C) 1994, 1995, 1996, 1997, 1998 Free Software Foundation
+
+ 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, 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include "ufs.h"
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <netinet/in.h>
+#include <fcntl.h>
+#include <hurd/store.h>
+
+#define INOHSZ 512
+#if ((INOHSZ&(INOHSZ-1)) == 0)
+#define INOHASH(ino) ((ino)&(INOHSZ-1))
+#else
+#define INOHASH(ino) (((unsigned)(ino))%INOHSZ)
+#endif
+
+static struct node *nodehash[INOHSZ];
+static error_t read_disknode (struct node *np);
+
+spin_lock_t gennumberlock = SPIN_LOCK_INITIALIZER;
+
+/* Initialize the inode hash table. */
+void
+inode_init ()
+{
+ int n;
+ for (n = 0; n < INOHSZ; n++)
+ nodehash[n] = 0;
+}
+
+/* Fetch inode INUM, set *NPP to the node structure;
+ gain one user reference and lock the node. */
+error_t
+diskfs_cached_lookup (int inum, struct node **npp)
+{
+ struct disknode *dn;
+ struct node *np;
+ error_t err;
+
+ spin_lock (&diskfs_node_refcnt_lock);
+ for (np = nodehash[INOHASH(inum)]; np; np = np->dn->hnext)
+ {
+ if (np->dn->number != inum)
+ continue;
+
+ np->references++;
+ spin_unlock (&diskfs_node_refcnt_lock);
+ mutex_lock (&np->lock);
+ *npp = np;
+ return 0;
+ }
+
+ dn = malloc (sizeof (struct disknode));
+
+ dn->number = inum;
+ dn->dirents = 0;
+ dn->dir_idx = 0;
+
+ rwlock_init (&dn->allocptrlock);
+ dn->dirty = 0;
+ dn->fileinfo = 0;
+
+ np = diskfs_make_node (dn);
+ np->cache_id = inum;
+
+ mutex_lock (&np->lock);
+ dn->hnext = nodehash[INOHASH(inum)];
+ if (dn->hnext)
+ dn->hnext->dn->hprevp = &dn->hnext;
+ dn->hprevp = &nodehash[INOHASH(inum)];
+ nodehash[INOHASH(inum)] = np;
+ spin_unlock (&diskfs_node_refcnt_lock);
+
+ err = read_disknode (np);
+
+ if (!diskfs_check_readonly () && !np->dn_stat.st_gen)
+ {
+ spin_lock (&gennumberlock);
+ if (++nextgennumber < diskfs_mtime->seconds)
+ nextgennumber = diskfs_mtime->seconds;
+ np->dn_stat.st_gen = nextgennumber;
+ spin_unlock (&gennumberlock);
+ np->dn_set_ctime = 1;
+ }
+
+ if (err)
+ return err;
+ else
+ {
+ *npp = np;
+ return 0;
+ }
+}
+
+/* Lookup node INUM (which must have a reference already) and return it
+ without allocating any new references. */
+struct node *
+ifind (ino_t inum)
+{
+ struct node *np;
+
+ spin_lock (&diskfs_node_refcnt_lock);
+ for (np = nodehash[INOHASH(inum)]; np; np = np->dn->hnext)
+ {
+ if (np->dn->number != inum)
+ continue;
+
+ assert (np->references);
+ spin_unlock (&diskfs_node_refcnt_lock);
+ return np;
+ }
+ assert (0);
+}
+
+/* The last reference to a node has gone away; drop
+ it from the hash table and clean all state in the dn structure. */
+void
+diskfs_node_norefs (struct node *np)
+{
+ *np->dn->hprevp = np->dn->hnext;
+ if (np->dn->hnext)
+ np->dn->hnext->dn->hprevp = np->dn->hprevp;
+ if (np->dn->dirents)
+ free (np->dn->dirents);
+ assert (!np->dn->fileinfo);
+ free (np->dn);
+ free (np);
+}
+
+/* The last hard reference to a node has gone away; arrange to have
+ all the weak references dropped that can be. */
+void
+diskfs_try_dropping_softrefs (struct node *np)
+{
+ drop_pager_softrefs (np);
+}
+
+/* The last hard reference to a node has gone away. */
+void
+diskfs_lost_hardrefs (struct node *np)
+{
+#ifdef notanymore
+ struct port_info *pi;
+ struct pager *p;
+
+ /* Check and see if there is a pager which has only
+ one reference (ours). If so, then drop that reference,
+ breaking the cycle. The complexity in this routine
+ is all due to this cycle. */
+
+ if (np->dn->fileinfo)
+ {
+ spin_lock (&_libports_portrefcntlock);
+ pi = (struct port_info *) np->dn->fileinfo->p;
+ if (pi->refcnt == 1)
+ {
+
+ /* The only way to get a new reference to the pager
+ in this state is to call diskfs_get_filemap; this
+ can't happen as long as we hold NP locked. So
+ we can safely unlock _libports_portrefcntlock for
+ the following call. */
+ spin_unlock (&_libports_portrefcntlock);
+
+ /* Right now the node is locked with no hard refs;
+ this is an anomolous situation. Before messing with
+ the reference count on the file pager, we have to
+ give ourselves a reference back so that we are really
+ allowed to hold the lock. Then we can do the
+ unreference. */
+ p = np->dn->fileinfo->p;
+ np->dn->fileinfo = 0;
+ diskfs_nref (np);
+ pager_unreference (p);
+
+ assert (np->references == 1 && np->light_references == 0);
+
+ /* This will do the real deallocate. Whew. */
+ diskfs_nput (np);
+ }
+ else
+ spin_unlock (&_libports_portrefcntlock);
+ }
+#endif
+}
+
+/* A new hard reference to a node has been created; it's now OK to
+ have unused weak references. */
+void
+diskfs_new_hardrefs (struct node *np)
+{
+ allow_pager_softrefs (np);
+}
+
+/* Read stat information out of the dinode. */
+static error_t
+read_disknode (struct node *np)
+{
+ static int fsid, fsidset;
+ struct stat *st = &np->dn_stat;
+ struct dinode *di = dino (np->dn->number);
+ error_t err;
+
+ err = diskfs_catch_exception ();
+ if (err)
+ return err;
+
+ if (! fsidset)
+ {
+ fsid = getpid ();
+ fsidset = 1;
+ }
+
+ st->st_fstype = FSTYPE_UFS;
+ st->st_fsid = fsid;
+ st->st_ino = np->dn->number;
+ st->st_gen = read_disk_entry (di->di_gen);
+ st->st_rdev = read_disk_entry(di->di_rdev);
+ st->st_mode = (((read_disk_entry (di->di_model)
+ | (read_disk_entry (di->di_modeh) << 16))
+ & ~S_ITRANS)
+ | (di->di_trans ? S_IPTRANS : 0));
+ st->st_nlink = read_disk_entry (di->di_nlink);
+ st->st_size = read_disk_entry (di->di_size);
+#ifdef notyet
+ st->st_atimespec = di->di_atime;
+ st->st_mtimespec = di->di_mtime;
+ st->st_ctimespec = di->di_ctime;
+#else
+ st->st_atime = read_disk_entry (di->di_atime.tv_sec);
+ st->st_atime_usec = read_disk_entry (di->di_atime.tv_nsec) / 1000;
+ st->st_mtime = read_disk_entry (di->di_mtime.tv_sec);
+ st->st_mtime_usec = read_disk_entry (di->di_mtime.tv_nsec) / 1000;
+ st->st_ctime = read_disk_entry (di->di_ctime.tv_sec);
+ st->st_ctime_usec = read_disk_entry (di->di_ctime.tv_nsec) / 1000;
+#endif
+ st->st_blksize = sblock->fs_bsize;
+ st->st_blocks = read_disk_entry (di->di_blocks);
+ st->st_flags = read_disk_entry (di->di_flags);
+
+ if (sblock->fs_inodefmt < FS_44INODEFMT)
+ {
+ st->st_uid = read_disk_entry (di->di_ouid);
+ st->st_gid = read_disk_entry (di->di_ogid);
+ st->st_author = st->st_uid;
+ np->author_tracks_uid = 1;
+ }
+ else
+ {
+ st->st_uid = read_disk_entry (di->di_uid);
+ st->st_gid = read_disk_entry (di->di_gid);
+ st->st_author = read_disk_entry (di->di_author);
+ if (st->st_author == -1)
+ st->st_author = st->st_uid;
+ }
+
+ diskfs_end_catch_exception ();
+ if (!S_ISBLK (st->st_mode) && !S_ISCHR (st->st_mode))
+ st->st_rdev = 0;
+
+ if (S_ISLNK (st->st_mode)
+ && direct_symlink_extension
+ && st->st_size < sblock->fs_maxsymlinklen)
+ np->allocsize = 0;
+ else
+ {
+ if (lblkno (sblock, np->dn_stat.st_size) < NDADDR)
+ np->allocsize = fragroundup (sblock, st->st_size);
+ else
+ np->allocsize = blkroundup (sblock, st->st_size);
+ }
+
+ return 0;
+}
+
+error_t diskfs_node_reload (struct node *node)
+{
+ if (node->dn->dirents)
+ {
+ free (node->dn->dirents);
+ node->dn->dirents = 0;
+ }
+ flush_node_pager (node);
+ read_disknode (node);
+ return 0;
+}
+
+/* Return 0 if NP's author can be changed to AUTHOR; otherwise return an
+ error code. */
+error_t
+diskfs_validate_author_change (struct node *np, uid_t author)
+{
+ if (compat_mode == COMPAT_GNU)
+ return 0;
+ else
+ /* For non-hurd filesystems, the author & owner are the same. */
+ return (author == np->dn_stat.st_uid) ? 0 : EINVAL;
+}
+
+static void
+write_node (struct node *np)
+{
+ struct stat *st = &np->dn_stat;
+ struct dinode *di = dino (np->dn->number);
+ error_t err;
+
+ assert (!np->dn_set_ctime && !np->dn_set_atime && !np->dn_set_mtime);
+ if (np->dn_stat_dirty)
+ {
+ assert (!diskfs_readonly);
+
+ err = diskfs_catch_exception ();
+ if (err)
+ return;
+
+ write_disk_entry (di->di_gen, st->st_gen);
+
+ if (S_ISBLK (st->st_mode) || S_ISCHR (st->st_mode))
+ write_disk_entry (di->di_rdev, st->st_rdev);
+
+ /* We happen to know that the stat mode bits are the same
+ as the ufs mode bits. */
+
+ if (compat_mode == COMPAT_GNU)
+ {
+ mode_t mode = st->st_mode & ~S_ITRANS;
+ write_disk_entry (di->di_model, mode & 0xffff);
+ write_disk_entry (di->di_modeh, (mode >> 16) & 0xffff);
+ }
+ else
+ {
+ write_disk_entry (di->di_model, st->st_mode & 0xffff & ~S_ITRANS);
+ di->di_modeh = 0;
+ }
+
+ if (compat_mode != COMPAT_BSD42)
+ {
+ write_disk_entry (di->di_uid, st->st_uid);
+ write_disk_entry (di->di_gid, st->st_gid);
+ }
+
+ if (sblock->fs_inodefmt < FS_44INODEFMT)
+ {
+ write_disk_entry (di->di_ouid, st->st_uid & 0xffff);
+ write_disk_entry (di->di_ogid, st->st_gid & 0xffff);
+ }
+ else if (compat_mode == COMPAT_GNU)
+ write_disk_entry (di->di_author, st->st_author);
+
+ write_disk_entry (di->di_nlink, st->st_nlink);
+ write_disk_entry (di->di_size, st->st_size);
+#ifdef notyet
+ di->di_atime = st->st_atimespec;
+ di->di_mtime = st->st_mtimespec;
+ di->di_ctime = st->st_ctimespec;
+#else
+ write_disk_entry (di->di_atime.tv_sec, st->st_atime);
+ write_disk_entry (di->di_atime.tv_nsec, st->st_atime_usec * 1000);
+ write_disk_entry (di->di_mtime.tv_sec, st->st_mtime);
+ write_disk_entry (di->di_mtime.tv_nsec, st->st_mtime_usec * 1000);
+ write_disk_entry (di->di_ctime.tv_sec, st->st_ctime);
+ write_disk_entry (di->di_ctime.tv_nsec, st->st_ctime_usec * 1000);
+#endif
+ write_disk_entry (di->di_blocks, st->st_blocks);
+ write_disk_entry (di->di_flags, st->st_flags);
+
+ diskfs_end_catch_exception ();
+ np->dn_stat_dirty = 0;
+ record_poke (di, sizeof (struct dinode));
+ }
+}
+
+/* See if we should create a symlink by writing it directly into
+ the block pointer array. Returning EINVAL tells diskfs to do it
+ the usual way. */
+static error_t
+create_symlink_hook (struct node *np, const char *target)
+{
+ int len = strlen (target);
+ error_t err;
+ struct dinode *di;
+
+ if (!direct_symlink_extension)
+ return EINVAL;
+
+ assert (compat_mode != COMPAT_BSD42);
+
+ if (len >= sblock->fs_maxsymlinklen)
+ return EINVAL;
+
+ err = diskfs_catch_exception ();
+ if (err)
+ return err;
+
+ di = dino (np->dn->number);
+ bcopy (target, di->di_shortlink, len);
+ np->dn_stat.st_size = len;
+ np->dn_set_ctime = 1;
+ np->dn_set_mtime = 1;
+ record_poke (di, sizeof (struct dinode));
+
+ diskfs_end_catch_exception ();
+ return 0;
+}
+error_t (*diskfs_create_symlink_hook)(struct node *, const char *)
+ = create_symlink_hook;
+
+/* Check if this symlink is stored directly in the block pointer array.
+ Returning EINVAL tells diskfs to do it the usual way. */
+static error_t
+read_symlink_hook (struct node *np,
+ char *buf)
+{
+ error_t err;
+
+ if (!direct_symlink_extension
+ || np->dn_stat.st_size >= sblock->fs_maxsymlinklen)
+ return EINVAL;
+
+ err = diskfs_catch_exception ();
+ if (err)
+ return err;
+
+ bcopy ((dino (np->dn->number))->di_shortlink, buf, np->dn_stat.st_size);
+
+ if (! diskfs_check_readonly ())
+ np->dn_set_atime = 1;
+
+ diskfs_end_catch_exception ();
+ return 0;
+}
+error_t (*diskfs_read_symlink_hook)(struct node *, char *)
+ = read_symlink_hook;
+
+error_t
+diskfs_node_iterate (error_t (*fun)(struct node *))
+{
+ struct node *np;
+ struct item {struct item *next; struct node *np;} *list = 0;
+ struct item *i;
+ error_t err;
+ int n;
+
+ /* Acquire a reference on all the nodes in the hash table
+ and enter them into a list on the stack. */
+ spin_lock (&diskfs_node_refcnt_lock);
+ for (n = 0; n < INOHSZ; n++)
+ for (np = nodehash[n]; np; np = np->dn->hnext)
+ {
+ np->references++;
+ i = alloca (sizeof (struct item));
+ i->next = list;
+ i->np = np;
+ list = i;
+ }
+ spin_unlock (&diskfs_node_refcnt_lock);
+
+ err = 0;
+ for (i = list; i; i = i->next)
+ {
+ if (!err)
+ {
+ mutex_lock (&i->np->lock);
+ err = (*fun)(i->np);
+ mutex_unlock (&i->np->lock);
+ }
+ diskfs_nrele (i->np);
+ }
+ return err;
+}
+
+/* Write all active disknodes into the dinode pager. */
+void
+write_all_disknodes ()
+{
+ error_t
+ helper (struct node *np)
+ {
+ diskfs_set_node_times (np);
+ write_node (np);
+ return 0;
+ }
+
+ diskfs_node_iterate (helper);
+}
+
+void
+diskfs_write_disknode (struct node *np, int wait)
+{
+ write_node (np);
+ if (wait)
+ sync_dinode (np->dn->number, 1);
+}
+
+/* Implement the diskfs_set_statfs callback from the diskfs library;
+ see <hurd/diskfs.h> for the interface description. */
+error_t
+diskfs_set_statfs (struct statfs *st)
+{
+ st->f_type = FSTYPE_UFS;
+ st->f_bsize = sblock->fs_fsize;
+ st->f_blocks = sblock->fs_dsize;
+ st->f_bfree = (sblock->fs_cstotal.cs_nbfree * sblock->fs_frag
+ + sblock->fs_cstotal.cs_nffree);
+ st->f_bavail = ((sblock->fs_dsize * (100 - sblock->fs_minfree) / 100)
+ - (sblock->fs_dsize - st->f_bfree));
+ st->f_files = sblock->fs_ncg * sblock->fs_ipg - 2; /* not 0 or 1 */
+ st->f_ffree = sblock->fs_cstotal.cs_nifree;
+ st->f_fsid = getpid ();
+ st->f_namelen = 0;
+ st->f_favail = st->f_ffree;
+ st->f_frsize = sblock->fs_fsize;
+ return 0;
+}
+
+/* Implement the diskfs_set_translator callback from the diskfs
+ library; see <hurd/diskfs.h> for the interface description. */
+error_t
+diskfs_set_translator (struct node *np, const char *name, u_int namelen,
+ struct protid *cred)
+{
+ daddr_t blkno;
+ error_t err;
+ char buf[sblock->fs_bsize];
+ struct dinode *di;
+
+ if (compat_mode != COMPAT_GNU)
+ return EOPNOTSUPP;
+
+ if (namelen + sizeof (u_int) > sblock->fs_bsize)
+ return ENAMETOOLONG;
+
+ err = diskfs_catch_exception ();
+ if (err)
+ return err;
+
+ di = dino (np->dn->number);
+ blkno = read_disk_entry (di->di_trans);
+
+ if (namelen && !blkno)
+ {
+ /* Allocate block for translator */
+ err = ffs_alloc (np, 0, 0, sblock->fs_bsize, &blkno, cred);
+ if (err)
+ {
+ diskfs_end_catch_exception ();
+ return err;
+ }
+ write_disk_entry (di->di_trans, blkno);
+ record_poke (di, sizeof (struct dinode));
+ np->dn_set_ctime = 1;
+ }
+ else if (!namelen && blkno)
+ {
+ /* Clear block for translator going away. */
+ ffs_blkfree (np, blkno, sblock->fs_bsize);
+ di->di_trans = 0;
+ record_poke (di, sizeof (struct dinode));
+ np->dn_stat.st_blocks -= btodb (sblock->fs_bsize);
+ np->dn_stat.st_mode &= ~S_IPTRANS;
+ np->dn_set_ctime = 1;
+ }
+
+ if (namelen)
+ {
+ bcopy (&namelen, buf, sizeof (u_int));
+ bcopy (name, buf + sizeof (u_int), namelen);
+
+ bcopy (buf, disk_image + fsaddr (sblock, blkno), sblock->fs_bsize);
+ sync_disk_blocks (blkno, sblock->fs_bsize, 1);
+
+ np->dn_stat.st_mode |= S_IPTRANS;
+ np->dn_set_ctime = 1;
+ }
+
+ diskfs_end_catch_exception ();
+ return err;
+}
+
+/* Implement the diskfs_get_translator callback from the diskfs library.
+ See <hurd/diskfs.h> for the interface description. */
+error_t
+diskfs_get_translator (struct node *np, char **namep, u_int *namelen)
+{
+ error_t err;
+ daddr_t blkno;
+ u_int datalen;
+ void *transloc;
+
+ err = diskfs_catch_exception ();
+ if (err)
+ return err;
+
+ blkno = read_disk_entry ((dino (np->dn->number))->di_trans);
+ assert (blkno);
+ transloc = disk_image + fsaddr (sblock, blkno);
+
+ datalen = *(u_int *)transloc;
+ *namep = malloc (datalen);
+ bcopy (transloc + sizeof (u_int), *namep, datalen);
+
+ diskfs_end_catch_exception ();
+
+ *namelen = datalen;
+ return 0;
+}
+
+/* Called when all hard ports have gone away. */
+void
+diskfs_shutdown_soft_ports ()
+{
+ /* Should initiate termination of internally held pager ports
+ (the only things that should be soft) XXX */
+}
+
+/* Return a description of the storage of the file. */
+/* In STORAGE_DATA are the following, in network byte order:
+
+ Inode number (4 bytes)
+ disk address of transator spec (4 bytes)
+ disk address of inode structure (4 bytes)
+ offset into inode block holding inode (4 bytes) */
+error_t
+diskfs_S_file_get_storage_info (struct protid *cred,
+ mach_port_t **ports,
+ mach_msg_type_name_t *ports_type,
+ mach_msg_type_number_t *num_ports,
+ int **ints, mach_msg_type_number_t *num_ints,
+ off_t **offsets,
+ mach_msg_type_number_t *num_offsets,
+ char **data, mach_msg_type_number_t *data_len)
+{
+ error_t err;
+ struct node *np;
+ struct store *file_store;
+ struct store_run runs[NDADDR];
+ size_t num_runs = 0;
+
+ if (! cred)
+ return EOPNOTSUPP;
+
+ np = cred->po->np;
+ mutex_lock (&np->lock);
+
+ /* See if this file fits in the direct block pointers. If not, punt
+ for now. (Reading indir blocks is a pain, and I'm postponing
+ pain.) XXX */
+ if (np->allocsize > NDADDR * sblock->fs_bsize)
+ {
+ mutex_unlock (&np->lock);
+ return EINVAL;
+ }
+
+ err = diskfs_catch_exception ();
+ if (! err)
+ if (!direct_symlink_extension
+ || np->dn_stat.st_size >= sblock->fs_maxsymlinklen
+ || !S_ISLNK (np->dn_stat.st_mode))
+ /* Copy the block pointers */
+ {
+ int i;
+ struct store_run *run = runs;
+ struct dinode *di = dino (np->dn->number);
+
+ for (i = 0; i < NDADDR; i++)
+ {
+ off_t start = fsbtodb (sblock, read_disk_entry (di->di_db[i]));
+ off_t length =
+ (((i + 1) * sblock->fs_bsize > np->allocsize)
+ ? np->allocsize - i * sblock->fs_bsize
+ : sblock->fs_bsize);
+ start <<= log2_dev_blocks_per_dev_bsize;
+ length <<= log2_dev_blocks_per_dev_bsize;
+ if (num_runs == 0 || run->start + run->length != start)
+ *run++ = (struct store_run){ start, length };
+ else
+ run->length += length;
+ }
+ }
+ diskfs_end_catch_exception ();
+
+ mutex_unlock (&np->lock);
+
+ if (! err)
+ err = store_clone (store, &file_store);
+ if (! err)
+ {
+ err = store_remap (file_store, runs, num_runs, &file_store);
+ if (! err)
+ err = store_return (file_store, ports, num_ports, ints, num_ints,
+ offsets, num_offsets, data, data_len);
+ store_free (file_store);
+ }
+ *ports_type = MACH_MSG_TYPE_COPY_SEND;
+
+ return err;
+}