summaryrefslogtreecommitdiff
path: root/fs.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs.c')
-rw-r--r--fs.c739
1 files changed, 739 insertions, 0 deletions
diff --git a/fs.c b/fs.c
new file mode 100644
index 000000000..84d7905b2
--- /dev/null
+++ b/fs.c
@@ -0,0 +1,739 @@
+/* tarfs - A GNU tar filesystem for the Hurd.
+ Copyright (C) 2002, Ludovic Courtès <ludo@type-z.org>
+
+ 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 of the
+ License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA */
+
+/*
+ * General filesystem node management facilities.
+ */
+
+#include <hurd.h>
+#include <hurd/netfs.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <maptime.h>
+#include <fcntl.h>
+#include "backend.h"
+#include "fs.h" /* to make sure that the header is up-to-date */
+#include "debug.h"
+
+
+/* General info */
+static pid_t pid;
+static uid_t uid;
+static gid_t gid;
+static volatile struct mapped_time_value *curr_time;
+
+/* Initialization. */
+int
+fs_init ()
+{
+ error_t err;
+
+ /* General stuff. */
+ pid = getpid ();
+ uid = getuid ();
+ gid = getgid ();
+ err = maptime_map (0, 0, &curr_time);
+
+ return err;
+}
+
+/* Returns the first entry of directory DIR (i.e. the first entry which was
+ added to DIR). */
+error_t
+fs_dir_first_entry (struct node *dir, struct node **first)
+{
+ if ((!dir->nn->entries) ||
+ (!S_ISDIR (dir->nn_stat.st_mode)))
+ return ENOTDIR;
+
+ /* This has to be consistent with _make_node () */
+ *first = dir->nn->entries;
+
+ if (!*first)
+ return ENOENT;
+
+ return 0;
+}
+
+/* Return DIR's last entry. */
+error_t
+fs_dir_last_entry (struct node *dir, struct node **last)
+{
+ struct node *node = dir->nn->entries;
+
+ if ((!dir->nn->entries) ||
+ (!S_ISDIR (dir->nn_stat.st_mode)))
+ return ENOTDIR;
+
+ if (!node)
+ {
+ *last = NULL;
+ return ENOENT;
+ }
+
+ for ( ; node->next; node = node->next);
+
+ *last = node;
+
+ return 0;
+}
+
+
+/* Filters a node name, that is, remove '/' and chars lower than 32.
+ Returns NAME is no change has been made, or a pointer to a newly
+ malloced buffer otherwise. */
+char*
+filter_node_name (char* name)
+{
+ char* newname;
+ int modified = 0;
+ char *s, *ns;
+
+ if (!name)
+ return NULL;
+ if (! (newname = calloc (strlen (name) + 1, sizeof (char))))
+ return name;
+
+ for (s = name, ns = newname;
+ *s != '\0';
+ s++, ns++)
+ {
+ if (*s == '/')
+ *ns = SUBST_SLASH, modified = 1;
+#ifdef SUBST_LOWER
+ else if (*s < 32)
+ *ns = SUBST_LOWER, modified = 1;
+#endif
+ else
+ *ns = *s;
+ }
+ *ns = *s;
+
+ if (!modified)
+ {
+ free (newname);
+ newname = name;
+ }
+
+ return newname;
+}
+
+/* Returns either NULL or a pointer to a node if found. */
+static inline struct node*
+_find_node (struct node *dir, char *name)
+{
+ struct node *node = NULL;
+
+ if (name)
+ {
+ /* Looking for '.' or '..'? */
+ if (name[0] == '.')
+ {
+ switch (name[1])
+ {
+ case '\0':
+ node = dir;
+ break;
+ case '.':
+ if (name[2] == '\0')
+ {
+ node = dir->nn->dir;
+ break;
+ }
+ }
+ }
+
+ if (!node)
+ {
+ /* Look for a "regular" node */
+ for (node = dir->nn->entries;
+ node != NULL;
+ node = node->next)
+ {
+ if (node->nn->name)
+ if (! strcmp (node->nn->name, name))
+ break;
+ }
+ }
+ }
+
+ return node;
+}
+
+struct node *
+fs_find_node (struct node *dir, char *name)
+{
+ return _find_node (dir, name);
+}
+
+/* Inserts a new node in directory DIR, with name NAME and mode M. If not NULL,
+ *N points to the newly created node.
+ NAME is *not* duplicated! */
+static inline error_t
+_make_node (struct node **n, struct node *dir, char* name, mode_t m)
+{
+ static ino_t id = 1;
+ io_statbuf_t st;
+ struct netnode *nn;
+ struct node* newnode = NULL;
+
+ /* Alloctes a new netnode */
+ nn = (struct netnode*) calloc (1, sizeof (struct netnode));
+ if (!nn)
+ return ENOMEM;
+ newnode = netfs_make_node (nn);
+ if (!newnode)
+ return ENOMEM;
+
+ /* General stat */
+ st.st_fstype = FSTYPE_TAR;
+ st.st_fsid = pid;
+ st.st_dev = st.st_rdev = pid; /* unique device id */
+ st.st_uid = st.st_author = uid;
+ st.st_gid = gid;
+ st.st_mode = m;
+ st.st_ino = id++; /* unique inode number for each fs node */
+ st.st_nlink = 1; /* number of subdir plus two, one otherwise. */
+ st.st_size = 0;
+ st.st_blksize = 1024; /* optimal block size for reading */
+ st.st_blocks = 1; /* XXX */
+ st.st_gen = 0;
+
+ if (S_ISDIR (m))
+ /* Set st_nlink to the number of subdirs plus 2 */
+ st.st_nlink = 2;
+
+ newnode->nn->name = filter_node_name (name);
+ newnode->nn->entries = NULL; /* ptr to the first entry of this node */
+ newnode->nn_stat = st;
+ newnode->nn_translated = m;
+
+ newnode->next = NULL;
+ newnode->prevp = NULL;
+
+ if (dir)
+ {
+ struct node *p;
+
+ /* Add a reference to DIR */
+ netfs_nref (dir);
+
+ /* Insert the new node *at the end* of the linked list of DIR entries. */
+ if (dir->nn->entries)
+ {
+ for (p = dir->nn->entries;
+ p->next;
+ p = p->next);
+ newnode->prevp = &p->next;
+ p->next = newnode;
+ }
+ else
+ {
+ newnode->prevp = &dir->nn->entries;
+ dir->nn->entries = newnode;
+ }
+
+#if 0
+ /* Insert the new node *at the beginning* of the linked list
+ of DIR entries. */
+ newnode->next = dir->nn->entries;
+ newnode->prevp = &dir->nn->entries;
+ dir->nn->entries = newnode;
+ if (newnode->next)
+ newnode->next->prevp = &newnode->next;
+#endif
+
+ newnode->nn->dir = dir;
+
+ /* Make sure that DIR is a directory. */
+ dir->nn_stat.st_mode |= S_IFDIR;
+
+ if (S_ISDIR (m))
+ /* Update DIR's hardlinks count */
+ dir->nn_stat.st_nlink++;
+ }
+
+ fshelp_touch (&newnode->nn_stat,
+ TOUCH_ATIME | TOUCH_CTIME | TOUCH_MTIME,
+ curr_time);
+
+ *n = newnode;
+
+ return 0;
+}
+
+/* Creates a new node in directory DIR, with name NAME (actually
+ a copy of NAME) and mode M. If not NULL, *N points to the newly
+ created node.
+ Checks whether there already exists such a node. */
+error_t
+fs_make_node (struct node **n, struct node *dir,
+ char* name, mode_t m)
+{
+ struct node* newnode = NULL;
+ error_t err = 0;
+
+ /* DIR == NULL means that we are creating NETFS_ROOT_NODE. */
+ if (dir)
+ newnode = _find_node (dir, name);
+
+ /* Creates a new one if not found. */
+ if (!newnode)
+ {
+ /* Make sure the filetype bits are set */
+ m = (m & S_IFMT) ? m : (m | S_IFREG);
+ name = name ? strdup (name) : NULL;
+ err = _make_node (&newnode, dir, name, m);
+ }
+ else
+ err = EEXIST;
+
+ /* Return a pointer to the newly created node. */
+ if (n)
+ *n = newnode;
+
+ return err;
+}
+
+/* Looks for a node located at PATH, starting at directory N.
+ When looking for "/foo/bar":
+ - if "/foo/bar" exists, a reference to it is returned in N and
+ the remaining parameters are set to NULL;
+ - if "/foo" doesn't exist, a reference to "/" is returned and RETRY_NAME
+ is set to "foo/bar" and NOTFOUND is set to "foo"; N points to "/foo";
+ - if "/foo" exists but "/foo/bar" doesn't, then RETRY is NULL but NOTFOUND
+ is equal to "bar". */
+error_t
+fs_find_node_path (struct node **n, char **retry_name, char **notfound,
+ const char *path)
+{
+ struct node *node = NULL;
+ char *str, *pathstr;
+ char *name = NULL;
+
+ pathstr = str = strdup (path);
+
+ /* Lookup nodes. */
+ if (! *n)
+ *n = netfs_root_node;
+ node = *n;
+ name = strtok_r (pathstr, "/", &str);
+
+ while (node && name)
+ {
+ /* Lookup base node. */
+ node = _find_node (*n, name);
+ if (node)
+ {
+ name = strtok_r (NULL, "/", &str);
+ *n = node;
+ }
+ }
+
+ /* Did we parse the whole string? */
+ if (*str == '\0')
+ {
+ if (!node)
+ {
+ /* Yes, but we didn't find the very last node. */
+ assert (name != NULL);
+ assert (strlen (name) != 0);
+
+ *notfound = strdup (name);
+ *retry_name = NULL;
+ }
+ else
+ /* Yes, and we did find it. */
+ *notfound = *retry_name = NULL;
+ }
+ else
+ {
+ /* No, we stopped before the end of the string. */
+ assert (name != NULL);
+ assert (strlen (name) != 0);
+ *notfound = strdup (name);
+ *retry_name = strdup (str);
+ }
+
+ free (pathstr);
+
+ return 0;
+}
+
+/* Tries to create a node located at PATH, starting at directory N.
+ When creating "/foo/bar":
+ - if "/foo" exists and is a directory, "/foo/bar" is created and
+ a reference to it is returned; RETRY_NAME is NULL; N points to "/foo/bar".
+ - if "/foo" doesn't exist, a reference to "/" is returned and RETRY_NAME
+ is set to "foo/bar" and NOTFOUND is set to "foo"; N points to "/foo". */
+error_t
+fs_make_node_path (struct node **n, char **retry_name, char **notfound,
+ const char *path, const mode_t m)
+{
+ struct node *updir = *n;
+
+ fs_find_node_path (&updir, retry_name, notfound, path);
+
+ /* If all parent dirs have been found, then create the new node. */
+ if (!*retry_name)
+ {
+ assert (*notfound != NULL);
+#ifdef DEBUG_FS
+ fprintf (stderr, "%s: Creating %s\n", __FUNCTION__, *notfound);
+#endif
+ fs_make_node (n, updir, *notfound, m);
+ /* Do *not* free *NOTFOUND. */
+
+ free (*notfound);
+ *notfound = NULL;
+ }
+
+ return 0;
+}
+
+/* Used to add a sub-directory to DIR. If SUBDIRNAME already exists in DIR,
+ returns the number of entries in it; otherwise creates it and returns
+ zero. NEWDIR points to DIR/SUBDIRNAME.
+ It also checks whether SUBDIRNAME already exists. */
+unsigned long
+fs_make_subdir (struct node **newdir, struct node *dir, char *subdirname)
+{
+ unsigned long nodenum = 0;
+ struct node *n, *p;
+
+ /* Look for an existing dir */
+ n = _find_node (dir, subdirname);
+
+ if (!n)
+ /* Create a new sub-directory. */
+ fs_make_node (&n, dir, subdirname, S_IFDIR|0555);
+ else
+ /* Compute the node number. */
+ for (p = n->nn->entries; p; p = p->next)
+ if (!S_ISDIR (p->nn_stat.st_mode))
+ nodenum++;
+
+ *newdir = n;
+ return nodenum;
+}
+
+
+/* Returns the path of a given node (relatively to the given root node).
+ This is a very funny function (see macro below). ;-) */
+char*
+fs_get_path_from_root (struct node *root, struct node *node)
+{
+#define REVERSE_COPY(dst, src) \
+ { int i; \
+ for (i=0; i < strlen ((src)); i++) \
+ (dst)[i] = src[strlen ((src)) - 1 - i]; \
+ (dst)[strlen ((src))] = '\0'; \
+ }
+
+ struct node *n;
+ size_t len = 256;
+ char *path;
+ char *ptr;
+
+ path = (char*)calloc(len, sizeof(char));
+ ptr = path;
+
+ for (n = node;
+ (n != root) && (n != NULL);
+ n = n->nn->dir)
+ {
+ /* Reallocate if necessary. */
+ if (strlen (path) + strlen (n->nn->name) + 1 + 1 > len)
+ {
+ char* new;
+ len *= 2;
+ new = realloc (path, len);
+ ptr = new + (ptr - path);
+ path = new;
+ }
+ REVERSE_COPY (ptr, n->nn->name);
+ ptr[strlen (n->nn->name)] = '/';
+ ptr += strlen (n->nn->name) + 1;
+ }
+
+ /* Remove trailing slash. */
+ if (strlen (path))
+ path[strlen (path) - 1] = '\0';
+
+ /* Reverse-copy the final result. */
+ ptr = (char*)calloc (strlen (path) + 1, sizeof (char));
+ REVERSE_COPY (ptr, path);
+ free (path);
+
+ return ptr;
+}
+
+/* Returns the relavive path to the given root node. */
+char*
+fs_get_path_to_root (struct node *root, struct node *node)
+{
+ struct node *n;
+ size_t len = 256;
+ char *path;
+ char *ptr;
+
+ /* Go to the parent dir if NODE is not a directory. */
+ if (! (node->nn_stat.st_mode & S_IFDIR))
+ node = node->nn->dir;
+
+ path = (char*)calloc(len, sizeof(char));
+ ptr = path;
+
+ for (n = node;
+ (n != root) && (n != NULL);
+ n = n->nn->dir)
+ {
+ /* Reallocate if necessary. */
+ if (strlen (path) + 3 + 1 > len)
+ {
+ char* new;
+ len *= 2;
+ new = realloc (path, len);
+ ptr = new + (ptr - path);
+ path = new;
+ }
+ strncpy (ptr, "../", 3);
+ ptr += 3;
+ }
+
+ /* Remove last slash. */
+ assert (strlen (path) > 0);
+ path[strlen (path) - 1] = '\0';
+
+ /* Reverse-copy the final result. */
+ ptr = (char*)calloc (strlen (path) + 1, sizeof (char));
+ strcpy (ptr, path);
+ free (path);
+
+ return ptr;
+}
+
+/* Gets the first common directory. */
+struct node*
+get_common_root (struct node *node1, struct node *node2)
+{
+#define MAX_PATH_DEPTH 256
+ struct node *n1 = node1, *n2 = node2;
+ struct node *path1[MAX_PATH_DEPTH];
+ struct node *path2[MAX_PATH_DEPTH];
+ int i1 = 0, i2 = 0;
+
+ if (n1 == n2)
+ return n1;
+
+ /* Save pathes to NETFS_ROOT_NODE in a stack. */
+ do
+ {
+ assert (i1 < MAX_PATH_DEPTH);
+ path1[i1++] = n1 = n1->nn->dir;
+ }
+ while (n1 != netfs_root_node);
+
+ do
+ {
+ assert (i2 < MAX_PATH_DEPTH);
+ path2[i2++] = n2 = n2->nn->dir;
+ }
+ while (n2 != netfs_root_node);
+
+ /* Get to the last common node. */
+ while (path1[--i1] == path2[--i2]);
+
+ return path1[++i1];
+}
+
+/* Makes NODE a symlink to TARGET, relatively to root directory ROOTDIR. */
+error_t
+fs_link_node (struct node *node, struct node *target)
+{
+ char *toroot, *tolink, *link;
+ struct node *rootdir;
+
+ /* Make it look like a symlink. */
+ node->nn_stat.st_mode |= S_IFLNK;
+ node->nn_translated |= S_IFLNK;
+
+ rootdir = get_common_root (node, target);
+ toroot = fs_get_path_to_root (rootdir, node);
+ tolink = fs_get_path_from_root (rootdir, target);
+ link = calloc (strlen (toroot) + 1 + strlen (tolink) + 1, sizeof (char));
+ sprintf (link, "%s/%s", toroot, tolink);
+
+ node->nn->symlink = link;
+ node->nn_stat.st_size = strlen (link);
+
+ return 0;
+}
+
+/* Turn NODE into a symbolic link to TARGET. */
+error_t
+fs_link_node_path (struct node *node, const char *target)
+{
+ /* Make it look like a symlink. */
+ node->nn_stat.st_mode |= S_IFLNK;
+ node->nn_translated |= S_IFLNK;
+
+ assert (node->nn);
+ node->nn->symlink = strdup (target);
+ node->nn_stat.st_size = strlen (target);
+
+ return 0;
+}
+
+/* Creates a new node NODE, in directory DIR, with name NAME and mode
+ M, hard linked to TARGET. */
+error_t
+fs_hard_link_node (struct node **node, struct node *dir, char* name,
+ const mode_t m, struct node *target)
+{
+ struct netnode *nn;
+ struct node* newnode = NULL;
+
+ /* Alloctes a new netnode */
+ nn = (struct netnode*) calloc (1, sizeof (struct netnode));
+ if (!nn)
+ return ENOMEM;
+ newnode = netfs_make_node (nn);
+ if (!newnode)
+ return ENOMEM;
+
+ /* Increase TARGET's hard links count. */
+ target->nn_stat.st_nlink++;
+ netfs_nref (target);
+
+ /* Copies netnode from TARGET (optional since only TARGET should be
+ accessed). */
+ newnode->nn_stat = target->nn_stat;
+ newnode->nn_stat.st_mode = m; /* FIXME: Should keep the upper bits. */
+ newnode->nn_translated = m;
+ newnode->nn_stat.st_nlink--; /* XXX: One less hard link? */
+ *newnode->nn = *target->nn;
+ newnode->nn->name = name;
+ newnode->next = NULL;
+ newnode->prevp = NULL;
+
+ /* Mark NEWNODE as a hard link to TARGET. */
+ newnode->nn->hardlink = target;
+
+ if (dir)
+ {
+ struct node *p;
+ netfs_nref (dir);
+
+ /* Insert the new node *at the end* of the linked list of DIR entries. */
+ if (dir->nn->entries)
+ {
+ for (p = dir->nn->entries;
+ p->next;
+ p = p->next);
+ newnode->prevp = &p;
+ p->next = newnode;
+ }
+ else
+ dir->nn->entries = newnode;
+
+ newnode->nn->dir = dir;
+
+ /* Make sure that DIR is a directory. */
+ dir->nn_stat.st_mode |= S_IFDIR;
+ }
+
+ fshelp_touch (&newnode->nn_stat,
+ TOUCH_ATIME|TOUCH_CTIME|TOUCH_MTIME, curr_time);
+
+ if (node)
+ *node = newnode;
+
+ return 0;
+}
+
+/* Unlink NODE *without* freeing its resources. */
+error_t
+fs_unlink_node (struct node *node)
+{
+ struct node *dir = node->nn->dir;
+ struct node *next = node->next;
+
+ if (node->nn->entries)
+ return ENOTEMPTY;
+
+ /* Check the number of hard links to NODE. */
+ if (S_ISDIR (node->nn_stat.st_mode))
+ {
+ if (node->nn_stat.st_nlink > 2)
+ return EBUSY;
+ }
+ else
+ {
+ if (node->nn_stat.st_nlink > 1)
+ return EBUSY;
+ }
+
+ /* PREVP should never be zero. */
+ assert (node->prevp);
+
+ /* Unlink NODE */
+ if (*node->prevp)
+ *node->prevp = next;
+
+ if (next)
+ next->prevp = node->prevp;
+
+ /* Decrease the reference count to the hardlink targets */
+ if (node->nn->hardlink)
+ {
+ node->nn->hardlink->nn_stat.st_nlink--;
+ netfs_nput (node->nn->hardlink);
+ }
+
+ /* Same for directories ('..' links to DIR) */
+ if (dir && S_ISDIR (node->nn_stat.st_mode))
+ dir->nn_stat.st_nlink--;
+
+ /* Finally, drop a reference to the node itself, which may result
+ in calling netfs_node_norefs (). */
+ netfs_nput (node);
+
+ /* Drop a reference from DIR */
+ netfs_nput (dir);
+
+ return 0;
+}
+
+/* Frees all memory associated to NODE (which is assumed to be already
+ unlinked) except its 'nn->info' field. */
+void
+fs_free_node (struct node *node)
+{
+ struct netnode *nn = node->nn;
+
+ assert (nn);
+
+ if (nn->name)
+ free (nn->name);
+ if (nn->symlink)
+ free (nn->symlink);
+
+ free (nn);
+ node->nn = NULL;
+}