/* * Traverse the file system in the level-order way. The description * and example is in the header file. */ int traverse_level(struct fs_traverse *ftp) { char path[PATH_MAX + 1]; /* full path name of the current dir */ char nm[NAME_MAX + 1]; /* directory entry name */ char *lp; /* last position on the path */ int next_dir, rv; int pl, el; /* path and directory entry length */ cstack_t *sp; fs_fhandle_t pfh, efh; struct stat64 pst, est; traverse_state_t *tsp; struct fst_node pn, en; /* parent and entry nodes */ dent_arg_t darg; if (!ftp || !ftp->ft_path || !*ftp->ft_path || !ftp->ft_callbk) { NDMP_LOG(LOG_DEBUG, "Invalid argument"); errno = EINVAL; return (-1); } /* set the default log function if it's not already set */ if (!ftp->ft_logfp) { ftp->ft_logfp = (ft_log_t)syslog; NDMP_LOG(LOG_DEBUG, "Log to system log \"%s\"", ftp->ft_path); } if (!ftp->ft_lpath) { NDMP_LOG(LOG_DEBUG, "report the same paths \"%s\"", ftp->ft_path); ftp->ft_lpath = ftp->ft_path; } pl = strlen(ftp->ft_lpath); if (pl + 1 > PATH_MAX) { /* +1 for the '/' */ NDMP_LOG(LOG_DEBUG, "lpath too long \"%s\"", ftp->ft_path); errno = ENAMETOOLONG; return (-1); } (void) strcpy(path, ftp->ft_lpath); (void) memset(&pfh, 0, sizeof (pfh)); rv = fs_getstat(ftp->ft_lpath, &pfh, &pst); if (rv != 0) { NDMP_LOG(LOG_DEBUG, "Error %d on fs_getstat(%s)", rv, ftp->ft_path); return (-1); } en.tn_path = NULL; en.tn_fh = NULL; en.tn_st = NULL; if (!S_ISDIR(pst.st_mode)) { pn.tn_path = ftp->ft_lpath; pn.tn_fh = &pfh; pn.tn_st = &pst; rv = CALLBACK(&pn, &en); if (VERBOSE(ftp)) NDMP_LOG(LOG_DEBUG, "CALLBACK(%s): %d", pn.tn_path, rv); free(pfh.fh_fpath); return (rv); } sp = cstack_new(); if (!sp) { free(pfh.fh_fpath); errno = ENOMEM; return (-1); } tsp = new_tsp(path); if (!tsp) { cstack_delete(sp); free(pfh.fh_fpath); errno = ENOMEM; return (-1); } darg.da_buf = ndmp_malloc(MAX_DENT_BUF_SIZE); if (!darg.da_buf) { cstack_delete(sp); free(pfh.fh_fpath); free(tsp); errno = ENOMEM; return (-1); } darg.da_size = MAX_DENT_BUF_SIZE; tsp->ts_ent = tsp->ts_end; tsp->ts_fh = pfh; tsp->ts_st = pst; pn.tn_path = path; pn.tn_fh = &tsp->ts_fh; pn.tn_st = &tsp->ts_st; /* call the callback function on the path itself */ traverse_stats.fss_dir_calls++; rv = CALLBACK(&pn, &en); if (rv < 0) { free(tsp); goto end; } if (rv == FST_SKIP) { traverse_stats.fss_dir_skipped++; free(tsp); rv = 0; goto end; } rv = 0; next_dir = 1; do { if (next_dir) { traverse_stats.fss_newdirs++; *tsp->ts_end = '\0'; if (VERBOSE(ftp)) NDMP_LOG(LOG_DEBUG, "pl %d \"%s\"", pl, path); rv = traverse_level_nondir(ftp, tsp, &pn, &darg); if (rv < 0) { NEGATE(rv); free(tsp->ts_fh.fh_fpath); free(tsp); break; } /* * If skipped by the callback function or * error happened reading the information */ if (rv == FST_SKIP || rv == SKIP_ENTRY) { /* * N.B. next_dir should be set to 0 as * well. This prevents the infinite loop. * If it's not set the same directory will * be poped from the stack and will be * scanned again. */ next_dir = 0; rv = 0; goto skip_dir; } /* re-start reading entries of the directory */ tsp->ts_dpos = 0; } next_dir = 0; do { el = NAME_MAX; rv = fs_readdir(&tsp->ts_fh, pn.tn_path, &tsp->ts_dpos, nm, &el, &efh, &est); if (rv != 0) { traverse_stats.fss_readdir_err++; NDMP_LOG(LOG_DEBUG, "Error %d on readdir(%s) pos %d", rv, path, tsp->ts_dpos); if (STOP_ONERR(ftp)) break; rv = SKIP_ENTRY; continue; } /* done with this directory */ if (el == 0) break; nm[el] = '\0'; if (rootfs_dot_or_dotdot(nm)) { free(efh.fh_fpath); continue; } if (VERBOSE(ftp)) NDMP_LOG(LOG_DEBUG, "%u dname: \"%s\"", tsp->ts_dpos, nm); if (pl + 1 + el > PATH_MAX) { /* * The long paths were already encountered * when processing non-dir entries in. * traverse_level_nondir. * We don't increase fss_longpath_err * counter for them again here. */ NDMP_LOG(LOG_ERR, "Path %s/%s is too long.", path, nm); if (STOP_ONLONG(ftp)) rv = ENAMETOOLONG; free(efh.fh_fpath); continue; } if (!S_ISDIR(est.st_mode)) continue; /* * Call the callback function for the new * directory found, then push the current * directory on to the stack. Then dive * into the entry found. */ traverse_stats.fss_dir_calls++; en.tn_path = nm; en.tn_fh = &efh; en.tn_st = &est; rv = CALLBACK(&pn, &en); if (rv < 0) { NEGATE(rv); free(efh.fh_fpath); break; } if (rv == FST_SKIP) { traverse_stats.fss_dir_skipped++; free(efh.fh_fpath); rv = 0; continue; } /* * Push the current directory on to the stack and * dive into the entry found. */ if (cstack_push(sp, tsp, 0)) { rv = ENOMEM; } else { traverse_stats.fss_pushes++; lp = tsp->ts_end; *tsp->ts_end = '/'; (void) strcpy(tsp->ts_end + 1, nm); tsp = new_tsp(path); if (!tsp) rv = ENOMEM; else { next_dir = 1; pl += el + 1; tsp->ts_fh = efh; tsp->ts_st = est; tsp->ts_ent = lp; pn.tn_fh = &tsp->ts_fh; pn.tn_st = &tsp->ts_st; } } break; } while (rv == 0); /* * A new directory must be processed, go to the start of * the loop, open it and process it. */ if (next_dir) continue; skip_dir: if (tsp) { free(tsp->ts_fh.fh_fpath); free(tsp); } if (rv == SKIP_ENTRY) rv = 0; if (rv == 0) { if (cstack_pop(sp, (void **)&tsp, (int *)NULL)) break; traverse_stats.fss_pops++; if (VERBOSE(ftp)) NDMP_LOG(LOG_DEBUG, "Poped pl %d \"%s\"", pl, path); *tsp->ts_end = '\0'; pl = tsp->ts_end - path; pn.tn_fh = &tsp->ts_fh; pn.tn_st = &tsp->ts_st; } } while (rv == 0); /* * Pop and free all the remaining entries on the stack. */ while (!cstack_pop(sp, (void **)&tsp, (int *)NULL)) { traverse_stats.fss_stack_residue++; free(tsp->ts_fh.fh_fpath); free(tsp); } end: free(darg.da_buf); cstack_delete(sp); return (rv); }
/* * backup_work * * Start the NDMP backup (V2 only). */ int backup_work(char *bk_path, tlm_job_stats_t *job_stats, ndmp_run_args_t *np, tlm_commands_t *commands, ndmp_lbr_params_t *nlp) { struct full_dir_info dir_info; /* the blob to push/pop with cstack_t */ struct full_dir_info *t_dir_info, *p_dir_info; struct stat64 ret_attr; /* attributes of current file name */ fs_fhandle_t ret_fh; char *first_name; /* where the first name is located */ char *dname; int erc; int retval; cstack_t *stk; unsigned long fileid; tlm_acls_t tlm_acls; int dname_size; longlong_t fsize; bk_selector_t bks; tlm_cmd_t *local_commands; long dpos; NDMP_LOG(LOG_DEBUG, "nr_chkpnted %d nr_ldate: %u bk_path: \"%s\"", NLP_ISCHKPNTED(nlp), nlp->nlp_ldate, bk_path); /* Get every name in this directory */ dname = ndmp_malloc(TLM_MAX_PATH_NAME); if (dname == NULL) return (-ENOMEM); local_commands = commands->tcs_command; retval = 0; (void) memset(&bks, 0, sizeof (bks)); bks.bs_cookie = (void *)nlp; bks.bs_level = nlp->nlp_clevel; bks.bs_ldate = nlp->nlp_ldate; bks.bs_fn = timecmp; /* * should we skip the whole thing? */ if (tlm_is_excluded("", bk_path, np->nr_excls)) { NDMP_LOG(LOG_DEBUG, "%s excluded", bk_path); free(dname); return (0); } /* * Search for the top-level file-directory */ if (NLP_ISCHKPNTED(nlp)) { first_name = np->nr_chkp_nm; (void) strlcpy(first_name, bk_path, TLM_MAX_PATH_NAME); } else { first_name = tlm_build_snapshot_name(bk_path, np->nr_chkp_nm, nlp->nlp_jstat->js_job_name); } (void) memset(&ret_fh, 0, sizeof (ret_fh)); erc = fs_getstat(first_name, &ret_fh, &ret_attr); if (erc != 0) { NDMP_LOG(LOG_ERR, "Path %s not found.", first_name); free(dname); return (-EINVAL); } if ((stk = cstack_new()) == NULL) { free(dname); NDMP_LOG(LOG_DEBUG, "cstack_new failed"); return (-ENOMEM); } (void) strlcpy(dir_info.fd_dir_name, first_name, TLM_MAX_PATH_NAME); (void) memcpy(&dir_info.fd_dir_fh, &ret_fh, sizeof (fs_fhandle_t)); p_dir_info = dup_dir_info(&dir_info); /* * Push the first name onto the stack so that we can pop it back * off as part of the normal cycle */ if (cstack_push(stk, p_dir_info, 0)) { free(dname); free(p_dir_info); cstack_delete(stk); NDMP_LOG(LOG_DEBUG, "cstack_push failed"); return (-ENOMEM); } (void) memset(&tlm_acls, 0, sizeof (tlm_acls)); /* * Did NDMP create a checkpoint? */ if (NLP_ISCHKPNTED(nlp) || fs_is_rdonly(bk_path)) { tlm_acls.acl_checkpointed = FALSE; } else { /* Use the checkpoint created by NDMP */ tlm_acls.acl_checkpointed = TRUE; } /* * This is level-backup. It never resets the archive bit. */ tlm_acls.acl_clear_archive = FALSE; NDMP_LOG(LOG_DEBUG, "acls.chkpnt: %c acls.clear_arcbit: %c", NDMP_YORN(tlm_acls.acl_checkpointed), NDMP_YORN(tlm_acls.acl_clear_archive)); while (commands->tcs_reader == TLM_BACKUP_RUN && local_commands->tc_reader == TLM_BACKUP_RUN && cstack_pop(stk, (void **)&p_dir_info, 0) == 0) { if (NLP_ISCHKPNTED(nlp)) (void) strlcpy(np->nr_unchkp_nm, p_dir_info->fd_dir_name, TLM_MAX_PATH_NAME); else (void) tlm_remove_checkpoint(p_dir_info->fd_dir_name, np->nr_unchkp_nm); (void) backup_dir(np->nr_unchkp_nm, &tlm_acls, local_commands, job_stats, &bks); while (commands->tcs_reader == TLM_BACKUP_RUN && local_commands->tc_reader == TLM_BACKUP_RUN) { dname_size = TLM_MAX_PATH_NAME - 1; NDMP_LOG(LOG_DEBUG, "dir_name: %s", p_dir_info->fd_dir_name); (void) memset(&ret_fh, 0, sizeof (ret_fh)); erc = fs_readdir(&p_dir_info->fd_dir_fh, p_dir_info->fd_dir_name, &dpos, dname, &dname_size, &ret_fh, &ret_attr); if (erc == 0) { fileid = ret_fh.fh_fid; } else { NDMP_LOG(LOG_DEBUG, "Filesystem readdir in [%s]", p_dir_info->fd_dir_name); retval = -ENOENT; break; } /* an empty name size marks the end of the list */ if (dname_size == 0) break; dname[dname_size] = '\0'; NDMP_LOG(LOG_DEBUG, "dname: \"%s\"", dname); /* * If name refers to a directory, push its file * handle onto the stack (skip "." and ".."). */ if (rootfs_dot_or_dotdot(dname)) { fileid = 0; continue; } /* * Skip the: * non-dir entries which should not be backed up * Or * dir-type entries which have have nothing under * their hierarchy to be backed up. */ if (!dbm_getone(nlp->nlp_bkmap, (u_longlong_t)fileid)) { NDMP_LOG(LOG_DEBUG, "Skipping %s/%s", p_dir_info->fd_dir_name, dname); fileid = 0; continue; } if (tlm_is_excluded(np->nr_unchkp_nm, dname, np->nr_excls)) { fileid = 0; continue; } if (S_ISDIR(ret_attr.st_mode)) { /* * only directories get pushed onto this stack, * so we do not have to test for regular files. */ t_dir_info = tlm_new_dir_info(&ret_fh, p_dir_info->fd_dir_name, dname); if (t_dir_info == NULL) { NDMP_LOG(LOG_DEBUG, "While backing up [%s][%s]", p_dir_info->fd_dir_name, dname); } else if (cstack_push(stk, t_dir_info, 0) != 0) { NDMP_LOG(LOG_DEBUG, "No enough memory stack_push"); retval = -ENOMEM; break; } } else if (S_ISREG(ret_attr.st_mode) || S_ISLNK(ret_attr.st_mode)) { fsize = backup_file(np->nr_unchkp_nm, dname, &tlm_acls, commands, local_commands, job_stats, &bks); if (fsize >= 0) { job_stats->js_files_so_far++; job_stats->js_bytes_total += fsize; } else job_stats->js_errors++; fileid = 0; } } fileid = 0; free(p_dir_info); if (retval != 0) break; } free(dname); while (cstack_pop(stk, (void **)&p_dir_info, 0) == 0) { free(p_dir_info); } cstack_delete(stk); return (retval); }
/* * Traverse the file system in the post-order way. The description * and example is in the header file. * * The callback function should return 0, on success and non-zero on * failure. If the callback function returns non-zero return value, * the traversing stops. */ int traverse_post(struct fs_traverse *ftp) { char path[PATH_MAX + 1]; /* full path name of the current dir */ char nm[NAME_MAX + 1]; /* directory entry name */ char *lp; /* last position on the path */ int next_dir, rv; int pl, el; /* path and directory entry length */ cstack_t *sp; fs_fhandle_t pfh, efh; struct stat64 pst, est; traverse_state_t *tsp; struct fst_node pn, en; /* parent and entry nodes */ if (!ftp || !ftp->ft_path || !*ftp->ft_path || !ftp->ft_callbk) { NDMP_LOG(LOG_DEBUG, "Invalid argument"); errno = EINVAL; return (-1); } /* set the default log function if it's not already set */ if (!ftp->ft_logfp) { ftp->ft_logfp = (ft_log_t)syslog; NDMP_LOG(LOG_DEBUG, "Log to system log \"%s\"", ftp->ft_path); } /* set the logical path to physical path if it's not already set */ if (!ftp->ft_lpath) { NDMP_LOG(LOG_DEBUG, "report the same paths: \"%s\"", ftp->ft_path); ftp->ft_lpath = ftp->ft_path; } pl = strlen(ftp->ft_lpath); if (pl + 1 > PATH_MAX) { /* +1 for the '/' */ NDMP_LOG(LOG_DEBUG, "lpath too long \"%s\"", ftp->ft_path); errno = ENAMETOOLONG; return (-1); } (void) strcpy(path, ftp->ft_lpath); (void) memset(&pfh, 0, sizeof (pfh)); rv = fs_getstat(ftp->ft_lpath, &pfh, &pst); if (rv != 0) { NDMP_LOG(LOG_DEBUG, "Error %d on fs_getstat(%s)", rv, ftp->ft_path); return (rv); } if (!S_ISDIR(pst.st_mode)) { pn.tn_path = ftp->ft_lpath; pn.tn_fh = &pfh; pn.tn_st = &pst; en.tn_path = NULL; en.tn_fh = NULL; en.tn_st = NULL; rv = CALLBACK(&pn, &en); if (VERBOSE(ftp)) NDMP_LOG(LOG_DEBUG, "CALLBACK(%s): %d", pn.tn_path, rv); free(pfh.fh_fpath); return (rv); } sp = cstack_new(); if (!sp) { errno = ENOMEM; free(pfh.fh_fpath); return (-1); } tsp = new_tsp(path); if (!tsp) { cstack_delete(sp); errno = ENOMEM; free(pfh.fh_fpath); return (-1); } tsp->ts_ent = tsp->ts_end; tsp->ts_fh = pfh; tsp->ts_st = pst; pn.tn_path = path; pn.tn_fh = &tsp->ts_fh; pn.tn_st = &tsp->ts_st; rv = 0; next_dir = 1; do { if (next_dir) { traverse_stats.fss_newdirs++; *tsp->ts_end = '\0'; if (VERBOSE(ftp)) NDMP_LOG(LOG_DEBUG, "pl %d \"%s\"", pl, path); } next_dir = 0; do { el = NAME_MAX; rv = fs_readdir(&tsp->ts_fh, pn.tn_path, &tsp->ts_dpos, nm, &el, &efh, &est); if (rv != 0) { free(efh.fh_fpath); traverse_stats.fss_readdir_err++; NDMP_LOG(LOG_DEBUG, "Error %d on readdir(%s) pos %d", rv, path, tsp->ts_dpos); if (STOP_ONERR(ftp)) break; rv = SKIP_ENTRY; continue; } /* done with this directory */ if (el == 0) { if (VERBOSE(ftp)) NDMP_LOG(LOG_DEBUG, "Done(%s)", pn.tn_path); break; } nm[el] = '\0'; if (rootfs_dot_or_dotdot(nm)) { free(efh.fh_fpath); continue; } if (VERBOSE(ftp)) NDMP_LOG(LOG_DEBUG, "%u dname: \"%s\"", tsp->ts_dpos, nm); if (pl + 1 + el > PATH_MAX) { traverse_stats.fss_longpath_err++; NDMP_LOG(LOG_ERR, "Path %s/%s is too long.", path, nm); if (STOP_ONLONG(ftp)) rv = ENAMETOOLONG; free(efh.fh_fpath); continue; } /* * Push the current directory on to the stack and * dive into the entry found. */ if (S_ISDIR(est.st_mode)) { assert(tsp != NULL); if (cstack_push(sp, tsp, 0)) { rv = ENOMEM; free(efh.fh_fpath); break; } traverse_stats.fss_pushes++; /* * Concatenate the current entry with the * current path. This will be the path of * the new directory to be scanned. * * Note: * sprintf(tsp->ts_end, "/%s", de->d_name); * could be used here, but concatenating * strings like this might be faster. * The length of the new path has been * checked above. So strcpy() can be * safe and should not lead to a buffer * over-run. */ lp = tsp->ts_end; *tsp->ts_end = '/'; (void) strcpy(tsp->ts_end + 1, nm); tsp = new_tsp(path); if (!tsp) { free(efh.fh_fpath); rv = ENOMEM; } else { next_dir = 1; pl += el; tsp->ts_fh = efh; tsp->ts_st = est; tsp->ts_ent = lp; pn.tn_fh = &tsp->ts_fh; pn.tn_st = &tsp->ts_st; } break; } else { /* * The entry is not a directory so the * callback function must be called. */ traverse_stats.fss_nondir_calls++; en.tn_path = nm; en.tn_fh = &efh; en.tn_st = &est; rv = CALLBACK(&pn, &en); free(efh.fh_fpath); if (VERBOSE(ftp)) NDMP_LOG(LOG_DEBUG, "CALLBACK(%s/%s): %d", pn.tn_path, en.tn_path, rv); if (rv != 0) break; } } while (rv == 0); /* * A new directory must be processed, go to the start of * the loop, open it and process it. */ if (next_dir) continue; if (rv == SKIP_ENTRY) rv = 0; /* We should skip the current directory */ if (rv == 0) { /* * Remove the ent from the end of path and send it * as an entry of the path. */ lp = tsp->ts_ent; *lp = '\0'; efh = tsp->ts_fh; est = tsp->ts_st; free(tsp); if (cstack_pop(sp, (void **)&tsp, (int *)NULL)) break; assert(tsp != NULL); pl = tsp->ts_end - path; if (VERBOSE(ftp)) NDMP_LOG(LOG_DEBUG, "poped pl %d 0x%p \"%s\"", pl, tsp, path); traverse_stats.fss_pops++; traverse_stats.fss_dir_calls++; pn.tn_fh = &tsp->ts_fh; pn.tn_st = &tsp->ts_st; en.tn_path = lp + 1; en.tn_fh = &efh; en.tn_st = &est; rv = CALLBACK(&pn, &en); free(efh.fh_fpath); if (VERBOSE(ftp)) NDMP_LOG(LOG_DEBUG, "CALLBACK(%s/%s): %d", pn.tn_path, en.tn_path, rv); /* * Does not need to free tsp here. It will be released * later. */ } if (rv != 0 && tsp) { free(tsp->ts_fh.fh_fpath); free(tsp); } } while (rv == 0); /* * For the 'ftp->ft_path' directory itself. */ if (rv == 0) { traverse_stats.fss_dir_calls++; pn.tn_fh = &efh; pn.tn_st = &est; en.tn_path = NULL; en.tn_fh = NULL; en.tn_st = NULL; rv = CALLBACK(&pn, &en); if (VERBOSE(ftp)) NDMP_LOG(LOG_DEBUG, "CALLBACK(%s): %d", pn.tn_path, rv); } /* * Pop and free all the remaining entries on the stack. */ while (!cstack_pop(sp, (void **)&tsp, (int *)NULL)) { traverse_stats.fss_stack_residue++; free(tsp->ts_fh.fh_fpath); free(tsp); } cstack_delete(sp); return (rv); }