summaryrefslogtreecommitdiff
path: root/ext2fs/inode.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext2fs/inode.c')
-rw-r--r--ext2fs/inode.c787
1 files changed, 787 insertions, 0 deletions
diff --git a/ext2fs/inode.c b/ext2fs/inode.c
new file mode 100644
index 00000000..6be299fd
--- /dev/null
+++ b/ext2fs/inode.c
@@ -0,0 +1,787 @@
+/* Inode management routines
+
+ Copyright (C) 1994,95,96,97,98,99 Free Software Foundation, Inc.
+
+ Converted for ext2fs by Miles Bader <miles@gnu.org>
+
+ 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 "ext2fs.h"
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/statvfs.h>
+
+/* these flags aren't actually defined by a header file yet, so temporarily
+ disable them if necessary. */
+#ifndef UF_APPEND
+#define UF_APPEND 0
+#endif
+#ifndef UF_NODUMP
+#define UF_NODUMP 0
+#endif
+#ifndef UF_IMMUTABLE
+#define UF_IMMUTABLE 0
+#endif
+
+#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_node (struct node *np);
+
+spin_lock_t generation_lock = 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)
+{
+ error_t err;
+ struct node *np;
+ struct disknode *dn;
+
+ spin_lock (&diskfs_node_refcnt_lock);
+ for (np = nodehash[INOHASH(inum)]; np; np = np->dn->hnext)
+ if (np->cache_id == inum)
+ {
+ np->references++;
+ spin_unlock (&diskfs_node_refcnt_lock);
+ mutex_lock (&np->lock);
+ *npp = np;
+ return 0;
+ }
+
+ /* Format specific data for the new node. */
+ dn = malloc (sizeof (struct disknode));
+ if (! dn)
+ {
+ spin_unlock (&diskfs_node_refcnt_lock);
+ return ENOMEM;
+ }
+ dn->dirents = 0;
+ dn->dir_idx = 0;
+ dn->pager = 0;
+ rwlock_init (&dn->alloc_lock);
+ pokel_init (&dn->indir_pokel, diskfs_disk_pager, disk_image);
+
+ /* Create the new node. */
+ np = diskfs_make_node (dn);
+ np->cache_id = inum;
+
+ mutex_lock (&np->lock);
+
+ /* Put NP in NODEHASH. */
+ 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);
+
+ /* Get the contents of NP off disk. */
+ err = read_node (np);
+
+ if (!diskfs_check_readonly () && !np->dn_stat.st_gen)
+ {
+ spin_lock (&generation_lock);
+ if (++next_generation < diskfs_mtime->seconds)
+ next_generation = diskfs_mtime->seconds;
+ np->dn_stat.st_gen = next_generation;
+ spin_unlock (&generation_lock);
+ 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->cache_id != 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->pager);
+
+ /* Move any pending writes of indirect blocks. */
+ pokel_inherit (&global_pokel, &np->dn->indir_pokel);
+ pokel_finalize (&np->dn->indir_pokel);
+
+ 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)
+{
+}
+
+/* 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 ext2_inode. */
+static error_t
+read_node (struct node *np)
+{
+ error_t err;
+ static int fsid, fsidset;
+ struct stat *st = &np->dn_stat;
+ struct disknode *dn = np->dn;
+ struct ext2_inode *di = dino (np->cache_id);
+ struct ext2_inode_info *info = &dn->info;
+
+ err = diskfs_catch_exception ();
+ if (err)
+ return err;
+
+ if (!fsidset)
+ {
+ fsid = getpid ();
+ fsidset = 1;
+ }
+
+ st->st_fstype = FSTYPE_EXT2FS;
+ st->st_fsid = fsid;
+ st->st_ino = np->cache_id;
+ st->st_blksize = vm_page_size * 2;
+
+ st->st_nlink = di->i_links_count;
+ st->st_size = di->i_size;
+ st->st_gen = di->i_generation;
+
+ st->st_atime = di->i_atime;
+ st->st_mtime = di->i_mtime;
+ st->st_ctime = di->i_ctime;
+
+#ifdef XXX
+ st->st_atime_usec = di->i_atime.ts_nsec / 1000;
+ st->st_mtime_usec = di->i_mtime.ts_nsec / 1000;
+ st->st_ctime_usec = di->i_ctime.ts_nsec / 1000;
+#endif
+
+ st->st_blocks = di->i_blocks;
+
+ st->st_flags = 0;
+ if (di->i_flags & EXT2_APPEND_FL)
+ st->st_flags |= UF_APPEND;
+ if (di->i_flags & EXT2_NODUMP_FL)
+ st->st_flags |= UF_NODUMP;
+ if (di->i_flags & EXT2_IMMUTABLE_FL)
+ st->st_flags |= UF_IMMUTABLE;
+
+ if (sblock->s_creator_os == EXT2_OS_HURD)
+ {
+ st->st_mode = di->i_mode | (di->i_mode_high << 16);
+ st->st_mode &= ~S_ITRANS;
+ if (di->i_translator)
+ st->st_mode |= S_IPTRANS;
+
+ st->st_uid = di->i_uid | (di->i_uid_high << 16);
+ st->st_gid = di->i_gid | (di->i_gid_high << 16);
+
+ st->st_author = di->i_author;
+ if (st->st_author == -1)
+ st->st_author = st->st_uid;
+ }
+ else
+ {
+ st->st_mode = di->i_mode & ~S_ITRANS;
+ st->st_uid = di->i_uid;
+ st->st_gid = di->i_gid;
+ st->st_author = st->st_uid;
+ np->author_tracks_uid = 1;
+ }
+
+ /* Setup the ext2fs auxiliary inode info. */
+ info->i_dtime = di->i_dtime;
+ info->i_flags = di->i_flags;
+ info->i_faddr = di->i_faddr;
+ info->i_frag_no = di->i_frag;
+ info->i_frag_size = di->i_fsize;
+ info->i_osync = 0;
+ info->i_file_acl = di->i_file_acl;
+ if (S_ISDIR (st->st_mode))
+ info->i_dir_acl = di->i_dir_acl;
+ else
+ {
+ info->i_dir_acl = 0;
+ info->i_high_size = di->i_size_high;
+ if (info->i_high_size) /* XXX */
+ {
+ ext2_warning ("cannot handle large file inode %d", np->cache_id);
+ return EFBIG;
+ }
+ }
+ info->i_block_group = inode_group_num (np->cache_id);
+ info->i_next_alloc_block = 0;
+ info->i_next_alloc_goal = 0;
+ info->i_prealloc_count = 0;
+
+ /* Set to a conservative value. */
+ dn->last_page_partially_writable = 0;
+
+ if (S_ISCHR (st->st_mode) || S_ISBLK (st->st_mode))
+ st->st_rdev = di->i_block[0];
+ else
+ {
+ memcpy (info->i_data, di->i_block,
+ EXT2_N_BLOCKS * sizeof info->i_data[0]);
+ st->st_rdev = 0;
+ }
+
+ diskfs_end_catch_exception ();
+
+ if (S_ISREG (st->st_mode) || S_ISDIR (st->st_mode)
+ || (S_ISLNK (st->st_mode) && st->st_blocks))
+ {
+ unsigned offset;
+
+ np->allocsize = np->dn_stat.st_size;
+
+ /* Round up to a block multiple. */
+ offset = np->allocsize & ((1 << log2_block_size) - 1);
+ if (offset > 0)
+ np->allocsize += block_size - offset;
+ }
+ else
+ /* Allocsize should be zero for anything except directories, files, and
+ long symlinks. These are the only things allowed to have any blocks
+ allocated as well, although st_size may be zero for any type (cases
+ where st_blocks=0 and st_size>0 include fast symlinks, and, under
+ linux, some devices). */
+ np->allocsize = 0;
+
+ return 0;
+}
+
+/* Return EINVAL if this is not a hurd filesystem and any bits are set in L
+ except the low 16 bits, else 0. */
+static inline error_t
+check_high_bits (struct node *np, long l)
+{
+ if (sblock->s_creator_os == EXT2_OS_HURD)
+ return 0;
+ else
+ return ((l & ~0xFFFF) == 0) ? 0 : EINVAL;
+}
+
+/* Return 0 if NP's owner can be changed to UID; otherwise return an error
+ code. */
+error_t
+diskfs_validate_owner_change (struct node *np, uid_t uid)
+{
+ return check_high_bits (np, uid);
+}
+
+/* Return 0 if NP's group can be changed to GID; otherwise return an error
+ code. */
+error_t
+diskfs_validate_group_change (struct node *np, gid_t gid)
+{
+ return check_high_bits (np, gid);
+}
+
+/* Return 0 if NP's mode can be changed to MODE; otherwise return an error
+ code. It must always be possible to clear the mode; diskfs will not ask
+ for permission before doing so. */
+error_t
+diskfs_validate_mode_change (struct node *np, mode_t mode)
+{
+ return check_high_bits (np, mode);
+}
+
+/* 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 (sblock->s_creator_os == EXT2_OS_HURD)
+ return 0;
+ else
+ /* For non-hurd filesystems, the author & owner are the same. */
+ return (author == np->dn_stat.st_uid) ? 0 : EINVAL;
+}
+
+/* The user may define this function. Return 0 if NP's flags can be
+ changed to FLAGS; otherwise return an error code. It must always
+ be possible to clear the flags. */
+error_t
+diskfs_validate_flags_change (struct node *np, int flags)
+{
+ if (flags & ~(UF_NODUMP | UF_IMMUTABLE | UF_APPEND))
+ return EINVAL;
+ else
+ return 0;
+}
+
+/* Writes everything from NP's inode to the disk image, and returns a pointer
+ to it, or NULL if nothing need be done. */
+static struct ext2_inode *
+write_node (struct node *np)
+{
+ error_t err;
+ struct stat *st = &np->dn_stat;
+ struct ext2_inode *di = dino (np->cache_id);
+
+ if (np->dn->info.i_prealloc_count)
+ ext2_discard_prealloc (np);
+
+ assert (!np->dn_set_ctime && !np->dn_set_atime && !np->dn_set_mtime);
+ if (np->dn_stat_dirty)
+ {
+ struct ext2_inode_info *info = &np->dn->info;
+
+ assert (!diskfs_readonly);
+
+ ext2_debug ("writing inode %d to disk", np->cache_id);
+
+ err = diskfs_catch_exception ();
+ if (err)
+ return NULL;
+
+ di->i_generation = st->st_gen;
+
+ /* We happen to know that the stat mode bits are the same
+ as the ext2fs mode bits. */
+ /* XXX? */
+
+ /* Only the low 16 bits of these fields are standard across all ext2
+ implementations. */
+ di->i_mode = st->st_mode & 0xFFFF & ~S_ITRANS;
+ di->i_uid = st->st_uid & 0xFFFF;
+ di->i_gid = st->st_gid & 0xFFFF;
+
+ if (sblock->s_creator_os == EXT2_OS_HURD)
+ /* If this is a hurd-compatible filesystem, write the high bits too. */
+ {
+ di->i_mode_high = (st->st_mode >> 16) & 0xffff & ~S_ITRANS;
+ di->i_uid_high = st->st_uid >> 16;
+ di->i_gid_high = st->st_gid >> 16;
+ di->i_author = st->st_author;
+ }
+ else
+ /* No hurd extensions should be turned on. */
+ {
+ assert ((st->st_uid & ~0xFFFF) == 0);
+ assert ((st->st_gid & ~0xFFFF) == 0);
+ assert ((st->st_mode & ~0xFFFF) == 0);
+ assert (np->author_tracks_uid && st->st_author == st->st_uid);
+ }
+
+ di->i_links_count = st->st_nlink;
+ di->i_size = st->st_size;
+
+ di->i_atime = st->st_atime;
+ di->i_mtime = st->st_mtime;
+ di->i_ctime = st->st_ctime;
+#ifdef XXX
+ di->i_atime.ts_nsec = st->st_atime_usec * 1000;
+ di->i_mtime.ts_nsec = st->st_mtime_usec * 1000;
+ di->i_ctime.ts_nsec = st->st_ctime_usec * 1000;
+#endif
+
+ di->i_blocks = st->st_blocks;
+
+ /* Convert generic flags in ST->st_flags to ext2-specific flags in DI
+ (but don't mess with ext2 flags we don't know about). The original
+ set was copied from DI into INFO by read_node, but might have been
+ modified for ext2fs-specific reasons; so we use INFO->i_flags
+ to start with, and then apply the flags in ST->st_flags. */
+ info->i_flags &= ~(EXT2_APPEND_FL | EXT2_NODUMP_FL | EXT2_IMMUTABLE_FL);
+ if (st->st_flags & UF_APPEND)
+ info->i_flags |= EXT2_APPEND_FL;
+ if (st->st_flags & UF_NODUMP)
+ info->i_flags |= EXT2_NODUMP_FL;
+ if (st->st_flags & UF_IMMUTABLE)
+ info->i_flags |= EXT2_IMMUTABLE_FL;
+ di->i_flags = info->i_flags;
+
+ if (!(st->st_mode & S_IPTRANS) && sblock->s_creator_os == EXT2_OS_HURD)
+ di->i_translator = 0;
+
+ /* Set dtime non-zero to indicate a deleted file. */
+ di->i_dtime = (st->st_mode ? 0 : di->i_mtime);
+
+ if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode))
+ di->i_block[0] = st->st_rdev;
+ else
+ {
+ int block;
+ for (block = 0; block < EXT2_N_BLOCKS; block++)
+ di->i_block[block] = np->dn->info.i_data[block];
+ }
+
+ diskfs_end_catch_exception ();
+ np->dn_stat_dirty = 0;
+
+ return di;
+ }
+ else
+ return NULL;
+}
+
+/* Reload all data specific to NODE from disk, without writing anything.
+ Always called with DISKFS_READONLY true. */
+error_t
+diskfs_node_reload (struct node *node)
+{
+ struct disknode *dn = node->dn;
+
+ if (dn->dirents)
+ {
+ free (dn->dirents);
+ dn->dirents = 0;
+ }
+ pokel_flush (&dn->indir_pokel);
+ flush_node_pager (node);
+ read_node (node);
+
+ return 0;
+}
+
+/* For each active node, call FUN. The node is to be locked around the call
+ to FUN. If FUN returns non-zero for any node, then immediately stop, and
+ return that value. */
+error_t
+diskfs_node_iterate (error_t (*fun)(struct node *))
+{
+ error_t err = 0;
+ int n, num_nodes = 0;
+ struct node *node, **node_list, **p;
+
+ spin_lock (&diskfs_node_refcnt_lock);
+
+ /* We must copy everything from the hash table into another data structure
+ to avoid running into any problems with the hash-table being modified
+ during processing (normally we delegate access to hash-table with
+ diskfs_node_refcnt_lock, but we can't hold this while locking the
+ individual node locks). */
+
+ for (n = 0; n < INOHSZ; n++)
+ for (node = nodehash[n]; node; node = node->dn->hnext)
+ num_nodes++;
+
+ node_list = alloca (num_nodes * sizeof (struct node *));
+ p = node_list;
+ for (n = 0; n < INOHSZ; n++)
+ for (node = nodehash[n]; node; node = node->dn->hnext)
+ {
+ *p++ = node;
+ node->references++;
+ }
+
+ spin_unlock (&diskfs_node_refcnt_lock);
+
+ p = node_list;
+ while (num_nodes-- > 0)
+ {
+ node = *p++;
+ if (!err)
+ {
+ mutex_lock (&node->lock);
+ err = (*fun)(node);
+ mutex_unlock (&node->lock);
+ }
+ diskfs_nrele (node);
+ }
+
+ return err;
+}
+
+/* Write all active disknodes into the ext2_inode pager. */
+void
+write_all_disknodes ()
+{
+ error_t write_one_disknode (struct node *node)
+ {
+ struct ext2_inode *di;
+
+ diskfs_set_node_times (node);
+
+ /* Sync the indirect blocks here; they'll all be done before any
+ inodes. Waiting for them shouldn't be too bad. */
+ pokel_sync (&node->dn->indir_pokel, 1);
+
+ /* Update the inode image. */
+ di = write_node (node);
+ if (di)
+ record_global_poke (di);
+
+ return 0;
+ }
+
+ diskfs_node_iterate (write_one_disknode);
+}
+
+/* Sync the info in NP->dn_stat and any associated format-specific
+ information to disk. If WAIT is true, then return only after the
+ physicial media has been completely updated. */
+void
+diskfs_write_disknode (struct node *np, int wait)
+{
+ struct ext2_inode *di = write_node (np);
+ if (di)
+ {
+ if (wait)
+ sync_global_ptr (di, 1);
+ else
+ record_global_poke (di);
+ }
+}
+
+/* Set *ST with appropriate values to reflect the current state of the
+ filesystem. */
+error_t
+diskfs_set_statfs (struct statfs *st)
+{
+ st->f_type = FSTYPE_EXT2FS;
+ st->f_bsize = block_size;
+ st->f_blocks = sblock->s_blocks_count;
+ st->f_bfree = sblock->s_free_blocks_count;
+ st->f_bavail = st->f_bfree - sblock->s_r_blocks_count;
+ st->f_files = sblock->s_inodes_count;
+ st->f_ffree = sblock->s_free_inodes_count;
+ st->f_fsid = getpid ();
+ st->f_namelen = 0;
+ st->f_favail = st->f_ffree;
+ st->f_frsize = frag_size;
+ 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, unsigned namelen,
+ struct protid *cred)
+{
+ daddr_t blkno;
+ error_t err;
+ char buf[block_size];
+ struct ext2_inode *di;
+
+ assert (!diskfs_readonly);
+
+ if (sblock->s_creator_os != EXT2_OS_HURD)
+ return EOPNOTSUPP;
+
+ if (namelen + 2 > block_size)
+ return ENAMETOOLONG;
+
+ err = diskfs_catch_exception ();
+ if (err)
+ return err;
+
+ di = dino (np->cache_id);
+ blkno = di->i_translator;
+
+ if (namelen && !blkno)
+ {
+ /* Allocate block for translator */
+ blkno =
+ ext2_new_block ((np->dn->info.i_block_group
+ * EXT2_BLOCKS_PER_GROUP (sblock))
+ + sblock->s_first_data_block,
+ 0, 0, 0);
+ if (blkno == 0)
+ {
+ diskfs_end_catch_exception ();
+ return ENOSPC;
+ }
+
+ di->i_translator = blkno;
+ record_global_poke (di);
+
+ np->dn_stat.st_blocks += 1 << log2_stat_blocks_per_fs_block;
+ np->dn_set_ctime = 1;
+ }
+ else if (!namelen && blkno)
+ {
+ /* Clear block for translator going away. */
+ di->i_translator = 0;
+ record_global_poke (di);
+ ext2_free_blocks (blkno, 1);
+
+ np->dn_stat.st_blocks -= 1 << log2_stat_blocks_per_fs_block;
+ np->dn_stat.st_mode &= ~S_IPTRANS;
+ np->dn_set_ctime = 1;
+ }
+
+ if (namelen)
+ {
+ buf[0] = namelen & 0xFF;
+ buf[1] = (namelen >> 8) & 0xFF;
+ bcopy (name, buf + 2, namelen);
+
+ bcopy (buf, bptr (blkno), block_size);
+ record_global_poke (bptr (blkno));
+
+ 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, unsigned *namelen)
+{
+ error_t err;
+ daddr_t blkno;
+ unsigned datalen;
+ void *transloc;
+
+ assert (sblock->s_creator_os == EXT2_OS_HURD);
+
+ err = diskfs_catch_exception ();
+ if (err)
+ return err;
+
+ blkno = (dino (np->cache_id))->i_translator;
+ assert (blkno);
+ transloc = bptr (blkno);
+
+ datalen =
+ ((unsigned char *)transloc)[0] + (((unsigned char *)transloc)[1] << 8);
+ *namep = malloc (datalen);
+ bcopy (transloc + 2, *namep, datalen);
+
+ diskfs_end_catch_exception ();
+
+ *namelen = datalen;
+ return 0;
+}
+
+/* The maximum size of a symlink store in the inode (including '\0'). */
+#define MAX_INODE_SYMLINK \
+ (EXT2_N_BLOCKS * sizeof (((struct ext2_inode *)0)->i_block[0]))
+
+/* Write an in-inode symlink, or return EINVAL if we can't. */
+static error_t
+write_symlink (struct node *node, const char *target)
+{
+ size_t len = strlen (target) + 1;
+
+ if (len > MAX_INODE_SYMLINK)
+ return EINVAL;
+
+ assert (node->dn_stat.st_blocks == 0);
+
+ bcopy (target, node->dn->info.i_data, len);
+ node->dn_stat.st_size = len - 1;
+ node->dn_set_ctime = 1;
+ node->dn_set_mtime = 1;
+
+ return 0;
+}
+
+/* Read an in-inode symlink, or return EINVAL if we can't. */
+static error_t
+read_symlink (struct node *node, char *target)
+{
+ if (node->dn_stat.st_blocks)
+ return EINVAL;
+
+ assert (node->dn_stat.st_size < MAX_INODE_SYMLINK);
+
+ bcopy (node->dn->info.i_data, target, node->dn_stat.st_size);
+ return 0;
+}
+
+/* If this function is nonzero (and diskfs_shortcut_symlink is set) it
+ is called to set a symlink. If it returns EINVAL or isn't set,
+ then the normal method (writing the contents into the file data) is
+ used. If it returns any other error, it is returned to the user. */
+error_t (*diskfs_create_symlink_hook)(struct node *np, const char *target) =
+ write_symlink;
+
+/* If this function is nonzero (and diskfs_shortcut_symlink is set) it
+ is called to read the contents of a symlink. If it returns EINVAL or
+ isn't set, then the normal method (reading from the file data) is
+ used. If it returns any other error, it is returned to the user. */
+error_t (*diskfs_read_symlink_hook)(struct node *np, char *target) =
+ read_symlink;
+
+/* 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 */
+}