diff options
Diffstat (limited to 'tmpfs')
-rw-r--r-- | tmpfs/Makefile | 29 | ||||
-rw-r--r-- | tmpfs/dir.c | 307 | ||||
-rw-r--r-- | tmpfs/node.c | 590 | ||||
-rw-r--r-- | tmpfs/pager-stubs.c | 88 | ||||
-rw-r--r-- | tmpfs/tmpfs.c | 402 | ||||
-rw-r--r-- | tmpfs/tmpfs.h | 85 |
6 files changed, 1501 insertions, 0 deletions
diff --git a/tmpfs/Makefile b/tmpfs/Makefile new file mode 100644 index 00000000..18293771 --- /dev/null +++ b/tmpfs/Makefile @@ -0,0 +1,29 @@ +# Makefile for tmpfs +# +# Copyright (C) 2000,01,02 Free Software Foundation, Inc. +# +# 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. + +dir := tmpfs +makemode := server + +target = tmpfs +SRCS = tmpfs.c node.c dir.c pager-stubs.c +OBJS = $(SRCS:.c=.o) default_pagerUser.o +LCLHDRS = tmpfs.h +# XXX The shared libdiskfs requires libstore even though we don't use it here. +HURDLIBS = diskfs pager iohelp fshelp store threads ports ihash shouldbeinlibc + +include ../Makeconf diff --git a/tmpfs/dir.c b/tmpfs/dir.c new file mode 100644 index 00000000..4a0a60b5 --- /dev/null +++ b/tmpfs/dir.c @@ -0,0 +1,307 @@ +/* Directories for tmpfs. + Copyright (C) 2000,01,02 Free Software Foundation, Inc. + +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 the GNU Hurd; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include <stddef.h> +#include <unistd.h> +#include <sys/mman.h> + +#include "tmpfs.h" +#include <stdlib.h> + +error_t +diskfs_init_dir (struct node *dp, struct node *pdp, struct protid *cred) +{ + dp->dn->u.dir.dotdot = pdp->dn; + dp->dn->u.dir.entries = 0; + + /* Increase hardlink count for parent directory */ + pdp->dn_stat.st_nlink++; + /* Take '.' directory into account */ + dp->dn_stat.st_nlink++; + + return 0; +} + +error_t +diskfs_clear_directory (struct node *dp, struct node *pdp, + struct protid *cred) +{ + if (dp->dn->u.dir.entries != 0) + return ENOTEMPTY; + assert (dp->dn_stat.st_size == 0); + assert (dp->dn->u.dir.dotdot == pdp->dn); + + /* Decrease hardlink count for parent directory */ + pdp->dn_stat.st_nlink--; + /* Take '.' directory into account */ + dp->dn_stat.st_nlink--; + + return 0; +} + +int +diskfs_dirempty (struct node *dp, struct protid *cred) +{ + return dp->dn->u.dir.entries == 0; +} + +error_t +diskfs_get_directs (struct node *dp, int entry, int n, + char **data, size_t *datacnt, + vm_size_t bufsiz, int *amt) +{ + struct tmpfs_dirent *d; + struct dirent *entp; + int i; + + if (bufsiz == 0) + bufsiz = dp->dn_stat.st_size + + 2 * ((offsetof (struct dirent, d_name[3]) + 7) & ~7); + if (bufsiz > *datacnt) + { + *data = mmap (0, bufsiz, PROT_READ|PROT_WRITE, MAP_ANON, 0, 0); + if (*data == MAP_FAILED) + return ENOMEM; + } + + /* We always synthesize the first two entries (. and ..) on the fly. */ + entp = (struct dirent *) *data; + i = 0; + if (i++ >= entry) + { + entp->d_fileno = dp->dn_stat.st_ino; + entp->d_type = DT_DIR; + entp->d_namlen = 1; + entp->d_name[0] = '.'; + entp->d_name[1] = '\0'; + entp->d_reclen = (&entp->d_name[2] - (char *) entp + 7) & ~7; + entp = (void *) entp + entp->d_reclen; + } + if (i++ >= entry) + { + if (dp->dn->u.dir.dotdot == 0) + { + assert (dp == diskfs_root_node); + /* Use something not zero and not an st_ino value for any node in + this filesystem. Since we use pointer values, 2 will never + be a valid number. */ + entp->d_fileno = 2; + } + else + entp->d_fileno = (ino_t) (uintptr_t) dp->dn->u.dir.dotdot; + entp->d_type = DT_DIR; + entp->d_namlen = 2; + entp->d_name[0] = '.'; + entp->d_name[1] = '.'; + entp->d_name[2] = '\0'; + entp->d_reclen = (&entp->d_name[3] - (char *) entp + 7) & ~7; + entp = (void *) entp + entp->d_reclen; + } + + /* Skip ahead to the desired entry. */ + for (d = dp->dn->u.dir.entries; i < entry && d != 0; d = d->next) + ++i; + + if (i < entry) + { + assert (d == 0); + *datacnt = 0; + *amt = 0; + return 0; + } + + /* Now fill in the buffer with real entries. */ + for (; d != 0; d = d->next, i++) + { + size_t rlen = (offsetof (struct dirent, d_name[1]) + d->namelen + 7) & ~7; + if (rlen + (char *) entp - *data > bufsiz || (n >= 0 && i > n)) + break; + entp->d_fileno = (ino_t) (uintptr_t) d->dn; + entp->d_type = DT_UNKNOWN; + entp->d_namlen = d->namelen; + memcpy (entp->d_name, d->name, d->namelen + 1); + entp->d_reclen = rlen; + entp = (void *) entp + rlen; + } + + *datacnt = (char *) entp - *data; + *amt = i - entry; + + return 0; +} + + +struct dirstat +{ + struct tmpfs_dirent **prevp; +}; +const size_t diskfs_dirstat_size = sizeof (struct dirstat); + +void +diskfs_null_dirstat (struct dirstat *ds) +{ + ds->prevp = 0; +} + +error_t +diskfs_drop_dirstat (struct node *dp, struct dirstat *ds) +{ + /* No need to clear the pointers. */ + return 0; +} + +error_t +diskfs_lookup_hard (struct node *dp, + const char *name, enum lookup_type type, + struct node **np, struct dirstat *ds, + struct protid *cred) +{ + const size_t namelen = strlen (name); + struct tmpfs_dirent *d, **prevp; + + if (type == REMOVE || type == RENAME) + assert (np); + + if (namelen == 1 && name[0] == '.') + { + if (np != 0) + { + *np = dp; + diskfs_nref (dp); + } + return 0; + } + if (namelen == 2 && name[0] == '.' && name[1] == '.') + { + struct disknode *dddn = dp->dn->u.dir.dotdot; + error_t err; + + assert (np != 0); + if (dddn == 0) /* root directory */ + return EAGAIN; + + if (type == (REMOVE|SPEC_DOTDOT) || type == (RENAME|SPEC_DOTDOT)) + { + *np = *dddn->hprevp; + assert (*np); + assert ((*np)->dn == dddn); + assert (*dddn->hprevp == *np); + return 0; + } + else + { + mutex_unlock (&dp->lock); + err = diskfs_cached_lookup ((ino_t) (intptr_t) dddn, np); + + if (type == (LOOKUP|SPEC_DOTDOT)) + diskfs_nrele (dp); + else + mutex_lock (&dp->lock); + + if (err) + *np = 0; + + return err; + } + } + + for (d = *(prevp = &dp->dn->u.dir.entries); d != 0; + d = *(prevp = &d->next)) + if (d->namelen == namelen && !memcmp (d->name, name, namelen)) + { + if (ds) + ds->prevp = prevp; + + if (np) + return diskfs_cached_lookup ((ino_t) (uintptr_t) d->dn, np); + else + return 0; + } + + if (ds) + ds->prevp = prevp; + if (np) + *np = 0; + return ENOENT; +} + + +error_t +diskfs_direnter_hard (struct node *dp, const char *name, + struct node *np, struct dirstat *ds, + struct protid *cred) +{ + const size_t namelen = strlen (name); + const size_t entsize + = (offsetof (struct dirent, d_name[1]) + namelen + 7) & ~7; + struct tmpfs_dirent *new; + + if (round_page (tmpfs_space_used + entsize) / vm_page_size + > tmpfs_page_limit) + return ENOSPC; + + new = malloc (offsetof (struct tmpfs_dirent, name) + namelen + 1); + if (new == 0) + return ENOSPC; + + new->next = 0; + new->dn = np->dn; + new->namelen = namelen; + memcpy (new->name, name, namelen + 1); + *ds->prevp = new; + + dp->dn_stat.st_size += entsize; + adjust_used (entsize); + + dp->dn_stat.st_blocks = ((sizeof *dp->dn + dp->dn->translen + + dp->dn_stat.st_size + 511) + / 512); + return 0; +} + +error_t +diskfs_dirrewrite_hard (struct node *dp, struct node *np, + struct dirstat *ds) +{ + (*ds->prevp)->dn = np->dn; + return 0; +} + +error_t +diskfs_dirremove_hard (struct node *dp, struct dirstat *ds) +{ + struct tmpfs_dirent *d = *ds->prevp; + const size_t entsize + = (offsetof (struct dirent, d_name[1]) + d->namelen + 7) & ~7; + + *ds->prevp = d->next; + + if (dp->dirmod_reqs != 0) + diskfs_notice_dirchange (dp, DIR_CHANGED_UNLINK, d->name); + + free (d); + + adjust_used (-entsize); + dp->dn_stat.st_size -= entsize; + dp->dn_stat.st_blocks = ((sizeof *dp->dn + dp->dn->translen + + dp->dn_stat.st_size + 511) + / 512); + + return 0; +} diff --git a/tmpfs/node.c b/tmpfs/node.c new file mode 100644 index 00000000..bce15370 --- /dev/null +++ b/tmpfs/node.c @@ -0,0 +1,590 @@ +/* Node state and file contents for tmpfs. + Copyright (C) 2000,01,02 Free Software Foundation, Inc. + +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 the GNU Hurd; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include "tmpfs.h" +#include <stddef.h> +#include <stdlib.h> +#include <fcntl.h> +#include <hurd/hurd_types.h> +#include <hurd/store.h> +#include "default_pager_U.h" + +unsigned int num_files; +static unsigned int gen; + +struct node *all_nodes; + +error_t +diskfs_alloc_node (struct node *dp, mode_t mode, struct node **npp) +{ + struct disknode *dn; + + dn = calloc (1, sizeof *dn); + if (dn == 0) + return ENOSPC; + spin_lock (&diskfs_node_refcnt_lock); + if (round_page (tmpfs_space_used + sizeof *dn) / vm_page_size + > tmpfs_page_limit) + { + spin_unlock (&diskfs_node_refcnt_lock); + free (dn); + return ENOSPC; + } + dn->gen = gen++; + ++num_files; + tmpfs_space_used += sizeof *dn; + spin_unlock (&diskfs_node_refcnt_lock); + + dn->type = IFTODT (mode & S_IFMT); + return diskfs_cached_lookup ((ino_t) (uintptr_t) dn, npp); +} + +void +diskfs_free_node (struct node *np, mode_t mode) +{ + switch (np->dn->type) + { + case DT_REG: + if (np->dn->u.reg.memobj != MACH_PORT_NULL) { + vm_deallocate (mach_task_self (), np->dn->u.reg.memref, 4096); + mach_port_deallocate (mach_task_self (), np->dn->u.reg.memobj); + } + break; + case DT_DIR: + assert (np->dn->u.dir.entries == 0); + break; + case DT_LNK: + free (np->dn->u.lnk); + break; + } + *np->dn->hprevp = np->dn->hnext; + if (np->dn->hnext != 0) + np->dn->hnext->dn->hprevp = np->dn->hprevp; + free (np->dn); + np->dn = 0; + + --num_files; + tmpfs_space_used -= sizeof *np->dn; +} + +void +diskfs_node_norefs (struct node *np) +{ + if (np->dn != 0) + { + /* We don't bother to do this in diskfs_write_disknode, since it only + ever matters here. The node state goes back into the `struct + disknode' while it has no associated diskfs node. */ + + np->dn->size = np->dn_stat.st_size; + np->dn->mode = np->dn_stat.st_mode; + np->dn->nlink = np->dn_stat.st_nlink; + np->dn->uid = np->dn_stat.st_uid; + np->dn->author = np->dn_stat.st_author; + np->dn->gid = np->dn_stat.st_gid; + np->dn->atime = np->dn_stat.st_atim; + np->dn->mtime = np->dn_stat.st_mtim; + np->dn->ctime = np->dn_stat.st_ctim; + np->dn->flags = np->dn_stat.st_flags; + + switch (np->dn->type) + { + case DT_REG: + assert (np->allocsize % vm_page_size == 0); + np->dn->u.reg.allocpages = np->allocsize / vm_page_size; + break; + case DT_CHR: + case DT_BLK: + np->dn->u.chr = np->dn_stat.st_rdev; + break; + } + + /* Remove this node from the cache list rooted at `all_nodes'. */ + *np->dn->hprevp = np->dn->hnext; + if (np->dn->hnext != 0) + np->dn->hnext->dn->hprevp = np->dn->hprevp; + np->dn->hnext = 0; + np->dn->hprevp = 0; + } + + free (np); +} + +static void +recompute_blocks (struct node *np) +{ + struct disknode *const dn = np->dn; + struct stat *const st = &np->dn_stat; + + st->st_blocks = sizeof *dn + dn->translen; + switch (dn->type) + { + case DT_REG: + np->allocsize = dn->u.reg.allocpages * vm_page_size; + st->st_blocks += np->allocsize; + break; + case DT_LNK: + st->st_blocks += st->st_size + 1; + break; + case DT_CHR: + case DT_BLK: + st->st_rdev = dn->u.chr; + break; + case DT_DIR: + st->st_blocks += dn->size; + break; + } + st->st_blocks = (st->st_blocks + 511) / 512; +} + +/* Fetch inode INUM, set *NPP to the node structure; + gain one user reference and lock the node. */ +error_t +diskfs_cached_lookup (ino_t inum, struct node **npp) +{ + struct disknode *dn = (void *) (uintptr_t) inum; + struct node *np; + + assert (npp); + + if (dn->hprevp != 0) /* There is already a node. */ + { + np = *dn->hprevp; + assert (np->dn == dn); + assert (*dn->hprevp == np); + + diskfs_nref (np); + } + else + /* Create the new node. */ + { + struct stat *st; + + np = diskfs_make_node (dn); + np->cache_id = (ino_t) (uintptr_t) dn; + + spin_lock (&diskfs_node_refcnt_lock); + dn->hnext = all_nodes; + if (dn->hnext) + dn->hnext->dn->hprevp = &dn->hnext; + dn->hprevp = &all_nodes; + all_nodes = np; + spin_unlock (&diskfs_node_refcnt_lock); + + st = &np->dn_stat; + memset (st, 0, sizeof *st); + st->st_fstype = FSTYPE_MEMFS; + st->st_fsid = getpid (); + st->st_blksize = vm_page_size; + + st->st_ino = (ino_t) (uintptr_t) dn; + st->st_gen = dn->gen; + + st->st_size = dn->size; + st->st_mode = dn->mode; + st->st_nlink = dn->nlink; + st->st_uid = dn->uid; + st->st_author = dn->author; + st->st_gid = dn->gid; + st->st_atim = dn->atime; + st->st_mtim = dn->mtime; + st->st_ctim = dn->ctime; + st->st_flags = dn->flags; + + st->st_rdev = 0; + np->allocsize = 0; + recompute_blocks (np); + } + + mutex_lock (&np->lock); + *npp = np; + return 0; +} + +error_t +diskfs_node_iterate (error_t (*fun) (struct node *)) +{ + error_t err = 0; + unsigned int 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 (node = all_nodes; node != 0; node = node->dn->hnext) + num_nodes++; + + p = node_list = alloca (num_nodes * sizeof (struct node *)); + for (node = all_nodes; node != 0; 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; +} + +/* The user must define this function. Node NP has some light + references, but has just lost its last hard references. Take steps + so that if any light references can be freed, they are. NP is locked + as is the pager refcount lock. This function will be called after + diskfs_lost_hardrefs. */ +void +diskfs_try_dropping_softrefs (struct node *np) +{ +} + +/* The user must define this funcction. Node NP has some light + references but has just lost its last hard reference. NP is locked. */ +void +diskfs_lost_hardrefs (struct node *np) +{ +} + +/* The user must define this function. Node NP has just acquired + a hard reference where it had none previously. It is thus now + OK again to have light references without real users. NP is + locked. */ +void +diskfs_new_hardrefs (struct node *np) +{ +} + + + +error_t +diskfs_get_translator (struct node *np, char **namep, u_int *namelen) +{ + *namelen = np->dn->translen; + if (*namelen == 0) + return 0; + *namep = malloc (*namelen); + if (*namep == 0) + return ENOMEM; + memcpy (*namep, np->dn->trans, *namelen); + return 0; +} + +error_t +diskfs_set_translator (struct node *np, + const char *name, u_int namelen, + struct protid *cred) +{ + char *new; + if (namelen == 0) + { + free (np->dn->trans); + new = 0; + np->dn_stat.st_mode &= ~S_IPTRANS; + } + else + { + new = realloc (np->dn->trans, namelen); + if (new == 0) + return ENOSPC; + memcpy (new, name, namelen); + np->dn_stat.st_mode |= S_IPTRANS; + } + adjust_used (namelen - np->dn->translen); + np->dn->trans = new; + np->dn->translen = namelen; + recompute_blocks (np); + return 0; +} + +static error_t +create_symlink_hook (struct node *np, const char *target) +{ + assert (np->dn->u.lnk == 0); + np->dn_stat.st_size = strlen (target); + if (np->dn_stat.st_size > 0) + { + const size_t size = np->dn_stat.st_size + 1; + np->dn->u.lnk = malloc (size); + if (np->dn->u.lnk == 0) + return ENOSPC; + memcpy (np->dn->u.lnk, target, size); + np->dn->type = DT_LNK; + adjust_used (size); + recompute_blocks (np); + } + return 0; +} +error_t (*diskfs_create_symlink_hook)(struct node *np, const char *target) + = create_symlink_hook; + +static error_t +read_symlink_hook (struct node *np, char *target) +{ + memcpy (target, np->dn->u.lnk, np->dn_stat.st_size + 1); + return 0; +} +error_t (*diskfs_read_symlink_hook)(struct node *np, char *target) + = read_symlink_hook; + +void +diskfs_write_disknode (struct node *np, int wait) +{ +} + +void +diskfs_file_update (struct node *np, int wait) +{ + diskfs_node_update (np, wait); +} + +error_t +diskfs_node_reload (struct node *node) +{ + return 0; +} + + +/* The user must define this function. Truncate locked node NP to be SIZE + bytes long. (If NP is already less than or equal to SIZE bytes + long, do nothing.) If this is a symlink (and diskfs_shortcut_symlink + is set) then this should clear the symlink, even if + diskfs_create_symlink_hook stores the link target elsewhere. */ +error_t +diskfs_truncate (struct node *np, off_t size) +{ + if (np->dn->type == DT_LNK) + { + free (np->dn->u.lnk); + adjust_used (size - np->dn_stat.st_size); + np->dn->u.lnk = 0; + np->dn_stat.st_size = size; + return 0; + } + + if (np->allocsize <= size) + return 0; + + assert (np->dn->type == DT_REG); + + if (default_pager == MACH_PORT_NULL) + return EIO; + + np->dn_stat.st_size = size; + + off_t set_size = size; + size = round_page (size); + + if (np->dn->u.reg.memobj != MACH_PORT_NULL) + { + error_t err = default_pager_object_set_size (np->dn->u.reg.memobj, set_size); + if (err == MIG_BAD_ID) + /* This is an old default pager. We have no way to truncate the + memory object. Note that the behavior here will be wrong in + two ways: user accesses past the end won't fault; and, more + importantly, later growing the file won't zero the contents + past the size we just supposedly truncated to. For proper + behavior, use a new default pager. */ + return 0; + if (err) + return err; + } + /* Otherwise it never had any real contents. */ + + adjust_used (size - np->allocsize); + np->dn_stat.st_blocks += (size - np->allocsize) / 512; + np->allocsize = size; + + return 0; +} + +/* The user must define this function. Grow the disk allocated to locked node + NP to be at least SIZE bytes, and set NP->allocsize to the actual + allocated size. (If the allocated size is already SIZE bytes, do + nothing.) CRED identifies the user responsible for the call. */ +error_t +diskfs_grow (struct node *np, off_t size, struct protid *cred) +{ + assert (np->dn->type == DT_REG); + + if (np->allocsize >= size) + return 0; + + off_t set_size = size; + size = round_page (size); + if (round_page (tmpfs_space_used + size - np->allocsize) + / vm_page_size > tmpfs_page_limit) + return ENOSPC; + + if (default_pager == MACH_PORT_NULL) + return EIO; + + if (np->dn->u.reg.memobj != MACH_PORT_NULL) + { + /* Increase the limit the memory object will allow to be accessed. */ + error_t err = default_pager_object_set_size (np->dn->u.reg.memobj, set_size); + if (err == MIG_BAD_ID) /* Old default pager, never limited it. */ + err = 0; + if (err) + return err; + } + + adjust_used (size - np->allocsize); + np->dn_stat.st_blocks += (size - np->allocsize) / 512; + np->allocsize = size; + return 0; +} + +mach_port_t +diskfs_get_filemap (struct node *np, vm_prot_t prot) +{ + error_t err; + + if (np->dn->type != DT_REG) + { + errno = EOPNOTSUPP; /* ? */ + return MACH_PORT_NULL; + } + + if (default_pager == MACH_PORT_NULL) + { + errno = EIO; + return MACH_PORT_NULL; + } + + /* We don't bother to create the memory object until the first time we + need it (i.e. first mapping or i/o). This way we might have a clue + what size it's going to be beforehand, so we can tell the default + pager how big to make its bitmaps. This is just an optimization for + the default pager; the memory object can be expanded at any time just + by accessing more of it. (It also optimizes the case of empty files + so we might never make a memory object at all.) */ + if (np->dn->u.reg.memobj == MACH_PORT_NULL) + { + error_t err = default_pager_object_create (default_pager, + &np->dn->u.reg.memobj, + np->allocsize); + if (err) + { + errno = err; + return MACH_PORT_NULL; + } + assert (np->dn->u.reg.memobj != MACH_PORT_NULL); + + /* XXX we need to keep a reference to the object, or GNU Mach + will terminate it when we release the map. */ + vm_map (mach_task_self (), &np->dn->u.reg.memref, 4096, 0, 1, + np->dn->u.reg.memobj, 0, 0, VM_PROT_NONE, VM_PROT_NONE, + VM_INHERIT_NONE); + assert_perror (err); + } + + /* XXX always writable */ + + /* Add a reference for each call, the caller will deallocate it. */ + err = mach_port_mod_refs (mach_task_self (), np->dn->u.reg.memobj, + MACH_PORT_RIGHT_SEND, +1); + assert_perror (err); + + return np->dn->u.reg.memobj; +} + +/* The user must define this function. Return a `struct pager *' suitable + for use as an argument to diskfs_register_memory_fault_area that + refers to the pager returned by diskfs_get_filemap for node NP. + NP is locked. */ +struct pager * +diskfs_get_filemap_pager_struct (struct node *np) +{ + return 0; +} + +/* We have no pager of our own, so there is no need to worry about + users of it, or to shut it down. */ +int +diskfs_pager_users () +{ + return 0; +} +void +diskfs_shutdown_pager () +{ +} + +/* The purpose of this is to decide that it's ok to make the fs read-only. + Turning a temporary filesystem read-only seem pretty useless. */ +vm_prot_t +diskfs_max_user_pager_prot () +{ + return VM_PROT_READ; /* Probable lie that lets us go read-only. */ +} + +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) +{ + mach_port_t memobj = diskfs_get_filemap (cred->po->np, VM_PROT_ALL); + if (memobj == MACH_PORT_NULL) + return errno; + + assert (*num_ports >= 1); /* mig always gives us some */ + *num_ports = 1; + *ports_type = MACH_MSG_TYPE_MOVE_SEND; + (*ports)[0] + = (cred->po->openstat & O_RDWR) == O_RDWR ? memobj : MACH_PORT_NULL; + + assert (*num_offsets >= 2); /* mig always gives us some */ + *num_offsets = 2; + (*offsets)[0] = 0; + (*offsets)[1] = cred->po->np->dn_stat.st_size; + + assert (*num_ints >= 6); /* mig always gives us some */ + *num_ints = 6; + (*ints)[0] = STORAGE_MEMORY; + (*ints)[1] = (cred->po->openstat & O_WRITE) ? 0 : STORE_READONLY; + (*ints)[2] = 1; /* block size */ + (*ints)[3] = 1; /* 1 run in offsets list */ + (*ints)[4] = 0; /* name len */ + (*ints)[5] = 0; /* misc len */ + + *data_len = 0; + + return 0; +} diff --git a/tmpfs/pager-stubs.c b/tmpfs/pager-stubs.c new file mode 100644 index 00000000..25d70fe2 --- /dev/null +++ b/tmpfs/pager-stubs.c @@ -0,0 +1,88 @@ +/* stupid stub functions never called, needed because libdiskfs uses libpager + Copyright (C) 2001 Free Software Foundation, Inc. + +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 the GNU Hurd; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + + +#include <hurd/pager.h> +#include <stdlib.h> + +/* The user must define this function. For pager PAGER, read one + page from offset PAGE. Set *BUF to be the address of the page, + and set *WRITE_LOCK if the page must be provided read-only. + The only permissible error returns are EIO, EDQUOT, and ENOSPC. */ +error_t +pager_read_page (struct user_pager_info *pager, + vm_offset_t page, + vm_address_t *buf, + int *write_lock) +{ + abort(); + return EIEIO; +} + +/* The user must define this function. For pager PAGER, synchronously + write one page from BUF to offset PAGE. In addition, mfree + (or equivalent) BUF. The only permissible error returns are EIO, + EDQUOT, and ENOSPC. */ +error_t +pager_write_page (struct user_pager_info *pager, + vm_offset_t page, + vm_address_t buf) +{ + abort(); + return EIEIO; +} + +/* The user must define this function. A page should be made writable. */ +error_t +pager_unlock_page (struct user_pager_info *pager, + vm_offset_t address) +{ + abort(); + return EIEIO; +} + +/* The user must define this function. It should report back (in + *OFFSET and *SIZE the minimum valid address the pager will accept + and the size of the object. */ +error_t +pager_report_extent (struct user_pager_info *pager, + vm_address_t *offset, + vm_size_t *size) +{ + abort(); + return EIEIO; +} + +/* The user must define this function. This is called when a pager is + being deallocated after all extant send rights have been destroyed. */ +void +pager_clear_user_data (struct user_pager_info *pager) +{ + abort(); +} + +/* The use must define this function. This will be called when the ports + library wants to drop weak references. The pager library creates no + weak references itself. If the user doesn't either, then it's OK for + this function to do nothing. */ +void +pager_dropweak (struct user_pager_info *p) +{ + abort(); +} diff --git a/tmpfs/tmpfs.c b/tmpfs/tmpfs.c new file mode 100644 index 00000000..b72459fe --- /dev/null +++ b/tmpfs/tmpfs.c @@ -0,0 +1,402 @@ +/* Main program and global state for tmpfs. + Copyright (C) 2000,01,02 Free Software Foundation, Inc. + +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 the GNU Hurd; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include <argp.h> +#include <argz.h> +#include <string.h> +#include <inttypes.h> +#include <error.h> + +#include "tmpfs.h" +#include <limits.h> +#include <version.h> +#include <fcntl.h> +#include <hurd.h> +#include <hurd/paths.h> + +char *diskfs_server_name = "tmpfs"; +char *diskfs_server_version = HURD_VERSION; +char *diskfs_extra_version = "GNU Hurd"; +char *diskfs_disk_name = "swap"; + +/* We ain't got to show you no stinkin' sync'ing. */ +int diskfs_default_sync_interval = 0; + +/* We must supply some claimed limits, though we don't impose any new ones. */ +int diskfs_link_max = (1ULL << (sizeof (nlink_t) * CHAR_BIT)) - 1; +int diskfs_name_max = 255; /* dirent d_namlen limit */ +int diskfs_maxsymlinks = 8; + +/* Yeah, baby, we do it all! */ +int diskfs_shortcut_symlink = 1; +int diskfs_shortcut_chrdev = 1; +int diskfs_shortcut_blkdev = 1; +int diskfs_shortcut_fifo = 1; +int diskfs_shortcut_ifsock = 1; + +struct node *diskfs_root_node; +mach_port_t default_pager; + +off_t tmpfs_page_limit, tmpfs_space_used; +mode_t tmpfs_root_mode = -1; + +error_t +diskfs_set_statfs (struct statfs *st) +{ + fsblkcnt_t pages; + + st->f_type = FSTYPE_MEMFS; + st->f_fsid = getpid (); + + st->f_bsize = vm_page_size; + st->f_blocks = tmpfs_page_limit; + + spin_lock (&diskfs_node_refcnt_lock); + st->f_files = num_files; + pages = round_page (tmpfs_space_used) / vm_page_size; + spin_unlock (&diskfs_node_refcnt_lock); + + st->f_bfree = pages < tmpfs_page_limit ? tmpfs_page_limit - pages : 0; + st->f_bavail = st->f_bfree; + st->f_ffree = st->f_bavail / sizeof (struct disknode); /* Well, sort of. */ + + return 0; +} + + +error_t +diskfs_set_hypermetadata (int wait, int clean) +{ + /* All the state always just lives in core, so we have nothing to do. */ + return 0; +} + +void +diskfs_sync_everything (int wait) +{ +} + +error_t +diskfs_reload_global_state () +{ + return 0; +} + +int diskfs_synchronous = 0; + +static const struct argp_option options[] = +{ + {"mode", 'm', "MODE", 0, "Permissions (octal) for root directory"}, + {NULL,} +}; + +struct option_values +{ + off_t size; + mode_t mode; +}; + +/* Parse a command line option. */ +static error_t +parse_opt (int key, char *arg, struct argp_state *state) +{ + /* We save our parsed values in this structure, hung off STATE->hook. + Only after parsing all options successfully will we use these values. */ + struct option_values *values = state->hook; + + switch (key) + { + case ARGP_KEY_INIT: + state->child_inputs[0] = state->input; + values = malloc (sizeof *values); + if (values == 0) + return ENOMEM; + state->hook = values; + values->size = 0; + values->mode = -1; + break; + case ARGP_KEY_FINI: + free (values); + state->hook = 0; + break; + + case 'm': /* --mode=OCTAL */ + { + char *end = NULL; + mode_t mode = strtoul (arg, &end, 8); + if (end == NULL || end == arg) + { + argp_error (state, "argument must be an octal number"); + return EINVAL; + } + if (mode & S_IFMT) + { + argp_error (state, "invalid bits in mode"); + return EINVAL; + } + values->mode = mode; + } + break; + + case ARGP_KEY_NO_ARGS: + argp_error (state, "must supply maximum size"); + return EINVAL; + + case ARGP_KEY_ARGS: + if (state->argv[state->next + 1] != 0) + { + argp_error (state, "too many arguments"); + return EINVAL; + } + else + { + char *end = NULL; + intmax_t size = strtoimax (state->argv[state->next], &end, 0); + if (end == NULL || end == arg) + { + argp_error (state, "argument must be a number"); + return EINVAL; + } + if (size < 0) + { + argp_error (state, "negative size not meaningful"); + return EINVAL; + } + switch (*end) + { + case 'g': + case 'G': + size <<= 10; + case 'm': + case 'M': + size <<= 10; + case 'k': + case 'K': + size <<= 10; + break; + case '%': + { + /* Set as a percentage of the machine's physical memory. */ + struct vm_statistics vmstats; + error_t err = vm_statistics (mach_task_self (), &vmstats); + if (err) + { + argp_error (state, "cannot find total physical memory: %s", + strerror (err)); + return err; + } + size = round_page ((((vmstats.free_count + + vmstats.active_count + + vmstats.inactive_count + + vmstats.wire_count) + * vm_page_size) + * size + 99) / 100); + break; + } + } + size = (off_t) size; + if (size < 0) + { + argp_error (state, "size too large"); + return EINVAL; + } + values->size = size; + } + break; + + case ARGP_KEY_SUCCESS: + /* All options parse successfully, so implement ours if possible. */ + tmpfs_page_limit = values->size / vm_page_size; + tmpfs_root_mode = values->mode; + break; + + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +/* Override the standard diskfs routine so we can add our own output. */ +error_t +diskfs_append_args (char **argz, size_t *argz_len) +{ + error_t err; + + /* Get the standard things. */ + err = diskfs_append_std_options (argz, argz_len); + + if (!err) + { + off_t lim = tmpfs_page_limit * vm_page_size; + char buf[100], sfx; +#define S(n, c) if ((lim & ((1 << n) - 1)) == 0) sfx = c, lim >>= n + S (30, 'G'); else S (20, 'M'); else S (10, 'K'); else sfx = '\0'; +#undef S + snprintf (buf, sizeof buf, "%Ld%c", lim, sfx); + err = argz_add (argz, argz_len, buf); + } + + return err; +} + +/* Handling of operations for the ports in diskfs_port_bucket, calling + * demuxer for each incoming message */ +static any_t +diskfs_thread_function (any_t demuxer) +{ + error_t err; + + do + { + ports_manage_port_operations_multithread (diskfs_port_bucket, + (ports_demuxer_type) demuxer, + 0, + 0, + 0); + err = diskfs_shutdown (0); + } + while (err); + + exit (0); + /* NOTREACHED */ + return (any_t) 0; +} + + +/* Add our startup arguments to the standard diskfs set. */ +static const struct argp_child startup_children[] = + {{&diskfs_startup_argp}, {0}}; +static struct argp startup_argp = {options, parse_opt, "MAX-BYTES", "\ +\v\ +MAX-BYTES may be followed by k or K for kilobytes,\n\ +m or M for megabytes, g or G for gigabytes.", + startup_children}; + +/* Similarly at runtime. */ +static const struct argp_child runtime_children[] = + {{&diskfs_std_runtime_argp}, {0}}; +static struct argp runtime_argp = {0, parse_opt, 0, 0, runtime_children}; + +struct argp *diskfs_runtime_argp = (struct argp *)&runtime_argp; + + + +int +main (int argc, char **argv) +{ + error_t err; + mach_port_t bootstrap, realnode, host_priv; + struct stat st; + + err = argp_parse (&startup_argp, argc, argv, ARGP_IN_ORDER, NULL, NULL); + assert_perror (err); + + task_get_bootstrap_port (mach_task_self (), &bootstrap); + if (bootstrap == MACH_PORT_NULL) + error (2, 0, "Must be started as a translator"); + + /* Get our port to the default pager. Without that, + we have no place to put file contents. */ + err = get_privileged_ports (&host_priv, NULL); + if (err == EPERM) + { + default_pager = file_name_lookup (_SERVERS_DEFPAGER, O_EXEC, 0); + if (default_pager == MACH_PORT_NULL) + error (0, errno, _SERVERS_DEFPAGER); + } + else if (err) + error (0, err, "Cannot get host privileged port"); + else + { + err = vm_set_default_memory_manager (host_priv, &default_pager); + mach_port_deallocate (mach_task_self (), host_priv); + if (err) + error (0, err, "Cannot get default pager port"); + } + if (default_pager == MACH_PORT_NULL) + error (0, 0, "files cannot have contents with no default pager port"); + + /* Initialize the diskfs library. Must come before any other diskfs call. */ + err = diskfs_init_diskfs (); + if (err) + error (4, err, "init"); + + err = diskfs_alloc_node (0, S_IFDIR, &diskfs_root_node); + if (err) + error (4, err, "cannot create root directory"); + + /* Like diskfs_spawn_first_thread. But do it manually, without timeout */ + cthread_detach (cthread_fork ((cthread_fn_t) diskfs_thread_function, + (any_t) diskfs_demuxer)); + + /* Now that we are all set up to handle requests, and diskfs_root_node is + set properly, it is safe to export our fsys control port to the + outside world. */ + realnode = diskfs_startup_diskfs (bootstrap, 0); + diskfs_root_node->dn_stat.st_mode = S_IFDIR; + + /* Propagate permissions, owner, etc. from underlying node to + the root directory of the new (empty) filesystem. */ + err = io_stat (realnode, &st); + if (err) + { + error (0, err, "cannot stat underlying node"); + if (tmpfs_root_mode == -1) + diskfs_root_node->dn_stat.st_mode |= 0777 | S_ISVTX; + else + diskfs_root_node->dn_stat.st_mode |= tmpfs_root_mode; + diskfs_root_node->dn_set_ctime = 1; + diskfs_root_node->dn_set_mtime = 1; + diskfs_root_node->dn_set_atime = 1; + } + else + { + if (tmpfs_root_mode == -1) + { + diskfs_root_node->dn_stat.st_mode |= st.st_mode &~ S_IFMT; + if (S_ISREG (st.st_mode) && (st.st_mode & 0111) == 0) + /* There are no execute bits set, as by default on a plain file. + For the virtual directory, set execute bits where read bits are + set on the underlying plain file. */ + diskfs_root_node->dn_stat.st_mode |= (st.st_mode & 0444) >> 2; + } + else + diskfs_root_node->dn_stat.st_mode |= tmpfs_root_mode; + diskfs_root_node->dn_stat.st_uid = st.st_uid; + diskfs_root_node->dn_stat.st_author = st.st_author; + diskfs_root_node->dn_stat.st_gid = st.st_gid; + diskfs_root_node->dn_stat.st_atim = st.st_atim; + diskfs_root_node->dn_stat.st_mtim = st.st_mtim; + diskfs_root_node->dn_stat.st_ctim = st.st_ctim; + diskfs_root_node->dn_stat.st_flags = st.st_flags; + } + diskfs_root_node->dn_stat.st_mode &= ~S_ITRANS; + diskfs_root_node->dn_stat.st_mode |= S_IROOT; + diskfs_root_node->dn_stat.st_nlink = 2; + + /* We must keep the REALNODE send right to remain the active + translator for the underlying node. */ + + mutex_unlock (&diskfs_root_node->lock); + + /* and so we die, leaving others to do the real work. */ + cthread_exit (0); + /* NOTREACHED */ + return 0; +} diff --git a/tmpfs/tmpfs.h b/tmpfs/tmpfs.h new file mode 100644 index 00000000..3cb31e6b --- /dev/null +++ b/tmpfs/tmpfs.h @@ -0,0 +1,85 @@ +/* Private data structures for tmpfs. + Copyright (C) 2000 Free Software Foundation, Inc. + +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 the GNU Hurd; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifndef _tmpfs_h +#define _tmpfs_h 1 + +#include <hurd/diskfs.h> +#include <sys/types.h> +#include <dirent.h> +#include <stdint.h> + +struct disknode +{ + uint_fast8_t type; /* DT_REG et al */ + + unsigned int gen; + off_t size; + mode_t mode; + nlink_t nlink; + uid_t uid, author; + gid_t gid; + struct timespec atime, mtime, ctime; + unsigned int flags; + + char *trans; + size_t translen; + + union + { + char *lnk; /* malloc'd symlink target */ + struct + { + mach_port_t memobj; + vm_address_t memref; + unsigned int allocpages; /* largest size while memobj was live */ + } reg; + struct + { + struct tmpfs_dirent *entries; + struct disknode *dotdot; + } dir; + dev_t chr, blk; + } u; + + struct node *hnext, **hprevp; +}; + +struct tmpfs_dirent +{ + struct tmpfs_dirent *next; + struct disknode *dn; + uint8_t namelen; + char name[0]; +}; + +extern unsigned int num_files; +extern off_t tmpfs_page_limit, tmpfs_space_used; + +extern mach_port_t default_pager; + +static inline void +adjust_used (off_t change) +{ + spin_lock (&diskfs_node_refcnt_lock); + tmpfs_space_used += change; + spin_unlock (&diskfs_node_refcnt_lock); +} + +#endif |