aboutsummaryrefslogtreecommitdiff
path: root/ext2fs/inode.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext2fs/inode.c')
-rw-r--r--ext2fs/inode.c378
1 files changed, 269 insertions, 109 deletions
diff --git a/ext2fs/inode.c b/ext2fs/inode.c
index 7704edbd..f25cc1fa 100644
--- a/ext2fs/inode.c
+++ b/ext2fs/inode.c
@@ -1,8 +1,9 @@
/* Inode management routines
- Copyright (C) 1994, 1995, 1996 Free Software Foundation, Inc.
+ Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2007
+ Free Software Foundation, Inc.
- Converted for ext2fs by Miles Bader <miles@gnu.ai.mit.edu>
+ 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
@@ -22,6 +23,21 @@
#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)
@@ -32,7 +48,7 @@
static struct node *nodehash[INOHSZ];
-static error_t read_disknode (struct node *np);
+static error_t read_node (struct node *np);
spin_lock_t generation_lock = SPIN_LOCK_INITIALIZER;
@@ -45,10 +61,10 @@ inode_init ()
nodehash[n] = 0;
}
-/* Fetch inode INUM, set *NPP to the node structure;
+/* 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
+diskfs_cached_lookup (ino_t inum, struct node **npp)
{
error_t err;
struct node *np;
@@ -64,7 +80,7 @@ diskfs_cached_lookup (int inum, struct node **npp)
*npp = np;
return 0;
}
-
+
/* Format specific data for the new node. */
dn = malloc (sizeof (struct disknode));
if (! dn)
@@ -73,9 +89,10 @@ diskfs_cached_lookup (int inum, struct node **npp)
return ENOMEM;
}
dn->dirents = 0;
+ dn->dir_idx = 0;
dn->pager = 0;
rwlock_init (&dn->alloc_lock);
- pokel_init (&dn->indir_pokel, disk_pager, disk_image);
+ pokel_init (&dn->indir_pokel, diskfs_disk_pager, disk_image);
/* Create the new node. */
np = diskfs_make_node (dn);
@@ -91,10 +108,10 @@ diskfs_cached_lookup (int inum, struct node **npp)
nodehash[INOHASH(inum)] = np;
spin_unlock (&diskfs_node_refcnt_lock);
-
+
/* Get the contents of NP off disk. */
- err = read_disknode (np);
-
+ err = read_node (np);
+
if (!diskfs_check_readonly () && !np->dn_stat.st_gen)
{
spin_lock (&generation_lock);
@@ -104,7 +121,7 @@ diskfs_cached_lookup (int inum, struct node **npp)
spin_unlock (&generation_lock);
np->dn_set_ctime = 1;
}
-
+
if (err)
return err;
else
@@ -120,13 +137,13 @@ 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;
@@ -136,7 +153,7 @@ ifind (ino_t inum)
/* The last reference to a node has gone away; drop
it from the hash table and clean all state in the dn structure. */
-void
+void
diskfs_node_norefs (struct node *np)
{
*np->dn->hprevp = np->dn->hnext;
@@ -179,65 +196,77 @@ diskfs_new_hardrefs (struct node *np)
/* Read stat information out of the ext2_inode. */
static error_t
-read_disknode (struct node *np)
+read_node (struct node *np)
{
error_t err;
- unsigned offset;
- 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;
- np->istranslated = sblock->s_creator_os == EXT2_OS_HURD && di->i_translator;
-
- if (!fsidset)
- {
- fsid = getpid ();
- fsidset = 1;
- }
-
st->st_fstype = FSTYPE_EXT2FS;
- st->st_fsid = fsid;
+ st->st_fsid = getpid (); /* This call is very cheap. */
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_version;
+ 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;
+ st->st_atim.tv_sec = di->i_atime;
+#ifdef not_yet
+ /* ``struct ext2_inode'' doesn't do better than sec. precision yet. */
+#else
+ st->st_atim.tv_nsec = 0;
+#endif
+ st->st_mtim.tv_sec = di->i_mtime;
+#ifdef not_yet
+ /* ``struct ext2_inode'' doesn't do better than sec. precision yet. */
+#else
+ st->st_mtim.tv_nsec = 0;
+#endif
+ st->st_ctim.tv_sec = di->i_ctime;
+#ifdef not_yet
+ /* ``struct ext2_inode'' doesn't do better than sec. precision yet. */
+#else
+ st->st_ctim.tv_nsec = 0;
#endif
st->st_blocks = di->i_blocks;
- st->st_flags = di->i_flags;
-
+
+ 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;
+ 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. */
@@ -248,32 +277,57 @@ read_disknode (struct node *np)
info->i_frag_size = di->i_fsize;
info->i_osync = 0;
info->i_file_acl = di->i_file_acl;
- info->i_dir_acl = di->i_dir_acl;
- info->i_version = di->i_version;
+ 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 %Ld", 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;
- if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode))
+ /* 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
{
- int block;
- for (block = 0; block < EXT2_N_BLOCKS; block++)
- info->i_data[block] = di->i_block[block];
+ memcpy (info->i_data, di->i_block,
+ EXT2_N_BLOCKS * sizeof info->i_data[0]);
st->st_rdev = 0;
}
+ dn->info_i_translator = di->i_translator;
diskfs_end_catch_exception ();
- /* Set these to conservative values. */
- dn->last_page_partially_writable = 0;
-
- np->allocsize = np->dn_stat.st_size;
- offset = np->allocsize & ((1 << log2_block_size) - 1);
- if (offset > 0)
- np->allocsize += block_size - offset;
+ 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;
}
@@ -285,8 +339,17 @@ 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;
+
+ /* Linux 2.3.42 has a mount-time option (not a bit stored on disk)
+ NO_UID32 to ignore the high 16 bits of uid and gid, but by default
+ allows them. It also does this check for "interoperability with old
+ kernels". Note that our check refuses to change the values, while
+ Linux 2.3.42 just silently clears the high bits in an inode it updates,
+ even if it was updating it for an unrelated reason. */
+ if (np->dn->info.i_dtime != 0)
+ return 0;
+
+ return ((l & ~0xFFFF) == 0) ? 0 : EINVAL;
}
/* Return 0 if NP's owner can be changed to UID; otherwise return an error
@@ -322,10 +385,22 @@ 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 auther & owner are the same. */
+ /* 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 *
@@ -337,10 +412,11 @@ write_node (struct node *np)
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);
@@ -348,8 +424,8 @@ write_node (struct node *np)
err = diskfs_catch_exception ();
if (err)
return NULL;
-
- di->i_version = st->st_gen;
+
+ di->i_generation = st->st_gen;
/* We happen to know that the stat mode bits are the same
as the ext2fs mode bits. */
@@ -357,14 +433,14 @@ write_node (struct node *np)
/* Only the low 16 bits of these fields are standard across all ext2
implementations. */
- di->i_mode = st->st_mode & 0xFFFF;
+ 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;
+ 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;
@@ -375,39 +451,57 @@ write_node (struct node *np)
assert ((st->st_uid & ~0xFFFF) == 0);
assert ((st->st_gid & ~0xFFFF) == 0);
assert ((st->st_mode & ~0xFFFF) == 0);
- assert (st->st_author == st->st_uid);
+ 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;
- di->i_flags = st->st_flags;
-
- if (!np->istranslated && sblock->s_creator_os == EXT2_OS_HURD)
- di->i_translator = 0;
+ di->i_atime = st->st_atim.tv_sec;
+#ifdef not_yet
+ /* ``struct ext2_inode'' doesn't do better than sec. precision yet. */
+ di->i_atime.tv_nsec = st->st_atim.tv_nsec;
+#endif
+ di->i_mtime = st->st_mtim.tv_sec;
+#ifdef not_yet
+ di->i_mtime.tv_nsec = st->st_mtim.tv_nsec;
+#endif
+ di->i_ctime = st->st_ctim.tv_sec;
+#ifdef not_yet
+ di->i_ctime.tv_nsec = st->st_ctim.tv_nsec;
+#endif
- /* Set dtime non-zero to indicate a deleted file. */
- di->i_dtime = (st->st_mode ? 0 : di->i_mtime);
+ /* 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 == 0)
+ /* Set dtime non-zero to indicate a deleted file.
+ We don't clear i_size, i_blocks, and i_translator in this case,
+ to give "undeletion" utilities a chance. */
+ di->i_dtime = di->i_mtime;
+ else
+ {
+ di->i_dtime = 0;
+ di->i_size = st->st_size;
+ di->i_blocks = st->st_blocks;
+ }
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];
- }
-
+ memcpy (di->i_block, np->dn->info.i_data,
+ EXT2_N_BLOCKS * sizeof di->i_block[0]);
+
diskfs_end_catch_exception ();
np->dn_stat_dirty = 0;
@@ -431,7 +525,7 @@ diskfs_node_reload (struct node *node)
}
pokel_flush (&dn->indir_pokel);
flush_node_pager (node);
- read_disknode (node);
+ read_node (node);
return 0;
}
@@ -445,7 +539,7 @@ 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
@@ -493,12 +587,12 @@ write_all_disknodes ()
{
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);
+ diskfs_set_node_times (node);
+
/* Update the inode image. */
di = write_node (node);
if (di)
@@ -518,10 +612,12 @@ 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);
+ {
+ if (wait)
+ sync_global_ptr (di, 1);
+ else
+ record_global_poke (di);
+ }
}
/* Set *ST with appropriate values to reflect the current state of the
@@ -534,17 +630,21 @@ diskfs_set_statfs (struct statfs *st)
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;
+ if (st->f_bfree < sblock->s_r_blocks_count)
+ st->f_bavail = 0;
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, char *name, unsigned namelen,
+diskfs_set_translator (struct node *np, const char *name, unsigned namelen,
struct protid *cred)
{
daddr_t blkno;
@@ -563,10 +663,10 @@ diskfs_set_translator (struct node *np, char *name, unsigned namelen,
err = diskfs_catch_exception ();
if (err)
return err;
-
+
di = dino (np->cache_id);
blkno = di->i_translator;
-
+
if (namelen && !blkno)
{
/* Allocate block for translator */
@@ -574,14 +674,15 @@ diskfs_set_translator (struct node *np, char *name, unsigned namelen,
ext2_new_block ((np->dn->info.i_block_group
* EXT2_BLOCKS_PER_GROUP (sblock))
+ sblock->s_first_data_block,
- 0, 0);
+ 0, 0, 0);
if (blkno == 0)
{
diskfs_end_catch_exception ();
return ENOSPC;
}
-
+
di->i_translator = blkno;
+ np->dn->info_i_translator = blkno;
record_global_poke (di);
np->dn_stat.st_blocks += 1 << log2_stat_blocks_per_fs_block;
@@ -591,14 +692,15 @@ diskfs_set_translator (struct node *np, char *name, unsigned namelen,
{
/* Clear block for translator going away. */
di->i_translator = 0;
+ np->dn->info_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->istranslated = 0;
+ np->dn_stat.st_mode &= ~S_IPTRANS;
np->dn_set_ctime = 1;
}
-
+
if (namelen)
{
buf[0] = namelen & 0xFF;
@@ -608,10 +710,10 @@ diskfs_set_translator (struct node *np, char *name, unsigned namelen,
bcopy (buf, bptr (blkno), block_size);
record_global_poke (bptr (blkno));
- np->istranslated = 1;
+ np->dn_stat.st_mode |= S_IPTRANS;
np->dn_set_ctime = 1;
}
-
+
diskfs_end_catch_exception ();
return err;
}
@@ -621,10 +723,10 @@ diskfs_set_translator (struct node *np, char *name, unsigned namelen,
error_t
diskfs_get_translator (struct node *np, char **namep, unsigned *namelen)
{
- error_t err;
+ error_t err = 0;
daddr_t blkno;
unsigned datalen;
- void *transloc;
+ const void *transloc;
assert (sblock->s_creator_os == EXT2_OS_HURD);
@@ -635,18 +737,76 @@ diskfs_get_translator (struct node *np, char **namep, unsigned *namelen)
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);
+ if (datalen > block_size - 2)
+ err = EFTYPE; /* ? */
+ else
+ {
+ *namep = malloc (datalen);
+ if (!*namep)
+ err = ENOMEM;
+ else
+ memcpy (*namep, transloc + 2, datalen);
+ }
diskfs_end_catch_exception ();
*namelen = datalen;
+ return err;
+}
+
+/* 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 ()