/* Copyright (C) 1997, 1998, 2002, 2007 Free Software Foundation, Inc. Written by Thomas Bushnell, n/BSG. This file is part of the GNU Hurd. The GNU Hurd 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. The GNU Hurd 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */ #include #include #include #include "isofs.h" #include "libdiskfs/fs_S.h" /* There is no such thing as an inode in this format, all such information being recorded in the directory entry. So we report inode numbers as absolute offsets from DISK_IMAGE. We use the directory record for symlinks and zero length files, and file_start otherwise. Only for hard links to zero length files we get extra inodes. */ #define INOHSZ 512 #if ((INOHSZ&(INOHSZ-1)) == 0) #define INOHASH(ino) ((ino>>8)&(INOHSZ-1)) #else #define INOHASH(ino) (((unsigned)(ino>>8))%INOHSZ) #endif struct node_cache { struct dirrect *dr; /* somewhere in disk_image */ off_t file_start; /* start of file */ off_t id; /* UNIQUE identifier. */ struct node *np; /* if live */ }; /* The node_cache is a cache of nodes. Access to node_cache, node_cache_size, and node_cache_alloced is protected by nodecache_lock. Every node in the node_cache carries a light reference. When we are asked to give up that light reference, we reacquire our lock momentarily to check whether someone else reacquired a reference through the node_cache. */ static int node_cache_size = 0; static int node_cache_alloced = 0; struct node_cache *node_cache = 0; static pthread_rwlock_t nodecache_lock = PTHREAD_RWLOCK_INITIALIZER; /* Forward */ static error_t read_disknode (struct node *, struct dirrect *, struct rrip_lookup *); /* Lookup node with id ID. Returns NULL if the node is not found in the node cache. */ static struct node * lookup (off_t id) { int i; for (i = 0; i < node_cache_size; i++) if (node_cache[i].id == id && node_cache[i].np) return node_cache[i].np; return NULL; } /* See if node with identifier ID is in the cache. If so, return it, with one additional reference. nodecache_lock must be held on entry to the call, and will be released iff the node was found in the cache. */ void inode_cache_find (off_t id, struct node **npp) { *npp = lookup (id); if (*npp) { diskfs_nref (*npp); pthread_rwlock_unlock (&nodecache_lock); pthread_mutex_lock (&(*npp)->lock); } } /* Determine if we use file_start or struct dirrect * as node id. */ int use_file_start_id (struct dirrect *record, struct rrip_lookup *rr) { /* If it is a symlink or a zero length file, don't use file_start. */ if (rr->valid & VALID_SL || isonum_733 (record->size) == 0) return 0; return 1; } /* Enter NP into the cache. The directory entry we used is DR, the cached Rock-Ridge info RR. nodecache_lock must be held. */ void cache_inode (struct node *np, struct dirrect *record, struct rrip_lookup *rr) { int i; struct node_cache *c = 0; off_t id; if (use_file_start_id (record, rr)) id = np->dn->file_start << store->log2_block_size; else id = (off_t) ((void *) record - (void *) disk_image); /* First see if there's already an entry. */ for (i = 0; i < node_cache_size; i++) if (node_cache[i].id == id) break; if (i == node_cache_size) { if (node_cache_size >= node_cache_alloced) { if (!node_cache_alloced) { /* Initialize */ node_cache_alloced = 10; node_cache = malloc (sizeof (struct node_cache) * 10); } else { node_cache_alloced *= 2; node_cache = realloc (node_cache, sizeof (struct node_cache) * node_cache_alloced); } assert (node_cache); } node_cache_size++; } c = &node_cache[i]; c->id = id; c->dr = record; c->file_start = np->dn->file_start; diskfs_nref_light (np); c->np = np; /* PLUS 1 so that we don't store zero cache ID's (not allowed by diskfs) */ np->cache_id = i + 1; } /* Fetch inode with cache id ID; set *NPP to the node structure; gain one user reference and lock the node. */ error_t diskfs_cached_lookup (ino_t id, struct node **npp) { struct node *np; error_t err; /* Cache ID's are incremented when presented to diskfs to avoid presenting zero cache ID's. */ id--; pthread_rwlock_rdlock (&nodecache_lock); assert (id < node_cache_size); np = node_cache[id].np; if (!np) { struct node_cache *c = &node_cache[id]; struct rrip_lookup rr; struct disknode *dn; pthread_rwlock_unlock (&nodecache_lock); rrip_lookup (node_cache[id].dr, &rr, 1); /* We should never cache the wrong directory entry */ assert (!(rr.valid & VALID_CL)); dn = malloc (sizeof (struct disknode)); if (!dn) { pthread_rwlock_unlock (&nodecache_lock); release_rrip (&rr); return ENOMEM; } dn->fileinfo = 0; dn->dr = c->dr; dn->file_start = c->file_start; np = diskfs_make_node (dn); if (!np) { free (dn); pthread_rwlock_unlock (&nodecache_lock); release_rrip (&rr); return ENOMEM; } np->cache_id = id + 1; /* see above for rationale for increment */ pthread_mutex_lock (&np->lock); pthread_rwlock_wrlock (&nodecache_lock); if (c->np != NULL) { /* We lost a race. */ diskfs_nput (np); np = c->np; goto gotit; } c->np = np; diskfs_nref_light (np); pthread_rwlock_unlock (&nodecache_lock); err = read_disknode (np, dn->dr, &rr); if (!err) *npp = np; release_rrip (&rr); return err; } gotit: diskfs_nref (np); pthread_rwlock_unlock (&nodecache_lock); pthread_mutex_lock (&np->lock); *npp = np; return 0; } /* Return Epoch-based time from a seven byte according to 9.1.5 */ char * isodate_915 (char *c, struct timespec *ts) { struct tm tm; signed char tz; /* Copy into a struct TM. */ tm.tm_year = *c++; tm.tm_mon = *c++ - 1; tm.tm_mday = *c++; tm.tm_hour = *c++; tm.tm_min = *c++; tm.tm_sec = *c++; tz = *c++; tm.tm_isdst = 0; ts->tv_sec = timegm (&tm); ts->tv_nsec = 0; /* Only honor TZ offset if it makes sense */ if (-48 <= tz && tz <= 52) ts->tv_sec -= 15 * 60 * tz; /* TZ is in fifteen minute chunks */ return c; } /* Return Epoch-based time from a seventeen byte according to 8.4.26.1 */ char * isodate_84261 (char *c, struct timespec *ts) { struct tm tm; int hsec; signed char tz; sscanf (c, "%4d%2d%2d%2d%2d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &hsec); /* Convert to appropriate units */ ts->tv_nsec = hsec * 10000000; tm.tm_year -= 1900; tm.tm_mon--; tm.tm_isdst = 0; ts->tv_sec = timegm (&tm); tz = c[16]; /* Only honor TZ offset if it makes sense */ if (-48 <= tz && tz <= 52) ts->tv_sec -= 15 * 60 * tz; /* TZ is in fifteen minute chunks */ return c + 17; } /* Calculate the file start (in store blocks) of the file at RECORD. */ error_t calculate_file_start (struct dirrect *record, off_t *file_start, struct rrip_lookup *rr) { error_t err; if (rr && (rr->valid & VALID_CL)) { *file_start = (void *) rr->realdirent - (void *)disk_image; *file_start >>= store->log2_block_size; } else if (rr && (rr->valid & VALID_PL)) *file_start = rr->realfilestart; else { err = diskfs_catch_exception (); if (err) return err; *file_start = ((isonum_733 (record->extent) + record->ext_attr_len) * (logical_block_size / store->block_size)); diskfs_end_catch_exception (); } return 0; } /* Load the inode with directory entry RECORD and cached Rock-Ridge info RR into NP. The directory entry is at OFFSET in BLOCK. */ error_t load_inode (struct node **npp, struct dirrect *record, struct rrip_lookup *rr) { error_t err; off_t file_start; struct disknode *dn; struct node *np, *tmp; off_t id; err = calculate_file_start (record, &file_start, rr); if (err) return err; if (rr->valid & VALID_CL) record = rr->realdirent; /* First check the cache */ if (use_file_start_id (record, rr)) id = file_start << store->log2_block_size; else id = (off_t) ((void *) record - (void *) disk_image); pthread_rwlock_rdlock (&nodecache_lock); inode_cache_find (id, npp); pthread_rwlock_unlock (&nodecache_lock); if (*npp) return 0; /* Create a new node */ dn = malloc (sizeof (struct disknode)); if (!dn) return ENOMEM; dn->fileinfo = 0; dn->dr = record; dn->file_start = file_start; np = diskfs_make_node (dn); if (!np) { free (dn); return ENOMEM; } pthread_mutex_lock (&np->lock); pthread_rwlock_wrlock (&nodecache_lock); tmp = lookup (id); if (tmp) { /* We lost a race. */ diskfs_nput (np); diskfs_nref (tmp); *npp = tmp; pthread_rwlock_unlock (&nodecache_lock); return 0; } cache_inode (np, record, rr); pthread_rwlock_unlock (&nodecache_lock); err = read_disknode (np, record, rr); *npp = np; return err; } /* Read stat information from the directory entry at DR and the contents of RL. */ static error_t read_disknode (struct node *np, struct dirrect *dr, struct rrip_lookup *rl) { error_t err; struct stat *st = &np->dn_stat; st->st_fstype = FSTYPE_ISO9660; st->st_fsid = getpid (); if (use_file_start_id (dr, rl)) st->st_ino = (ino_t) np->dn->file_start << store->log2_block_size; else st->st_ino = (ino_t) ((void *) dr - (void *) disk_image); st->st_gen = 0; st->st_rdev = 0; err = diskfs_catch_exception (); if (err) return err; if (rl->valid & VALID_PX) { if ((rl->valid & VALID_MD) == 0) st->st_mode = rl->mode; st->st_nlink = rl->nlink; st->st_uid = rl->uid; st->st_gid = rl->gid; } else { if ((rl->valid & VALID_MD) == 0) { /* If there are no periods, it's a directory. */ if (((rl->valid & VALID_NM) && !index (rl->name, '.')) || (!(rl->valid & VALID_NM) && !memchr (dr->name, '.', dr->namelen))) st->st_mode = S_IFDIR | 0777; else st->st_mode = S_IFREG | 0666; } st->st_nlink = 1; st->st_uid = 0; st->st_gid = 0; } if (rl->valid & VALID_MD) st->st_mode = rl->allmode; if (rl->valid & VALID_AU) st->st_author = rl->author; else st->st_author = st->st_gid; st->st_size = isonum_733 (dr->size); if ((rl->valid & VALID_PN) && (S_ISCHR (st->st_mode) || S_ISBLK (st->st_mode))) st->st_rdev = rl->rdev; else st->st_rdev = 0; if (dr->ileave) /* XXX ??? */ st->st_size = 0; /* Calculate these if we'll need them */ if (!(rl->valid & VALID_TF) || ((rl->tfflags & (TF_CREATION|TF_ACCESS|TF_MODIFY)) != (TF_CREATION|TF_ACCESS|TF_MODIFY))) { struct timespec ts; isodate_915 ((char *) dr->date, &ts); st->st_ctim = st->st_mtim = st->st_atim = ts; } /* Override what we have better info for */ if (rl->valid & VALID_TF) { if (rl->tfflags & TF_CREATION) st->st_ctim = rl->ctime; if (rl->tfflags & TF_ACCESS) st->st_atim = rl->atime; if (rl->tfflags & TF_MODIFY) st->st_mtim = rl->mtime; } st->st_blksize = logical_block_size; st->st_blocks = (st->st_size - 1) / 512 + 1; if (rl->valid & VALID_FL) st->st_flags = rl->flags; else st->st_flags = 0; if (S_ISLNK (st->st_mode)) { if (rl->valid & VALID_SL) { np->dn->link_target = rl->target; rl->target = 0; st->st_size = strlen (np->dn->link_target); } else { st->st_mode &= ~S_IFMT; st->st_mode |= S_IFREG; } } if (rl->valid & VALID_TR) { st->st_mode |= S_IPTRANS; np->dn->translen = rl->translen; np->dn->translator = rl->trans; rl->trans = 0; } else { np->dn->translator = 0; np->dn->translen = 0; } diskfs_end_catch_exception (); return 0; } /* Symlink targets are never stored in files, so always use this. */ static error_t read_symlink_hook (struct node *np, char *buf) { memcpy (buf, np->dn->link_target, np->dn_stat.st_size + 1); return 0; } error_t (*diskfs_read_symlink_hook) (struct node *, char *) = read_symlink_hook; /* The last reference to NP has gone away; drop it from the cache and clean all state in the dn structure. */ void diskfs_node_norefs (struct node *np) { if (np->dn->translator) free (np->dn->translator); 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) { pthread_rwlock_wrlock (&nodecache_lock); if (np->cache_id != 0) { assert (node_cache[np->cache_id - 1].np == np); /* Check if someone reacquired a reference through the node_cache. */ struct references result; refcounts_references (&np->refcounts, &result); if (result.hard > 0) { /* A reference was reacquired through a hash table lookup. It's fine, we didn't touch anything yet. */ pthread_rwlock_unlock (&nodecache_lock); return; } node_cache[np->cache_id - 1].np = 0; np->cache_id = 0; diskfs_nrele_light (np); } pthread_rwlock_unlock (&nodecache_lock); drop_pager_softrefs (np); } void diskfs_lost_hardrefs (struct node *np) { } void diskfs_new_hardrefs (struct node *np) { allow_pager_softrefs (np); } error_t diskfs_truncate (struct node *np, off_t length) { return EROFS; } error_t diskfs_grow (struct node *np, off_t end, struct protid *cred) { return EROFS; } error_t diskfs_set_translator (struct node *np, const char *name, u_int namelen, struct protid *cred) { return EROFS; } error_t diskfs_get_translator (struct node *np, char **namep, u_int *namelen) { return EOPNOTSUPP; } void diskfs_shutdown_soft_ports () { /* Should initiate termination of internally held pager ports (the only things that should be soft) XXX */ } error_t diskfs_node_reload (struct node *node) { /* Never necessary on a read-only medium */ return 0; } error_t diskfs_validate_author_change (struct node *np, uid_t author) { return EROFS; } error_t diskfs_node_iterate (error_t (*fun)(struct node *)) { /* We never actually have to do anything, because this function is only used for things that have to do with read-write media. */ return 0; } void diskfs_write_disknode (struct node *np, int wait) { } error_t diskfs_set_statfs (struct statfs *st) { /* There is no easy way to determine the number of files on an ISO 9660 filesystem. */ memset (st, 0, sizeof *st); st->f_type = FSTYPE_ISO9660; st->f_bsize = logical_block_size; st->f_blocks = isonum_733 (sblock->vol_sp_size); st->f_fsid = getpid (); st->f_frsize = logical_block_size; return 0; } 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) { /* XXX */ return EOPNOTSUPP; } void diskfs_free_node (struct node *no, mode_t mode) { abort (); } error_t diskfs_alloc_node (struct node *dp, mode_t mode, struct node **np) { return EROFS; }