diff options
Diffstat (limited to 'ext2fs/inode.c')
-rw-r--r-- | ext2fs/inode.c | 378 |
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 () |