From b1830081936939f72294cadce7b62f670460cedd Mon Sep 17 00:00:00 2001 From: Kern Sibbald Date: Tue, 13 Aug 2002 20:55:21 +0000 Subject: [PATCH] Documentation + more work on restore -- kes12Aug02 git-svn-id: https://bacula.svn.sourceforge.net/svnroot/bacula/trunk@92 91ce42f0-d328-0410-95d8-f526ca767f89 --- bacula/kernstodo | 2 + bacula/src/cats/sql_get.c | 49 ++++++- bacula/src/dird/sql_cmds.c | 24 ++++ bacula/src/dird/ua_restore.c | 120 +++++++++++++--- bacula/src/findlib/Makefile.in | 8 +- bacula/src/tools/testfind.c | 249 +++++++++++++++++++++++++++++++++ 6 files changed, 423 insertions(+), 29 deletions(-) create mode 100644 bacula/src/tools/testfind.c diff --git a/bacula/kernstodo b/bacula/kernstodo index 613d013a13..67adbeedf0 100644 --- a/bacula/kernstodo +++ b/bacula/kernstodo @@ -28,6 +28,8 @@ From Chuck: --sd.conf password does not match dir.conf storage password ======= +- Fix catalog filename truncation in sql_get and elsewhere. Use + only a single filename split routine. - Add command to reset VolFiles to a larger value (don't allow a smaller number or print big warning). - Make SD disallow writing on Volume with fewer files than in diff --git a/bacula/src/cats/sql_get.c b/bacula/src/cats/sql_get.c index edd0840ce4..ec6ea58813 100644 --- a/bacula/src/cats/sql_get.c +++ b/bacula/src/cats/sql_get.c @@ -133,11 +133,11 @@ int db_get_file_attributes_record(B_DB *mdb, char *fname, FILE_DBR *fdbr) db_escape_string(buf, file, fnl); fdbr->FilenameId = db_get_filename_record(mdb, buf); - Dmsg1(50, "db_get_filename_record FilenameId=%d\n", fdbr->FilenameId); + Dmsg2(100, "db_get_filename_record FilenameId=%d file=%s\n", fdbr->FilenameId, buf); db_escape_string(buf, spath, pnl); fdbr->PathId = db_get_path_record(mdb, buf); - Dmsg2(50, "db_get_path_record PathId=%d path=%s\n", fdbr->PathId, buf); + Dmsg2(100, "db_get_path_record PathId=%d path=%s\n", fdbr->PathId, buf); id = db_get_file_record(mdb, fdbr); @@ -508,6 +508,51 @@ PoolType, LabelFormat FROM Pool WHERE Pool.Name='%s'", pdbr->Name); return stat; } +/* Get FileSet Record + * If the FileSetId is non-zero, we get its record, + * otherwise, we search on the name + * + * Returns: 0 on failure + * id on success + */ +int db_get_fileset_record(B_DB *mdb, FILESET_DBR *fsr) +{ + SQL_ROW row; + int stat = 0; + + db_lock(mdb); + if (fsr->FileSetId != 0) { /* find by id */ + Mmsg(&mdb->cmd, + "SELECT FileSetId, FileSet, MD5 FROM FileSet " + "WHERE FileSetId=%u", fsr->FileSetId); + } else { /* find by name */ + Mmsg(&mdb->cmd, + "SELECT FileSetId, FileSet, MD5 FROM FileSet " + "WHERE FileSet='%s'", fsr->FileSet); + } + + if (QUERY_DB(mdb, mdb->cmd)) { + mdb->num_rows = sql_num_rows(mdb); + if (mdb->num_rows > 1) { + char ed1[30]; + Mmsg1(&mdb->errmsg, _("More than one Pool!: %s\n"), + edit_uint64(mdb->num_rows, ed1)); + } else if (mdb->num_rows == 1) { + if ((row = sql_fetch_row(mdb)) == NULL) { + Mmsg1(&mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb)); + } else { + fsr->FileSetId = atoi(row[0]); + strcpy(fsr->FileSet, row[1]); + strcpy(fsr->MD5, row[2]); + stat = fsr->FileSetId; + } + } + sql_free_result(mdb); + } + db_unlock(mdb); + return stat; +} + /* * Get the number of Media records diff --git a/bacula/src/dird/sql_cmds.c b/bacula/src/dird/sql_cmds.c index 05bcef6eb1..a554a716ef 100644 --- a/bacula/src/dird/sql_cmds.c +++ b/bacula/src/dird/sql_cmds.c @@ -115,12 +115,16 @@ char *select_restore_del = /* ======= ua_restore.c */ +/* List last 20 Jobs */ char *uar_list_jobs = "SELECT JobId,Client.Name as Client,StartTime,Type as " "JobType,JobFiles,JobBytes " "FROM Client,Job WHERE Client.ClientId=Job.ClientId AND JobStatus='T' " "LIMIT 20"; +#ifdef HAVE_MYSQL +/* MYSQL IS NOT STANDARD SQL !!!!! */ +/* List Jobs where a particular file is saved */ char *uar_file = "SELECT Job.JobId as JobId, Client.Name as Client, " "CONCAT(Path.Path,Filename.Name) as Name, " @@ -129,6 +133,18 @@ char *uar_file = "AND JobStatus='T' AND Job.JobId=File.JobId " "AND Path.PathId=File.PathId AND Filename.FilenameId=File.FilenameId " "AND Filename.Name='%s' LIMIT 20"; +#else +/* List Jobs where a particular file is saved */ +char *uar_file = + "SELECT Job.JobId as JobId, Client.Name as Client, " + "Path.Path||Filename.Name as Name, " + "StartTime,Type as JobType,JobFiles,JobBytes " + "FROM Client,Job,File,Filename,Path WHERE Client.ClientId=Job.ClientId " + "AND JobStatus='T' AND Job.JobId=File.JobId " + "AND Path.PathId=File.PathId AND Filename.FilenameId=File.FilenameId " + "AND Filename.Name='%s' LIMIT 20"; +#endif + char *uar_sel_files = "SELECT Path.Path,Filename.Name,FileIndex,JobId " @@ -163,6 +179,7 @@ char *uar_last_full = "AND Level='F' AND JobStatus='T' " "AND JobMedia.JobId=Job.JobId " "AND JobMedia.MediaId=Media.MediaId " + "AND Job.FileSetId=%u " "ORDER BY Job.JobTDate DESC LIMIT 1"; char *uar_full = @@ -183,6 +200,7 @@ char *uar_inc = "AND JobMedia.JobId=Job.JobId " "AND JobMedia.MediaId=Media.MediaId " "AND Job.Level='I' AND JobStatus='T' " + "AND Job.FileSetId=%u " "GROUP BY Job.JobId"; char *uar_list_temp = @@ -192,3 +210,9 @@ char *uar_list_temp = char *uar_sel_jobid_temp = "SELECT JobId FROM temp"; char *uar_sel_all_temp1 = "SELECT * FROM temp1"; + +char *uar_sel_fileset = + "SELECT FileSet.FileSetId,FileSet.FileSet FROM Job," + "Client,FileSet WHERE Job.FileSetId=FileSet.FileSetId " + "AND Job.ClientId=Client.ClientId AND Client.Name='%s' " + "GROUP BY FileSetId"; diff --git a/bacula/src/dird/ua_restore.c b/bacula/src/dird/ua_restore.c index 7071a0b748..797fb2ff34 100644 --- a/bacula/src/dird/ua_restore.c +++ b/bacula/src/dird/ua_restore.c @@ -39,19 +39,11 @@ extern int runcmd(UAContext *ua, char *cmd); /* Imported variables */ -extern char *uar_list_jobs; -extern char *uar_file; -extern char *uar_sel_files; -extern char *uar_del_temp; -extern char *uar_del_temp1; -extern char *uar_create_temp; -extern char *uar_create_temp1; -extern char *uar_last_full; -extern char *uar_full; -extern char *uar_inc; -extern char *uar_list_temp; -extern char *uar_sel_jobid_temp; -extern char *uar_sel_all_temp1; +extern char *uar_list_jobs, *uar_file, *uar_sel_files; +extern char *uar_del_temp, *uar_del_temp1, *uar_create_temp; +extern char *uar_create_temp1, *uar_last_full, *uar_full; +extern char *uar_inc, *uar_list_temp, *uar_sel_jobid_temp; +extern char *uar_sel_all_temp1, *uar_sel_fileset; /* Context for insert_tree_handler() */ typedef struct s_tree_ctx { @@ -102,6 +94,7 @@ static int jobid_handler(void *ctx, int num_fields, char **row); static int next_jobid_from_list(char **p, uint32_t *JobId); static int user_select_jobids(UAContext *ua, JobIds *ji); static void user_select_files(TREE_CTX *tree); +static int fileset_handler(void *ctx, int num_fields, char **row); /* @@ -222,7 +215,7 @@ int restorecmd(UAContext *ua, char *cmd) job->hdr.name, working_directory); } - Dmsg1(000, "Submitting: %s\n", ua->cmd); + Dmsg1(400, "Submitting: %s\n", ua->cmd); parse_command_args(ua); runcmd(ua, ua->cmd); @@ -238,7 +231,9 @@ int restorecmd(UAContext *ua, char *cmd) */ static int user_select_jobids(UAContext *ua, JobIds *ji) { + char fileset_name[MAX_NAME_LENGTH]; char *p; + FILESET_DBR fsr; JobId_t JobId; JOB_DBR jr; POOLMEM *query; @@ -294,6 +289,7 @@ static int user_select_jobids(UAContext *ua, JobIds *ji) done = 0; break; case 4: /* Select the most recent backups */ + query = get_pool_memory(PM_MESSAGE); db_sql_query(ua->db, uar_del_temp, NULL, NULL); db_sql_query(ua->db, uar_del_temp1, NULL, NULL); if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) { @@ -302,11 +298,34 @@ static int user_select_jobids(UAContext *ua, JobIds *ji) if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) { bsendmsg(ua, "%s\n", db_strerror(ua->db)); } + /* + * Select Client + */ if (!(ji->client = get_client_resource(ua))) { return 0; } - query = get_pool_memory(PM_MESSAGE); - Mmsg(&query, uar_last_full, ji->client->hdr.name); + + /* + * Select FileSet + */ + Mmsg(&query, uar_sel_fileset, ji->client->hdr.name); + start_prompt(ua, _("The defined FileSet resources are:\n")); + if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) { + bsendmsg(ua, "%s\n", db_strerror(ua->db)); + } + if (do_prompt(ua, _("Select FileSet resource"), fileset_name) < 0) { + free_pool_memory(query); + return 0; + } + fsr.FileSetId = 0; + strcpy(fsr.FileSet, fileset_name); + if (!db_get_fileset_record(ua->db, &fsr)) { + bsendmsg(ua, "%s\n", db_strerror(ua->db)); + free_pool_memory(query); + return 0; + } + + Mmsg(&query, uar_last_full, ji->client->hdr.name, fsr.FileSetId); /* Find JobId of full Backup of system */ if (!db_sql_query(ua->db, query, NULL, NULL)) { bsendmsg(ua, "%s\n", db_strerror(ua->db)); @@ -322,7 +341,7 @@ static int user_select_jobids(UAContext *ua, JobIds *ji) bsendmsg(ua, "%s\n", db_strerror(ua->db)); } /* Now find all Incremental Jobs */ - Mmsg(&query, uar_inc, (uint32_t)ji->JobTDate, ji->ClientId); + Mmsg(&query, uar_inc, (uint32_t)ji->JobTDate, ji->ClientId, fsr.FileSetId); if (!db_sql_query(ua->db, query, NULL, NULL)) { bsendmsg(ua, "%s\n", db_strerror(ua->db)); } @@ -422,8 +441,14 @@ static int last_full_handler(void *ctx, int num_fields, char **row) return 0; } - - +/* + * Callback handler build fileset prompt list + */ +static int fileset_handler(void *ctx, int num_fields, char **row) +{ + add_prompt((UAContext *)ctx, row[1]); + return 0; +} /* Forward referenced commands */ @@ -431,6 +456,7 @@ static int markcmd(UAContext *ua, TREE_CTX *tree); static int countcmd(UAContext *ua, TREE_CTX *tree); static int findcmd(UAContext *ua, TREE_CTX *tree); static int lscmd(UAContext *ua, TREE_CTX *tree); +static int dircmd(UAContext *ua, TREE_CTX *tree); static int helpcmd(UAContext *ua, TREE_CTX *tree); static int cdcmd(UAContext *ua, TREE_CTX *tree); static int pwdcmd(UAContext *ua, TREE_CTX *tree); @@ -445,7 +471,7 @@ static struct cmdstruct commands[] = { { N_("cd"), cdcmd, _("change current directory")}, { N_("pwd"), pwdcmd, _("print current working directory")}, { N_("ls"), lscmd, _("list current directory")}, - { N_("dir"), lscmd, _("list current directory")}, + { N_("dir"), dircmd, _("list current directory")}, { N_("count"), countcmd, _("count marked files")}, { N_("find"), findcmd, _("find files")}, { N_("done"), quitcmd, _("leave file selection mode")}, @@ -877,6 +903,60 @@ static int lscmd(UAContext *ua, TREE_CTX *tree) return 1; } +extern char *getuser(uid_t uid); +extern char *getgroup(gid_t gid); + +static void ls_output(char *buf, char *fname, struct stat *statp) +{ + char *p, *f; + int n; + + p = encode_mode(statp->st_mode, buf); + n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink); + p += n; + n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid)); + p += n; + n = sprintf(p, "%8ld ", statp->st_size); + p += n; + p = encode_time(statp->st_ctime, p); + *p++ = ' '; + *p++ = ' '; + for (f=fname; *f; ) + *p++ = *f++; + *p = 0; +} + + +/* + * Like ls command, but give more detail on each file + */ +static int dircmd(UAContext *ua, TREE_CTX *tree) +{ + TREE_NODE *node; + FILE_DBR fdbr; + struct stat statp; + char buf[1000]; + char cwd[1100]; + + if (!tree->node->child) { + return 1; + } + for (node = tree->node->child; node; node=node->sibling) { + if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) { + tree_getpath(node, cwd, sizeof(cwd)); + fdbr.FileId = 0; + fdbr.JobId = node->JobId; + if (db_get_file_attributes_record(ua->db, cwd, &fdbr)) { + decode_stat(fdbr.LStat, &statp); /* decode stat pkt */ + ls_output(buf, cwd, &statp); + bsendmsg(ua, "%s\n", buf); + } + } + } + return 1; +} + + static int helpcmd(UAContext *ua, TREE_CTX *tree) { unsigned int i; diff --git a/bacula/src/findlib/Makefile.in b/bacula/src/findlib/Makefile.in index be830fe1c1..bc3b42717b 100644 --- a/bacula/src/findlib/Makefile.in +++ b/bacula/src/findlib/Makefile.in @@ -23,9 +23,6 @@ dummy: LIBSRCS = find.c match.c find_one.c LIBOBJS = find.o match.o find_one.o -FINDSRCS = testfind.c -FINDOBJS = testfind.o - .SUFFIXES: .c .o .PHONY: .DONTCARE: @@ -43,9 +40,6 @@ libfind.a: $(LIBOBJS) $(AR) cru $@ $(LIBOBJS) $(RANLIB) $@ -testfind: libfind.a $(FINDOBJS) - $(CC) -g $(LDFLAGS) -L. -L../lib -o $@ $(FINDOBJS) $(LIBS) $(DLIB) -lfind -lbac -lm - Makefile: $(srcdir)/Makefile.in $(topdir)/config.status cd $(topdir) \ && CONFIG_FILES=$(thisdir)/$@ CONFIG_HEADERS= $(SHELL) ./config.status @@ -56,7 +50,7 @@ uninstall: clean: - $(RMF) find testfind core a.out *.a *.o *.bak *~ *.intpro *.extpro 1 2 3 + $(RMF) find core a.out *.a *.o *.bak *~ *.intpro *.extpro 1 2 3 realclean: clean $(RMF) tags diff --git a/bacula/src/tools/testfind.c b/bacula/src/tools/testfind.c new file mode 100644 index 0000000000..f98cf4297d --- /dev/null +++ b/bacula/src/tools/testfind.c @@ -0,0 +1,249 @@ +/* + * Test program for find files + */ + +#include "bacula.h" +#include "findlib/find.h" +#include "jcr.h" + + +/* Global variables */ +static int num_files = 0; +static int max_file_len = 0; +static int max_path_len = 0; +static int trunc_fname = 0; +static int trunc_path = 0; + + +static int print_file(FF_PKT *ff, void *pkt); +static void count_files(FF_PKT *ff); + +static void usage() +{ + fprintf(stderr, _( +"\n" +"Usage: testfind [-d debug_level] [-] [pattern1 ...]\n" +" -dnn set debug level to nn\n" +" - read pattern(s) from stdin\n" +" -? print this message.\n" +"\n" +"Patterns are file inclusion -- normally directories.\n" +"Debug level >= 1 prints each file found.\n" +"Debug level >= 10 prints path/file for catalog.\n" +"Errors always printed.\n" +"Files/paths truncated is number with len > 255.\n" +"Truncation is only in catalog.\n" +"\n")); + + exit(1); +} + + +int +main (int argc, char *const *argv) +{ + FF_PKT *ff; + char name[1000]; + int i, ch; + + while ((ch = getopt(argc, argv, "d:?")) != -1) { + switch (ch) { + case 'd': /* set debug level */ + debug_level = atoi(optarg); + if (debug_level <= 0) { + debug_level = 1; + } + break; + + case '?': + default: + usage(); + + } + } + argc -= optind; + argv += optind; + + ff = init_find_files(); + if (argc == 0) { + add_fname_to_include_list(ff, 0, "/"); /* default to / */ + } else { + for (i=0; i < argc; i++) { + if (strcmp(argv[i], "-") == 0) { + while (fgets(name, sizeof(name)-1, stdin)) { + strip_trailing_junk(name); + add_fname_to_include_list(ff, 0, name); + } + continue; + } + add_fname_to_include_list(ff, 0, argv[i]); + } + } + + find_files(ff, print_file, NULL); + term_find_files(ff); + + printf(_("\ +Total files : %d\n\ +Max file length: %d\n\ +Max path length: %d\n\ +Files truncated: %d\n\ +Paths truncated: %d\n"), + num_files, max_file_len, max_path_len, + trunc_fname, trunc_path); + + sm_dump(False); + exit(0); +} + +static int print_file(FF_PKT *ff, void *pkt) +{ + switch (ff->type) { + case FT_LNKSAVED: + if (debug_level == 1) { + printf("%s\n", ff->fname); + } else if (debug_level > 1) { + printf("Lnka: %s -> %s\n", ff->fname, ff->link); + } + break; + case FT_REGE: + if (debug_level == 1) { + printf("%s\n", ff->fname); + } else if (debug_level > 1) { + printf("Empty: %s\n", ff->fname); + } + count_files(ff); + break; + case FT_REG: + if (debug_level == 1) { + printf("%s\n", ff->fname); + } else if (debug_level > 1) { + printf("Reg: %s\n", ff->fname); + } + count_files(ff); + break; + case FT_LNK: + if (debug_level == 1) { + printf("%s\n", ff->fname); + } else if (debug_level > 1) { + printf("Lnk: %s -> %s\n", ff->fname, ff->link); + } + count_files(ff); + break; + case FT_DIR: + if (debug_level == 1) { + printf("%s\n", ff->fname); + } else if (debug_level > 1) { + printf("Dir: %s\n", ff->fname); + } + count_files(ff); + break; + case FT_SPEC: + if (debug_level == 1) { + printf("%s\n", ff->fname); + } else if (debug_level > 1) { + printf("Spec: %s\n", ff->fname); + } + count_files(ff); + break; + case FT_NOACCESS: + printf(_("Err: Could not access %s: %s\n"), ff->fname, strerror(errno)); + break; + case FT_NOFOLLOW: + printf(_("Err: Could not follow ff->link %s: %s\n"), ff->fname, strerror(errno)); + break; + case FT_NOSTAT: + printf(_("Err: Could not stat %s: %s\n"), ff->fname, strerror(errno)); + break; + case FT_NOCHG: + printf(_("Skip: File not saved. No change. %s\n"), ff->fname); + break; + case FT_ISARCH: + printf(_("Err: Attempt to backup archive. Not saved. %s\n"), ff->fname); + break; + case FT_NORECURSE: + printf(_("Recursion turned off. Directory not entered. %s\n"), ff->fname); + break; + case FT_NOFSCHG: + printf(_("Skip: File system change prohibited. Directory not entered. %s\n"), ff->fname); + break; + case FT_NOOPEN: + printf(_("Err: Could not open directory %s: %s\n"), ff->fname, strerror(errno)); + break; + default: + printf(_("Err: Unknown file ff->type %d: %s\n"), ff->type, ff->fname); + break; + } + return 1; +} + +static void count_files(FF_PKT *ar) +{ + int fnl, pnl; + char *l, *p; + char file[MAXSTRING]; + char spath[MAXSTRING]; + + num_files++; + + /* Find path without the filename. + * I.e. everything after the last / is a "filename". + * OK, maybe it is a directory name, but we treat it like + * a filename. If we don't find a / then the whole name + * must be a path name (e.g. c:). + */ + for (p=l=ar->fname; *p; p++) { + if (*p == '/') { + l = p; /* set pos of last slash */ + } + } + if (*l == '/') { /* did we find a slash? */ + l++; /* yes, point to filename */ + } else { /* no, whole thing must be path name */ + l = p; + } + + /* If filename doesn't exist (i.e. root directory), we + * simply create a blank name consisting of a single + * space. This makes handling zero length filenames + * easier. + */ + fnl = p - l; + if (fnl > max_file_len) { + max_file_len = fnl; + } + if (fnl > 255) { + printf(_("===== Filename truncated to 255 chars: %s\n"), l); + fnl = 255; + trunc_fname++; + } + if (fnl > 0) { + strncpy(file, l, fnl); /* copy filename */ + file[fnl] = 0; + } else { + file[0] = ' '; /* blank filename */ + file[1] = 0; + } + + pnl = l - ar->fname; + if (pnl > max_path_len) { + max_path_len = pnl; + } + if (pnl > 255) { + printf(_("========== Path name truncated to 255 chars: %s\n"), ar->fname); + pnl = 255; + trunc_path++; + } + strncpy(spath, ar->fname, pnl); + spath[pnl] = 0; + if (pnl == 0) { + spath[0] = ' '; + spath[1] = 0; + printf(_("========== Path length is zero. File=%s\n"), ar->fname); + } + if (debug_level >= 10) { + printf("Path: %s\n", spath); + printf("File: %s\n", file); + } + +} -- 2.39.5