static int read_mtree_spec(FILE *fp) { char pathspec[PATH_MAX]; char *cp; int error; error = read_word(fp, pathspec, sizeof(pathspec)); if (error) goto out; cp = strchr(pathspec, '/'); if (cp != NULL) { /* Absolute pathname */ mtree_current = mtree_root; do { *cp++ = '\0'; /* Disallow '..' as a component. */ if (IS_DOTDOT(pathspec)) { mtree_error("absolute path cannot contain " ".. component"); goto out; } /* Ignore multiple adjacent slashes and '.'. */ if (pathspec[0] != '\0' && !IS_DOT(pathspec)) error = read_mtree_spec1(fp, false, pathspec); memmove(pathspec, cp, strlen(cp) + 1); cp = strchr(pathspec, '/'); } while (!error && cp != NULL); /* Disallow '.' and '..' as the last component. */ if (!error && (IS_DOT(pathspec) || IS_DOTDOT(pathspec))) { mtree_error("absolute path cannot contain . or .. " "components"); goto out; } } /* Ignore absolute specfications that end with a slash. */ if (!error && pathspec[0] != '\0') error = read_mtree_spec1(fp, true, pathspec); out: skip_to(fp, "\n"); (void)getc(fp); return (error); }
/* * ndmpd_fhdir_v3_cb * * Callback function for file history dir information */ int ndmpd_fhdir_v3_cb(lbr_fhlog_call_backs_t *cbp, char *dir, struct stat64 *stp) { char nm[PATH_MAX+1]; int nml; int err; ulong_t ino, pino; ulong_t pos; ndmp_lbr_params_t *nlp; ndmpd_module_params_t *params; DIR *dirp; char dirpath[PATH_MAX]; if (!cbp) { err = -1; syslog(LOG_DEBUG, "cbp is NULL"); } else if (!cbp->fh_cookie) { err = -1; syslog(LOG_DEBUG, "cookie is NULL"); } else if (!dir) { err = -1; syslog(LOG_DEBUG, "dir is NULL"); } else if (!(nlp = ndmp_get_nlp(cbp->fh_cookie))) { err = -1; syslog(LOG_DEBUG, "nlp is NULL"); } else err = 0; if (err != 0) return (0); if (!NLP_ISSET(nlp, NLPF_FH)) return (0); /* * Veritas net_backup accepts only 2 as the inode number of the backup * root directory. The other way compares the path against the * backup path which is slower. */ if (stp->st_ino == nlp->nlp_bkdirino) pino = ROOT_INODE; else pino = stp->st_ino; /* * There is nothing below this directory to be backed up. * If there was, the bit for this directory would have * been set. Backup root directory is exception. We * always send the dir file history records of it. */ if (pino != ROOT_INODE && !dbm_getone(nlp->nlp_bkmap, (u_longlong_t)stp->st_ino)) { syslog(LOG_DEBUG, "nothing below here"); return (0); } params = nlp->nlp_params; if (!params || !params->mp_file_history_dir_func) return (-1); pos = 0; err = 0; dirp = opendir(dir); if (dirp == NULL) return (0); do { nml = PATH_MAX; err = dp_readdir(dirp, &pos, nm, &nml, &ino); if (err != 0) { syslog(LOG_DEBUG, "%d reading pos %u dir \"%s\"", err, pos, dir); break; } if (nml == 0) break; nm[nml] = '\0'; if (pino == ROOT_INODE) { if (rootfs_dot_or_dotdot(nm)) ino = ROOT_INODE; } else if (ino == nlp->nlp_bkdirino && IS_DOTDOT(nm)) { ino = ROOT_INODE; } if (!dbm_getone(nlp->nlp_bkmap, (u_longlong_t)ino)) continue; /* * If the entry is on exclusion list dont send the info */ if (tlm_is_excluded(dir, nm, ndmp_excl_list)) { syslog(LOG_DEBUG, "name \"%s\" skipped", nm == 0 ? "nil" : nm); continue; } err = (*params->mp_file_history_dir_func)(cbp->fh_cookie, nm, ino, pino); if (err < 0) { syslog(LOG_ERR, "\"%s\": %d", dir, err); break; } /* * This is a requirement by some DMA's (net_vault) that during * the incremental backup, the node info should also be sent * along with the dir info for all directories leading to a * backed up file. */ if (ndmp_fhinode) { struct stat64 ret_attr; (void) strlcpy(dirpath, dir, PATH_MAX); (void) strlcat(dirpath, "/", PATH_MAX); (void) strlcat(dirpath, nm, PATH_MAX); err = stat64(dirpath, &ret_attr); if (err != 0) { syslog(LOG_ERR, "Error looking up %s", nm); break; } if (S_ISDIR(ret_attr.st_mode)) { err = (*params->mp_file_history_node_func)(cbp-> fh_cookie, ino, &ret_attr, 0); if (err < 0) { syslog(LOG_ERR, "\"%s/\": %d", dir, err); break; } } } } while (err == 0); (void) closedir(dirp); return (err); }
static int read_mtree_spec1(FILE *fp, bool def, const char *name) { fsnode *last, *node, *parent; u_int type; int error; assert(name[0] != '\0'); /* * Treat '..' specially, because it only changes our current * directory. We don't create a node for it. We simply ignore * any keywords that may appear on the line as well. * Going up a directory is a little non-obvious. A directory * node has a corresponding '.' child. The parent of '.' is * not the '.' node of the parent directory, but the directory * node within the parent to which the child relates. However, * going up a directory means we need to find the '.' node to * which the directoy node is linked. This we can do via the * first * pointer, because '.' is always the first entry in a * directory. */ if (IS_DOTDOT(name)) { /* This deals with NULL pointers as well. */ if (mtree_current == mtree_root) { mtree_warning("ignoring .. in root directory"); return (0); } node = mtree_current; assert(node != NULL); assert(IS_DOT(node->name)); assert(node->first == node); /* Get the corresponding directory node in the parent. */ node = mtree_current->parent; assert(node != NULL); assert(!IS_DOT(node->name)); node = node->first; assert(node != NULL); assert(IS_DOT(node->name)); assert(node->first == node); mtree_current = node; return (0); } /* * If we don't have a current directory and the first specification * (either implicit or defined) is not '.', then we need to create * a '.' node first (using a recursive call). */ if (!IS_DOT(name) && mtree_current == NULL) { error = read_mtree_spec1(fp, false, "."); if (error) return (error); } /* * Lookup the name in the current directory (if we have a current * directory) to make sure we do not create multiple nodes for the * same component. For non-definitions, if we find a node with the * same name, simply change the current directory. For definitions * more happens. */ last = NULL; node = mtree_current; while (node != NULL) { assert(node->first == mtree_current); if (strcmp(name, node->name) == 0) { if (def == true) { if (!dupsok) mtree_error( "duplicate definition of %s", name); else mtree_warning( "duplicate definition of %s", name); return (0); } if (node->type != S_IFDIR) { mtree_error("%s is not a directory", name); return (0); } assert(!IS_DOT(name)); node = node->child; assert(node != NULL); assert(IS_DOT(node->name)); mtree_current = node; return (0); } last = node; node = last->next; } parent = (mtree_current != NULL) ? mtree_current->parent : NULL; type = (def == false || IS_DOT(name)) ? S_IFDIR : 0; node = create_node(name, type, parent, &mtree_global); if (node == NULL) return (ENOMEM); if (def == true) { error = read_mtree_keywords(fp, node); if (error) { destroy_node(node); return (error); } } node->first = (mtree_current != NULL) ? mtree_current : node; if (last != NULL) last->next = node; if (node->type != S_IFDIR) return (0); if (!IS_DOT(node->name)) { parent = node; node = create_node(".", S_IFDIR, parent, parent); if (node == NULL) { last->next = NULL; destroy_node(parent); return (ENOMEM); } parent->child = node; node->first = node; } assert(node != NULL); assert(IS_DOT(node->name)); assert(node->first == node); mtree_current = node; if (mtree_root == NULL) mtree_root = node; return (0); }
/* dnormalize(): * The path will be normalized if it * 1) is "..", * 2) or starts with "../", * 3) or ends with "/..", * 4) or contains the string "/../", * then it will be normalized, unless those strings are quoted. * Otherwise, a copy is made and sent back. */ Char * dnormalize(const Char *cp, int expnd) { /* return true if dp is of the form "../xxx" or "/../xxx" */ #define IS_DOTDOT(sp, p) (ISDOTDOT(p) && ((p) == (sp) || *((p) - 1) == '/')) #define IS_DOT(sp, p) (ISDOT(p) && ((p) == (sp) || *((p) - 1) == '/')) #ifdef S_IFLNK if (expnd) { struct Strbuf buf = Strbuf_INIT; int dotdot = 0; Char *dp, *cwd; const Char *start = cp; # ifdef HAVE_SLASHSLASH int slashslash; # endif /* HAVE_SLASHSLASH */ /* * count the number of "../xxx" or "xxx/../xxx" in the path */ for ( ; *cp && *(cp + 1); cp++) if (IS_DOTDOT(start, cp)) dotdot++; /* * if none, we are done. */ if (dotdot == 0) return (Strsave(start)); # ifdef notdef struct stat sb; /* * We disable this test because: * cd /tmp; mkdir dir1 dir2; cd dir2; ln -s /tmp/dir1; cd dir1; * echo ../../dir1 does not expand. We had enabled this before * because it was bothering people with expansions in compilation * lines like -I../../foo. Maybe we need some kind of finer grain * control? * * If the path doesn't exist, we are done too. */ if (lstat(short2str(start), &sb) != 0 && errno == ENOENT) return (Strsave(start)); # endif cwd = xmalloc((Strlen(dcwd->di_name) + 3) * sizeof(Char)); (void) Strcpy(cwd, dcwd->di_name); /* * If the path starts with a slash, we are not relative to * the current working directory. */ if (ABSOLUTEP(start)) *cwd = '\0'; # ifdef HAVE_SLASHSLASH slashslash = cwd[0] == '/' && cwd[1] == '/'; # endif /* HAVE_SLASHSLASH */ /* * Ignore . and count ..'s */ cp = start; do { dotdot = 0; buf.len = 0; while (*cp) if (IS_DOT(start, cp)) { if (*++cp) cp++; } else if (IS_DOTDOT(start, cp)) { if (buf.len != 0) break; /* finish analyzing .././../xxx/[..] */ dotdot++; cp += 2; if (*cp) cp++; } else Strbuf_append1(&buf, *cp++); Strbuf_terminate(&buf); while (dotdot > 0) if ((dp = Strrchr(cwd, '/')) != NULL) { # ifdef HAVE_SLASHSLASH if (dp == &cwd[1]) slashslash = 1; # endif /* HAVE_SLASHSLASH */ *dp = '\0'; dotdot--; } else break; if (!*cwd) { /* too many ..'s, starts with "/" */ cwd[0] = '/'; # ifdef HAVE_SLASHSLASH /* * Only append another slash, if already the former cwd * was in a double-slash path. */ cwd[1] = slashslash ? '/' : '\0'; cwd[2] = '\0'; # else /* !HAVE_SLASHSLASH */ cwd[1] = '\0'; # endif /* HAVE_SLASHSLASH */ } # ifdef HAVE_SLASHSLASH else if (slashslash && cwd[1] == '\0') { cwd[1] = '/'; cwd[2] = '\0'; } # endif /* HAVE_SLASHSLASH */ if (buf.len != 0) { size_t i; i = Strlen(cwd); if (TRM(cwd[i - 1]) != '/') { cwd[i++] = '/'; cwd[i] = '\0'; } dp = Strspl(cwd, TRM(buf.s[0]) == '/' ? &buf.s[1] : buf.s); xfree(cwd); cwd = dp; i = Strlen(cwd) - 1; if (TRM(cwd[i]) == '/') cwd[i] = '\0'; } /* Reduction of ".." following the stuff we collected in buf * only makes sense if the directory item in buf really exists. * Avoid reduction of "-I../.." (typical compiler call) to "" * or "/usr/nonexistant/../bin" to "/usr/bin": */ if (cwd[0]) { struct stat exists; if (0 != stat(short2str(cwd), &exists)) { xfree(buf.s); xfree(cwd); return Strsave(start); } } } while (*cp != '\0'); xfree(buf.s); return cwd; } #endif /* S_IFLNK */ return Strsave(cp); }
/* * Read directory structure and store entries in `entries', which must initially * point to an empty list. */ static int read_path(struct mtree_reader *r, const char *path, struct mtree_entry **entries, struct mtree_entry *parent) { DIR *dirp; struct dirent *dp; struct mtree_entry *entry; struct mtree_entry *dirs = NULL; struct mtree_entry *dot = NULL; char *wd = NULL; int ret; int skip; int skip_children; if (parent == NULL) { entry = mtree_entry_create(path); if (entry == NULL) return (-1); ret = read_path_file(r, entry, &skip, &skip_children); if (ret == -1) { mtree_entry_free(entry); return (-1); } /* * If the path doesn't point to a directory, simply store * the single entry. */ if (entry->data.type != MTREE_ENTRY_DIR) { *entries = entry; return (0); } mtree_entry_free(entry); if ((r->options & MTREE_READ_PATH_DONT_CROSS_MOUNT) != 0) { struct stat st; if ((r->options & MTREE_READ_PATH_FOLLOW_SYMLINKS) != 0) ret = stat(path, &st); else ret = lstat(path, &st); if (ret == -1) { mtree_reader_set_error(r, errno, "`%s'", path); return (-1); } r->base_dev = st.st_dev; } /* * Change to the directory to be able to read the files * with the mtree's "./" prefix. */ wd = mtree_getcwd(); if (wd == NULL) { mtree_reader_set_error(r, errno, "Could not determine the current working directory"); return (-1); } if (chdir(path) == -1) { mtree_reader_set_error(r, errno, "Could not change the working directory to `%s'", path); return (-1); } } /* * Read the directory structure. */ ret = 0; if ((dirp = opendir((parent != NULL) ? path : ".")) == NULL) { if ((r->options & MTREE_READ_PATH_SKIP_ON_ERROR) == 0) { ret = -1; goto end; } return (0); } while ((dp = readdir(dirp)) != NULL) { if (IS_DOTDOT(dp->d_name)) continue; /* * Dot is read only in the initial directory. */ if (parent != NULL && IS_DOT(dp->d_name)) continue; entry = mtree_entry_create_empty(); if (entry == NULL) { ret = -1; break; } entry->name = strdup(dp->d_name); if (entry->name == NULL) { ret = -1; break; } entry->parent = parent; entry->path = create_v1_path(entry); if (entry->path == NULL) { ret = -1; break; } ret = read_path_file(r, entry, &skip, &skip_children); if (ret == -1) break; if (IS_DOT(dp->d_name)) { if (skip) mtree_entry_free(entry); else dot = entry; if (skip_children) { /* * This is a bit artificial, but when the user * asks to skip children below ".", remove * all the entries, except for the dot itself * (unless the dot is skipped as well). */ mtree_entry_free_all(*entries); mtree_entry_free_all(dirs); *entries = NULL; dirs = NULL; break; } continue; } if (skip && skip_children) { mtree_entry_free(entry); continue; } if (entry->data.type == MTREE_ENTRY_DIR) { if (skip) entry->flags |= __MTREE_ENTRY_SKIP; if (skip_children) entry->flags |= __MTREE_ENTRY_SKIP_CHILDREN; dirs = mtree_entry_prepend(dirs, entry); } else if (!skip) *entries = mtree_entry_prepend(*entries, entry); } end: if (ret == 0) { if (dot != NULL) { /* Put the initial dot at the (reversed) start. */ *entries = mtree_entry_append(*entries, dot); } closedir(dirp); /* Directories are processed after files. */ entry = dirs; while (entry != NULL) { struct mtree_entry *next = entry->next; if ((entry->flags & __MTREE_ENTRY_SKIP) == 0) { dirs = mtree_entry_unlink(dirs, entry); *entries = mtree_entry_prepend(*entries, entry); } if ((entry->flags & __MTREE_ENTRY_SKIP_CHILDREN) == 0) ret = read_path(r, entry->path, entries, entry); if (ret == -1) break; entry = next; } } else { /* Fatal error, clean up and make our way back to the caller. */ mtree_entry_free_all(*entries); *entries = NULL; } mtree_entry_free_all(dirs); if (parent == NULL && chdir(wd) == -1) WARN("Could not change the working directory back to `%s'", wd); return (ret); }
static int read_spec(struct mtree_reader *r, char *s) { struct mtree_entry *entry; char name[MAXPATHLEN]; char *slash, *file, *word, *next; int ret; int skip = 0; /* Try to detect the format if it isn't known yet. */ if (r->path_last == -1 && detect_format(r, s) == -1) return (-1); if (r->path_last != 1) { /* * Path is surely in the first field, either because we know * the format or there is just one single field. */ read_word(s, &file, &next); assert(file != NULL); if (IS_DOTDOT(file)) { /* Only change the parent, keywords are ignored. */ if (r->parent == NULL) { mtree_reader_set_error(r, EINVAL, "`..' not allowed, no parent directory"); return (-1); } r->parent = r->parent->parent; return (0); } } entry = mtree_entry_create_empty(); if (entry == NULL) { mtree_reader_set_error(r, errno, NULL); return (-1); } if (r->path_last != 1) { if (next != NULL) { /* Read keyword that follow the path. */ ret = read_keywords(r, next, &entry->data, 1); if (ret == -1) { mtree_entry_free(entry); return (-1); } } } else { /* Path is at the end, read the keywords first. */ for (file = NULL; file == NULL;) { read_word(s, &word, &next); assert(word != NULL); if (next != NULL) { ret = read_keyword(r, word, &entry->data, 1); if (ret == -1) { mtree_entry_free(entry); return (-1); } s = next; } else file = word; } assert(file != NULL); if (IS_DOTDOT(file)) { mtree_reader_set_error(r, EINVAL, "`..' not allowed in " "this format"); mtree_entry_free(entry); return (-1); } } /* Copy /set values to the entry. */ mtree_entry_data_copy_keywords(&entry->data, &r->defaults, r->defaults.keywords, 0); /* * See if we should skip this file. */ if (SKIP_TYPE(r->options, entry->data.type)) { /* * With directories in version 1.0 format, we'll have to worry * about potentially setting the directory as the current * parent, other types can be skipped immediately. */ if (entry->data.type != MTREE_ENTRY_DIR || r->path_last == 1) goto skip; else skip = 1; } /* Save the file path and name. */ if (strnunvis(name, sizeof(name), file) == -1) { mtree_reader_set_error(r, ENAMETOOLONG, "File name too long: `%s'", file); mtree_entry_free(entry); return (-1); } if ((slash = strchr(name, '/')) != NULL) { /* * If the name contains a slash, it is a relative path. * These do not change the working directory. */ if (skip) goto skip; ret = mtree_cleanup_path(name, &entry->path, &entry->name); if (ret == -1) { mtree_reader_set_error(r, errno, NULL); mtree_entry_free(entry); return (-1); } } else { entry->name = strdup(name); if (entry->name == NULL) { mtree_reader_set_error(r, errno, NULL); mtree_entry_free(entry); return (-1); } entry->parent = r->parent; entry->path = create_v1_path(entry); if (entry->path == NULL) { mtree_reader_set_error(r, errno, NULL); mtree_entry_free(entry); return (-1); } } /* * We may want to skip this entry if a filter told us to skip * children of a directory, which is a parent of this entry. * * There is also no need to worry about changing the parent if * this happens. */ if (!skip && r->skip_trie != NULL) { char *tok; char *endptr; for (tok = strtok_r(name, "/", &endptr); tok != NULL; tok = strtok_r(NULL, "/", &endptr)) { if (mtree_trie_find(r->skip_trie, tok) != NULL) { mtree_entry_free(entry); return (0); } } } /* * Mark the current entry as the current directory. This only applies * to classic (1.0) entries and must be done after keywords are read. */ if (slash == NULL && entry->data.type == MTREE_ENTRY_DIR) r->parent = entry; if (skip) goto skip; if (r->filter != NULL) { int result; /* Apply filter. */ result = r->filter(entry, r->filter_data); if (result & MTREE_ENTRY_SKIP_CHILDREN) { if (entry->data.type == MTREE_ENTRY_DIR) { struct mtree_entry *found, *start; if (r->skip_trie == NULL) { r->skip_trie = mtree_trie_create(NULL); if (r->skip_trie == NULL) { mtree_reader_set_error(r, errno, NULL); return (-1); } } if (mtree_trie_insert(r->skip_trie, entry->path, TRIE_ITEM) == -1) { mtree_reader_set_error(r, errno, NULL); return (-1); } strcpy(name, entry->path); strcat(name, "/"); /* * Remove children that are already in the * list. */ start = r->entries; for (;;) { found = mtree_entry_find_prefix(start, name); if (found == NULL) break; start = found->next; r->entries = mtree_entry_unlink( r->entries, found); mtree_entry_free(found); } } } if (result & MTREE_ENTRY_SKIP) goto skip; } r->entries = mtree_entry_prepend(r->entries, entry); return (0); skip: /* * Skipping directory which is the current parent would make its files * end up in a wrong directory. * * Keep such entries in a list of `loose' entries instead, these will * be deleted when all the work is done. */ if (entry == r->parent) r->loose = mtree_entry_prepend(r->loose, entry); else mtree_entry_free(entry); return (0); }