aboutsummaryrefslogtreecommitdiff
path: root/tmpfs/tmpfs.c
diff options
context:
space:
mode:
Diffstat (limited to 'tmpfs/tmpfs.c')
-rw-r--r--tmpfs/tmpfs.c447
1 files changed, 447 insertions, 0 deletions
diff --git a/tmpfs/tmpfs.c b/tmpfs/tmpfs.c
new file mode 100644
index 00000000..718c6d84
--- /dev/null
+++ b/tmpfs/tmpfs.c
@@ -0,0 +1,447 @@
+/* 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 = "none";
+
+/* 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;
+
+ pthread_spin_lock (&diskfs_node_refcnt_lock);
+ st->f_files = num_files;
+ pages = round_page (tmpfs_space_used) / vm_page_size;
+ pthread_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;
+
+#define OPT_SIZE 600 /* --size */
+
+static const struct argp_option options[] =
+{
+ {"mode", 'm', "MODE", 0, "Permissions (octal) for root directory"},
+ {"size", OPT_SIZE, "MAX-BYTES", 0, "Maximum size"},
+ {NULL,}
+};
+
+struct option_values
+{
+ off_t size;
+ mode_t mode;
+};
+
+/* Parse the size string ARG, and set *NEWSIZE with the resulting size. */
+static error_t
+parse_opt_size (const char *arg, struct argp_state *state, off_t *newsize)
+{
+ char *end = NULL;
+ intmax_t size = strtoimax (arg, &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;
+ }
+
+ *newsize = size;
+
+ return 0;
+}
+
+/* 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 = -1;
+ 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 OPT_SIZE: /* --size=MAX-BYTES */
+ {
+ error_t err = parse_opt_size (arg, state, &values->size);
+ if (err)
+ return err;
+ }
+ break;
+
+ case ARGP_KEY_NO_ARGS:
+ if (values->size < 0)
+ {
+ argp_error (state, "must supply maximum size");
+ return EINVAL;
+ }
+ break;
+
+ case ARGP_KEY_ARGS:
+ if (state->argv[state->next + 1] != 0)
+ {
+ argp_error (state, "too many arguments");
+ return EINVAL;
+ }
+ else if (values->size >= 0)
+ {
+ if (strcmp (state->argv[state->next], "tmpfs") != 0)
+ {
+ argp_error (state, "size specified with --size and argument is not \"tmpfs\"");
+ return EINVAL;
+ }
+ }
+ else
+ {
+ error_t err = parse_opt_size (state->argv[state->next], state,
+ &values->size);
+ if (err)
+ return err;
+ }
+ 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 void *
+diskfs_thread_function (void *demuxer)
+{
+ static int thread_timeout = 1000 * 60 * 2; /* two minutes */
+ error_t err;
+
+ do
+ {
+ ports_manage_port_operations_multithread (diskfs_port_bucket,
+ (ports_demuxer_type) demuxer,
+ thread_timeout,
+ 0,
+ 0);
+ err = diskfs_shutdown (0);
+ }
+ while (err);
+
+ exit (0);
+ /* NOTREACHED */
+ return NULL;
+}
+
+
+/* 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 = {options, 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;
+ pthread_t pthread_id;
+ 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 */
+ err = pthread_create (&pthread_id, NULL, diskfs_thread_function,
+ diskfs_demuxer);
+ if (!err)
+ pthread_detach (pthread_id);
+ else
+ {
+ errno = err;
+ perror ("pthread_create");
+ }
+
+ /* 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. */
+
+ pthread_mutex_unlock (&diskfs_root_node->lock);
+
+ /* and so we die, leaving others to do the real work. */
+ pthread_exit (NULL);
+ /* NOTREACHED */
+ return 0;
+}