/* vim: set noet ts=8 sts=8 sw=8 : */ /** * \file tree.c * * Implement filesystem trees. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "tree.h" #include "exclude.h" static void set_default_stat(struct stat *s) { time_t now = time(NULL); memset(s, 0, sizeof(struct stat)); s->st_mode = 0555; s->st_atime = s->st_mtime = s->st_ctime = now; } void iso_tree_add_child(struct iso_tree_node_dir *parent, struct iso_tree_node *child) { assert(parent && child); assert(!child->parent); parent->nchildren++; parent->children = realloc(parent->children, parent->nchildren * sizeof(void*)); parent->children[parent->nchildren-1] = child; child->parent = parent; } struct iso_tree_node_dir* iso_tree_new_root() { struct iso_tree_node_dir *root; root = calloc(1, sizeof(struct iso_tree_node_dir)); set_default_stat(&root->node.attrib); root->node.refcount = 1; root->node.attrib.st_mode = S_IFDIR | 0777; root->node.type = LIBISO_NODE_DIR; return root; } struct iso_tree_node* iso_tree_add_file(struct iso_tree_node_dir *parent, const char *path) { struct iso_tree_node_file *f; char *p; struct stat st; assert( parent && path); if (lstat(path, &st) == -1) { libisofs_errno = NO_FILE; return NULL; } if ( !S_ISREG(st.st_mode) ) { libisofs_errno = UNEXPECTED_FILE_TYPE; return NULL; } if ( access(path, R_OK) ) { libisofs_errno = NO_READ_ACCESS; return NULL; } f = calloc(1, sizeof(struct iso_tree_node_file)); /* fill fields */ f->node.refcount = 1; f->node.attrib = st; f->loc.path = strdup(path); f->node.type = LIBISO_NODE_FILE; p = strdup(path); /* because basename() might modify its arg */ f->node.name = strdup( basename(p) ); free(p); /* add to parent (this also sets f->node->parent) */ iso_tree_add_child(parent, (struct iso_tree_node*) f); return (struct iso_tree_node*) f; } struct iso_tree_node* iso_tree_add_symlink(struct iso_tree_node_dir *parent, const char *name, const char *dest) { struct iso_tree_node_symlink *link; assert( parent && name && dest); link = calloc(1, sizeof(struct iso_tree_node_symlink)); /* fill fields */ set_default_stat(&link->node.attrib); link->node.refcount = 1; link->node.attrib.st_mode |= S_IFLNK; link->node.name = strdup(name); link->node.type = LIBISO_NODE_SYMLINK; link->dest = strdup(dest); /* add to parent (this also sets link->node->parent) */ iso_tree_add_child(parent, (struct iso_tree_node*) link); return (struct iso_tree_node*) link; } struct iso_tree_node_dir* iso_tree_add_dir(struct iso_tree_node_dir *parent, const char *name) { struct iso_tree_node_dir *dir; assert( parent && name ); dir = calloc(1, sizeof(struct iso_tree_node_dir)); dir->node.refcount = 1; dir->node.attrib = parent->node.attrib; dir->node.type = LIBISO_NODE_DIR; dir->node.name = strdup(name); iso_tree_add_child(parent, (struct iso_tree_node*) dir); return dir; } enum iso_tree_node_type iso_tree_node_get_type(struct iso_tree_node *node) { assert(node); return node->type; } void iso_tree_node_set_name(struct iso_tree_node *node, const char *name) { free(node->name); node->name = strdup(name); } const char * iso_tree_node_get_name(struct iso_tree_node *node) { assert(node); return node->name; } void iso_tree_node_set_hidden(struct iso_tree_node *node, int hide_attrs) { assert(node); node->hide_flags = hide_attrs; } int iso_tree_node_is_hidden(struct iso_tree_node *node) { assert(node); return node->hide_flags; } void iso_tree_node_set_gid(struct iso_tree_node *node, gid_t gid) { assert(node); node->attrib.st_gid = gid; } gid_t iso_tree_node_get_gid(struct iso_tree_node *node) { assert(node); return node->attrib.st_gid; } void iso_tree_node_set_uid(struct iso_tree_node *node, uid_t uid) { assert(node); node->attrib.st_uid = uid; } uid_t iso_tree_node_get_uid(struct iso_tree_node *node) { assert(node); return node->attrib.st_uid; } void iso_tree_node_set_permissions(struct iso_tree_node *node, mode_t mode) { assert(node); node->attrib.st_mode = (node->attrib.st_mode & S_IFMT) | (mode & ~S_IFMT); } mode_t iso_tree_node_get_permissions(struct iso_tree_node *node) { assert(node); return node->attrib.st_mode & ~S_IFMT; } off_t iso_tree_node_get_size(struct iso_tree_node *node) { return node->attrib.st_size; } void iso_tree_node_set_mtime(struct iso_tree_node *node, time_t time) { node->attrib.st_mtime = time; } time_t iso_tree_node_get_mtime(struct iso_tree_node *node) { return node->attrib.st_mtime; } void iso_tree_node_set_atime(struct iso_tree_node *node, time_t time) { node->attrib.st_atime = time; } time_t iso_tree_node_get_atime(struct iso_tree_node *node) { return node->attrib.st_atime; } void iso_tree_node_set_ctime(struct iso_tree_node *node, time_t time) { node->attrib.st_ctime = time; } time_t iso_tree_node_get_ctime(struct iso_tree_node *node) { return node->attrib.st_ctime; } void iso_tree_node_set_sort_weight(struct iso_tree_node *node, int w) { assert(node); if ( ISO_ISDIR(node) ) { size_t i; struct iso_tree_node_dir *dir; dir = (struct iso_tree_node_dir *) node; for (i=0; i < dir->nchildren; i++) { iso_tree_node_set_sort_weight(dir->children[i], w); } } else if ( ISO_ISREG(node) ) { struct iso_tree_node_file *file; file = (struct iso_tree_node_file *) node; file->sort_weight = w; } } void iso_tree_node_symlink_set_dest(struct iso_tree_node_symlink *node, const char *dest) { assert(node && dest); free(node->dest); node->dest = strdup(dest); } const char * iso_tree_node_symlink_get_dest(struct iso_tree_node_symlink *node) { assert(node); return node->dest; } struct iso_tree_node* iso_tree_add_node(struct iso_tree_node_dir *parent, const char *path) { struct stat st; struct iso_tree_node *node; assert( parent && path); if (lstat(path, &st) == -1) { libisofs_errno = NO_FILE; return NULL; } if ( access(path, R_OK) ) { libisofs_errno = NO_READ_ACCESS; return NULL; } switch (st.st_mode & S_IFMT) { case S_IFREG: /* regular file */ node = iso_tree_add_file(parent, path); break; case S_IFLNK: /* symlink */ { char dest[PATH_MAX]; char *p; int n; n = readlink(path, dest, PATH_MAX); if ( n == -1 ) { libisofs_errno = INTERNAL_ERROR; return NULL; } dest[n] = '\0'; p = strdup(path); /* because basename() might modify its arg */ node = iso_tree_add_symlink(parent, basename(p), dest); free(p); node->attrib = st; } break; case S_IFDIR: /* directory */ { char *p; p = strdup(path); /* because basename() might modify its arg */ node = (struct iso_tree_node*) iso_tree_add_dir(parent, basename(p)); free(p); node->attrib = st; } break; default: libisofs_errno = UNEXPECTED_FILE_TYPE; node = NULL; break; } return node; } struct iso_tree_iter * iso_tree_node_children(struct iso_tree_node_dir *dir) { struct iso_tree_iter *iter; assert(dir); iter = malloc(sizeof(struct iso_tree_iter)); iter->dir = dir; iter->index = -1; return iter; } struct iso_tree_node * iso_tree_iter_next(struct iso_tree_iter *iter) { assert(iter); if ( ++iter->index < iter->dir->nchildren ) return iter->dir->children[iter->index]; else return NULL; } int iso_tree_iter_has_next(struct iso_tree_iter *iter) { assert(iter); return iter->index + 1 < iter->dir->nchildren; } void iso_tree_iter_free(struct iso_tree_iter *iter) { free(iter); } int iso_tree_node_take(struct iso_tree_node_dir *dir, struct iso_tree_node *node) { int i; assert(dir && node); /* search for the node in the dir */ for (i = 0; i < dir->nchildren; ++i) { if ( dir->children[i] == node ) break; } if (i < dir->nchildren) { int j; for (j = i+1; j < dir->nchildren; ++j) { dir->children[j-1] = dir->children[j]; } --dir->nchildren; dir->children = realloc(dir->children, dir->nchildren * sizeof(void*)); node->parent = NULL; return 0; } else { /* the node doesn't exist on dir */ return -1; } } int iso_tree_node_remove(struct iso_tree_node_dir *dir, struct iso_tree_node *node) { int res; assert(dir && node); res = iso_tree_node_take(dir, node); if (!res) iso_tree_free(node); return res; } int iso_tree_node_take_iter(struct iso_tree_iter *iter) { int j; struct iso_tree_node_dir *dir; struct iso_tree_node *node; assert(iter); dir = iter->dir; if (iter->index < 0) return -1; /* index before beginning */ if (iter->index >= dir->nchildren) return -2; /* index after end */ node = dir->children[iter->index]; node->parent = NULL; for (j = iter->index+1; j < dir->nchildren; ++j) { dir->children[j-1] = dir->children[j]; } --dir->nchildren; dir->children = realloc(dir->children, dir->nchildren * sizeof(void*)); /* update iter index */ --iter->index; return 0; } int iso_tree_node_remove_iter(struct iso_tree_iter *iter) { int j; struct iso_tree_node_dir *dir; struct iso_tree_node *node; assert(iter); dir = iter->dir; if (iter->index < 0) return -1; /* index before beginning */ if (iter->index >= dir->nchildren) return -2; /* index after end */ node = dir->children[iter->index]; for (j = iter->index+1; j < dir->nchildren; ++j) { dir->children[j-1] = dir->children[j]; } --dir->nchildren; dir->children = realloc(dir->children, dir->nchildren * sizeof(void*)); /* update iter index */ --iter->index; /* and free node */ node->parent = NULL; iso_tree_free(node); return 0; } struct iso_tree_node_dir * iso_tree_node_get_parent(struct iso_tree_node *node) { assert(node); return node->parent; } void iso_tree_node_ref(struct iso_tree_node *node) { ++node->refcount; } void iso_tree_free(struct iso_tree_node *root) { if (!root) return; if (--root->refcount < 1) { if ( ISO_ISDIR(root) ) { size_t i; struct iso_tree_node_dir *dir; dir = (struct iso_tree_node_dir *) root; for (i=0; i < dir->nchildren; i++) { iso_tree_free(dir->children[i]); } free(dir->children); } else if ( ISO_ISLNK(root) ) { struct iso_tree_node_symlink *link; link = (struct iso_tree_node_symlink *) root; free(link->dest); } else if ( ISO_ISREG(root) ) { struct iso_tree_node_file *file; file = (struct iso_tree_node_file *) root; if (root->procedence == LIBISO_NEW) free(file->loc.path); } else if ( ISO_ISBOOT(root) ) { struct iso_tree_node_boot *boot; boot = (struct iso_tree_node_boot *) root; if (root->procedence == LIBISO_NEW && boot->img) free(boot->loc.path); } free(root->name); free(root); } } static void iso_tree_radd_dir_aux(struct iso_tree_node_dir *parent, const char *path, struct iso_tree_radd_dir_behavior *behavior, struct iso_hash_table *excludes) { struct iso_tree_node *new; DIR *dir; struct dirent *ent; dir = opendir(path); if (!dir) { warn("couldn't open directory %s: %s\n", path, strerror(errno)); return; } while ((ent = readdir(dir))) { char child[strlen(ent->d_name) + strlen(path) + 2]; if (behavior->stop_on_error & behavior->error) break; if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue; //TODO check if path already finished in '/' sprintf(child, "%s/%s", path, ent->d_name); /* see if this child is excluded. */ if (iso_exclude_lookup(excludes, child)) continue; new = iso_tree_add_node(parent, child); if (!new || !ISO_ISDIR(new)) { if (!new) behavior->error = 1; continue; } iso_tree_radd_dir_aux( (struct iso_tree_node_dir *) new, child, behavior, excludes); } closedir(dir); return; } void iso_tree_radd_dir(struct iso_tree_node_dir *parent, const char *path, struct iso_tree_radd_dir_behavior *behavior) { struct iso_tree_node_dir *dir; struct iso_hash_table table = { {0,}, 0}; assert ( parent && path ); behavior->error = 0; /* initialize exclude hash_table */ if ( behavior->excludes ) { char *exclude; int i = 0; while ( (exclude = behavior->excludes[i++]) ) { iso_exclude_add_path(&table, exclude); } } /* recurse into dir */ iso_tree_radd_dir_aux(parent, path, behavior, &table); /* clear hashtable */ iso_exclude_empty(&table); } void iso_tree_print(const struct iso_tree_node *root, int spaces) { char sp[spaces+1]; memset(sp, ' ', spaces); sp[spaces] = '\0'; printf("%s%s\n", sp, root->name); if ( ISO_ISDIR(root) ) { size_t i; struct iso_tree_node_dir *dir; dir = (struct iso_tree_node_dir *) root; for (i=0; i < dir->nchildren; i++) { iso_tree_print(dir->children[i], spaces+2); } } } void iso_tree_print_verbose(const struct iso_tree_node *root, print_dir_callback dir, print_file_callback file, void *callback_data, int spaces) { (ISO_ISDIR(root) ? dir : file) (root, callback_data, spaces); if ( ISO_ISDIR(root) ) { size_t i; struct iso_tree_node_dir *dir_node; dir_node = (struct iso_tree_node_dir *) root; for (i=0; i < dir_node->nchildren; i++) { iso_tree_print_verbose(dir_node->children[i], dir, file, callback_data, spaces+2); } } }