/* tarfs - A GNU tar filesystem for the Hurd. Copyright (C) 2002, Ludovic Courtès 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 #include #include #include #include #include #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; 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; }