3 * Program to check a Bacula database for consistency and to
6 * Kern E. Sibbald, August 2002
12 Copyright (C) 2000-2004 Kern Sibbald and John Walker
14 This program is free software; you can redistribute it and/or
15 modify it under the terms of the GNU General Public License as
16 published by the Free Software Foundation; either version 2 of
17 the License, or (at your option) any later version.
19 This program is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 General Public License for more details.
24 You should have received a copy of the GNU General Public
25 License along with this program; if not, write to the Free
26 Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
32 #include "cats/cats.h"
33 #include "dird/dird_conf.h"
35 typedef struct s_id_ctx {
36 uint32_t *Id; /* ids to be modified */
37 int num_ids; /* ids stored */
38 int max_ids; /* size of array */
39 int num_del; /* number deleted */
40 int tot_ids; /* total to process */
43 typedef struct s_name_ctx {
44 char **name; /* list of names */
45 int num_ids; /* ids stored */
46 int max_ids; /* size of array */
47 int num_del; /* number deleted */
48 int tot_ids; /* total to process */
53 /* Global variables */
54 static bool fix = false;
55 static bool batch = false;
57 static ID_LIST id_list;
58 static NAME_LIST name_list;
59 static char buf[2000];
61 #define MAX_ID_LIST_LEN 10000000
63 /* Forward referenced functions */
64 static int make_id_list(const char *query, ID_LIST *id_list);
65 static int delete_id_list(const char *query, ID_LIST *id_list);
66 static int make_name_list(const char *query, NAME_LIST *name_list);
67 static void print_name_list(NAME_LIST *name_list);
68 static void free_name_list(NAME_LIST *name_list);
69 static char *get_cmd(const char *prompt);
70 static void eliminate_duplicate_filenames();
71 static void eliminate_duplicate_paths();
72 static void eliminate_orphaned_jobmedia_records();
73 static void eliminate_orphaned_file_records();
74 static void eliminate_orphaned_path_records();
75 static void eliminate_orphaned_filename_records();
76 static void eliminate_orphaned_fileset_records();
77 static void eliminate_orphaned_client_records();
78 static void repair_bad_paths();
79 static void repair_bad_filenames();
80 static void do_interactive_mode();
81 static int yes_no(const char *prompt);
87 "Usage: dbcheck [-c config] [-C catalog name] [-d debug_level] <working-directory> <bacula-database> <user> <password> [<dbhost>]\n"
89 " -C catalog name in the director conf file\n"
90 " -c director conf filename\n"
91 " -dnn set debug level to nn\n"
92 " -f fix inconsistencies\n"
94 " -? print this message\n\n");
98 int main (int argc, char *argv[])
101 const char *user, *password, *db_name, *dbhost;
102 char *configfile = NULL;
103 char *catalogname = NULL;
105 my_name_is(argc, argv, "dbcheck");
106 init_msg(NULL, NULL); /* setup message handler */
108 memset(&id_list, 0, sizeof(id_list));
109 memset(&name_list, 0, sizeof(name_list));
112 while ((ch = getopt(argc, argv, "bc:C:d:fv?")) != -1) {
114 case 'b': /* batch */
118 case 'C': /* CatalogName */
119 catalogname = optarg;
122 case 'c': /* configfile */
126 case 'd': /* debug level */
127 debug_level = atoi(optarg);
128 if (debug_level <= 0)
132 case 'f': /* fix inconsistencies */
152 Pmsg0(0, _("Warning skipping the additional parameters for working directory/dbname/user/password/host.\n"));
154 parse_config(configfile);
156 foreach_res(catalog, R_CATALOG) {
157 if (catalogname && !strcmp(catalog->hdr.name, catalogname)) {
160 } else if (!catalogname) { // stop on first if no catalogname is given
168 Pmsg2(0, "Error can not find the Catalog name[%s] in the given config file [%s]\n", catalogname, configfile);
170 Pmsg1(0, "Error there is no Catalog section in the given config file [%s]\n", configfile);
176 director = (DIRRES *)GetNextRes(R_DIRECTOR, NULL);
179 Pmsg0(0, "Error no Director resource defined.\n");
182 set_working_directory(director->working_directory);
183 db_name = catalog->db_name;
184 user = catalog->db_user;
185 password = catalog->db_password;
186 dbhost = catalog->db_address;
187 if (dbhost && dbhost[0] == 0) {
193 Pmsg0(0, _("Wrong number of arguments.\n"));
198 Pmsg0(0, _("Working directory not supplied.\n"));
202 /* This is needed by SQLite to find the db */
203 working_directory = argv[0];
212 } else if (argc == 3) {
215 } else if (argc == 4) {
219 } else if (argc == 5) {
228 db = db_init_database(NULL, db_name, user, password, dbhost, 0, NULL);
229 if (!db_open_database(NULL, db)) {
230 Emsg1(M_FATAL, 0, "%s", db_strerror(db));
236 repair_bad_filenames();
237 eliminate_duplicate_filenames();
238 eliminate_duplicate_paths();
239 eliminate_orphaned_jobmedia_records();
240 eliminate_orphaned_file_records();
241 eliminate_orphaned_path_records();
242 eliminate_orphaned_filename_records();
243 eliminate_orphaned_fileset_records();
244 eliminate_orphaned_client_records();
246 do_interactive_mode();
249 db_close_database(NULL, db);
255 static void do_interactive_mode()
260 printf("Hello, this is the database check/correct program.\n\
261 Modify database is %s. Verbose is %s.\n\
262 Please select the fuction you want to perform.\n",
263 fix?"On":"Off", verbose?"On":"Off");
268 1) Toggle modify database flag\n\
269 2) Toggle verbose flag\n\
270 3) Repair bad Filename records\n\
271 4) Repair bad Path records\n\
272 5) Eliminate duplicate Filename records\n\
273 6) Eliminate duplicate Path records\n\
274 7) Eliminate orphaned Jobmedia records\n\
275 8) Eliminate orphaned File records\n\
276 9) Eliminate orphaned Path records\n\
277 10) Eliminate orphaned Filename records\n\
278 11) Eliminate orphaned FileSet records\n\
279 12) Eliminate orphaned Client records\n\
284 1) Toggle modify database flag\n\
285 2) Toggle verbose flag\n\
286 3) Check for bad Filename records\n\
287 4) Check for bad Path records\n\
288 5) Check for duplicate Filename records\n\
289 6) Check for duplicate Path records\n\
290 7) Check for orphaned Jobmedia records\n\
291 8) Check for orphaned File records\n\
292 9) Check for orphaned Path records\n\
293 10) Check for orphaned Filename records\n\
294 11) Check for orphaned FileSet records\n\
295 12) Check for orphaned FileSet records\n\
300 cmd = get_cmd(_("Select function number: "));
302 int item = atoi(cmd);
306 printf(_("Database will %sbe modified.\n"), fix?"":_("NOT "));
309 verbose = verbose?0:1;
310 printf(_("Verbose is %s\n"), verbose?_("On"):_("Off"));
313 repair_bad_filenames();
319 eliminate_duplicate_filenames();
322 eliminate_duplicate_paths();
325 eliminate_orphaned_jobmedia_records();
328 eliminate_orphaned_file_records();
331 eliminate_orphaned_path_records();
334 eliminate_orphaned_filename_records();
337 eliminate_orphaned_fileset_records();
340 eliminate_orphaned_client_records();
343 repair_bad_filenames();
345 eliminate_duplicate_filenames();
346 eliminate_duplicate_paths();
347 eliminate_orphaned_jobmedia_records();
348 eliminate_orphaned_file_records();
349 eliminate_orphaned_path_records();
350 eliminate_orphaned_filename_records();
351 eliminate_orphaned_fileset_records();
352 eliminate_orphaned_client_records();
362 static int print_name_handler(void *ctx, int num_fields, char **row)
365 printf("%s\n", row[0]);
370 static int get_name_handler(void *ctx, int num_fields, char **row)
372 POOLMEM *buf = (POOLMEM *)ctx;
374 pm_strcpy(&buf, row[0]);
380 static int print_jobmedia_handler(void *ctx, int num_fields, char **row)
382 printf(_("Orphaned JobMediaId=%s JobId=%s Volume=\"%s\"\n"),
383 NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
387 static int print_file_handler(void *ctx, int num_fields, char **row)
389 printf(_("Orphaned FileId=%s JobId=%s Volume=\"%s\"\n"),
390 NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
394 static int print_fileset_handler(void *ctx, int num_fields, char **row)
396 printf(_("Orphaned FileSetId=%s FileSet=\"%s\" MD5=%s\n"),
397 NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
401 static int print_client_handler(void *ctx, int num_fields, char **row)
403 printf(_("Orphaned ClientId=%s Name=\"%s\"\n"),
404 NPRT(row[0]), NPRT(row[1]));
411 * Called here with each id to be added to the list
413 static int id_list_handler(void *ctx, int num_fields, char **row)
415 ID_LIST *lst = (ID_LIST *)ctx;
417 if (lst->num_ids == MAX_ID_LIST_LEN) {
420 if (lst->num_ids == lst->max_ids) {
421 if (lst->max_ids == 0) {
423 lst->Id = (uint32_t *)bmalloc(sizeof(uint32_t) * lst->max_ids);
425 lst->max_ids = (lst->max_ids * 3) / 2;
426 lst->Id = (uint32_t *)brealloc(lst->Id, sizeof(uint32_t) * lst->max_ids);
429 lst->Id[lst->num_ids++] = (uint32_t)strtod(row[0], NULL);
434 * Construct record id list
436 static int make_id_list(const char *query, ID_LIST *id_list)
438 id_list->num_ids = 0;
439 id_list->num_del = 0;
440 id_list->tot_ids = 0;
442 if (!db_sql_query(db, query, id_list_handler, (void *)id_list)) {
443 printf("%s", db_strerror(db));
450 * Delete all entries in the list
452 static int delete_id_list(const char *query, ID_LIST *id_list)
454 for (int i=0; i < id_list->num_ids; i++) {
455 sprintf(buf, query, id_list->Id[i]);
457 printf("Deleting: %s\n", buf);
459 db_sql_query(db, buf, NULL, NULL);
465 * Called here with each name to be added to the list
467 static int name_list_handler(void *ctx, int num_fields, char **row)
469 NAME_LIST *name = (NAME_LIST *)ctx;
471 if (name->num_ids == MAX_ID_LIST_LEN) {
474 if (name->num_ids == name->max_ids) {
475 if (name->max_ids == 0) {
476 name->max_ids = 1000;
477 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
479 name->max_ids = (name->max_ids * 3) / 2;
480 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
483 name->name[name->num_ids++] = bstrdup(row[0]);
489 * Construct name list
491 static int make_name_list(const char *query, NAME_LIST *name_list)
493 name_list->num_ids = 0;
494 name_list->num_del = 0;
495 name_list->tot_ids = 0;
497 if (!db_sql_query(db, query, name_list_handler, (void *)name_list)) {
498 printf("%s", db_strerror(db));
505 * Print names in the list
507 static void print_name_list(NAME_LIST *name_list)
509 for (int i=0; i < name_list->num_ids; i++) {
510 printf("%s\n", name_list->name[i]);
516 * Free names in the list
518 static void free_name_list(NAME_LIST *name_list)
520 for (int i=0; i < name_list->num_ids; i++) {
521 free(name_list->name[i]);
523 name_list->num_ids = 0;
526 static void eliminate_duplicate_filenames()
531 printf("Checking for duplicate Filename entries.\n");
533 /* Make list of duplicated names */
534 query = "SELECT Name, count(Name) as Count FROM Filename GROUP BY Name "
535 "HAVING count(Name) > 1";
537 if (!make_name_list(query, &name_list)) {
540 printf("Found %d duplicate Filename records.\n", name_list.num_ids);
541 if (name_list.num_ids && verbose && yes_no("Print the list? (yes/no): ")) {
542 print_name_list(&name_list);
545 /* Loop through list of duplicate names */
546 for (int i=0; i<name_list.num_ids; i++) {
547 /* Get all the Ids of each name */
548 db_escape_string(esc_name, name_list.name[i], strlen(name_list.name[i]));
549 sprintf(buf, "SELECT FilenameId FROM Filename WHERE Name='%s'", esc_name);
553 if (!make_id_list(buf, &id_list)) {
557 printf("Found %d for: %s\n", id_list.num_ids, name_list.name[i]);
559 /* Force all records to use the first id then delete the other ids */
560 for (int j=1; j<id_list.num_ids; j++) {
561 sprintf(buf, "UPDATE File SET FilenameId=%u WHERE FilenameId=%u",
562 id_list.Id[0], id_list.Id[j]);
566 db_sql_query(db, buf, NULL, NULL);
567 sprintf(buf, "DELETE FROM Filename WHERE FilenameId=%u",
572 db_sql_query(db, buf, NULL, NULL);
576 free_name_list(&name_list);
579 static void eliminate_duplicate_paths()
584 printf(_("Checking for duplicate Path entries.\n"));
586 /* Make list of duplicated names */
588 query = "SELECT Path, count(Path) as Count FROM Path "
589 "GROUP BY Path HAVING count(Path) > 1";
591 if (!make_name_list(query, &name_list)) {
594 printf("Found %d duplicate Path records.\n", name_list.num_ids);
595 if (name_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
596 print_name_list(&name_list);
599 /* Loop through list of duplicate names */
600 for (int i=0; i<name_list.num_ids; i++) {
601 /* Get all the Ids of each name */
602 db_escape_string(esc_name, name_list.name[i], strlen(name_list.name[i]));
603 sprintf(buf, "SELECT PathId FROM Path WHERE Path='%s'", esc_name);
607 if (!make_id_list(buf, &id_list)) {
611 printf("Found %d for: %s\n", id_list.num_ids, name_list.name[i]);
613 /* Force all records to use the first id then delete the other ids */
614 for (int j=1; j<id_list.num_ids; j++) {
615 sprintf(buf, "UPDATE File SET PathId=%u WHERE PathId=%u",
616 id_list.Id[0], id_list.Id[j]);
620 db_sql_query(db, buf, NULL, NULL);
621 sprintf(buf, "DELETE FROM Path WHERE PathId=%u",
626 db_sql_query(db, buf, NULL, NULL);
630 free_name_list(&name_list);
633 static void eliminate_orphaned_jobmedia_records()
637 printf("Checking for orphaned JobMedia entries.\n");
638 query = "SELECT JobMedia.JobMediaId,Job.JobId FROM JobMedia "
639 "LEFT OUTER JOIN Job ON (JobMedia.JobId=Job.JobId) "
640 "WHERE Job.JobId IS NULL";
641 if (!make_id_list(query, &id_list)) {
644 printf("Found %d orphaned JobMedia records.\n", id_list.num_ids);
645 if (id_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
646 for (int i=0; i < id_list.num_ids; i++) {
648 "SELECT JobMedia.JobMediaId,JobMedia.JobId,Media.VolumeName FROM JobMedia,Media "
649 "WHERE JobMedia.JobMediaId=%u AND Media.MediaId=JobMedia.MediaId", id_list.Id[i]);
650 if (!db_sql_query(db, buf, print_jobmedia_handler, NULL)) {
651 printf("%s\n", db_strerror(db));
656 if (fix && id_list.num_ids > 0) {
657 printf("Deleting %d orphaned JobMedia records.\n", id_list.num_ids);
658 delete_id_list("DELETE FROM JobMedia WHERE JobMediaId=%u", &id_list);
662 static void eliminate_orphaned_file_records()
666 printf("Checking for orphaned File entries. This may take some time!\n");
667 query = "SELECT File.FileId,Job.JobId FROM File "
668 "LEFT OUTER JOIN Job ON (File.JobId=Job.JobId) "
669 "WHERE Job.JobId IS NULL";
671 printf("%s\n", query);
673 if (!make_id_list(query, &id_list)) {
676 printf("Found %d orphaned File records.\n", id_list.num_ids);
677 if (name_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
678 for (int i=0; i < id_list.num_ids; i++) {
680 "SELECT File.FileId,File.JobId,Filename.Name FROM File,Filename "
681 "WHERE File.FileId=%u AND File.FilenameId=Filename.FilenameId", id_list.Id[i]);
682 if (!db_sql_query(db, buf, print_file_handler, NULL)) {
683 printf("%s\n", db_strerror(db));
688 if (fix && id_list.num_ids > 0) {
689 printf("Deleting %d orphaned File records.\n", id_list.num_ids);
690 delete_id_list("DELETE FROM File WHERE FileId=%u", &id_list);
694 static void eliminate_orphaned_path_records()
698 printf("Checking for orphaned Path entries. This may take some time!\n");
699 query = "SELECT DISTINCT Path.PathId,File.PathId FROM Path "
700 "LEFT OUTER JOIN File ON (Path.PathId=File.PathId) "
701 "HAVING File.PathId IS NULL";
703 printf("%s\n", query);
705 if (!make_id_list(query, &id_list)) {
708 printf("Found %d orphaned Path records.\n", id_list.num_ids);
709 if (id_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
710 for (int i=0; i < id_list.num_ids; i++) {
711 sprintf(buf, "SELECT Path FROM Path WHERE PathId=%u", id_list.Id[i]);
712 db_sql_query(db, buf, print_name_handler, NULL);
716 if (fix && id_list.num_ids > 0) {
717 printf("Deleting %d orphaned Path records.\n", id_list.num_ids);
718 delete_id_list("DELETE FROM Path WHERE PathId=%u", &id_list);
722 static void eliminate_orphaned_filename_records()
726 printf("Checking for orphaned Filename entries. This may take some time!\n");
727 query = "SELECT Filename.FilenameId,File.FilenameId FROM Filename "
728 "LEFT OUTER JOIN File ON (Filename.FilenameId=File.FilenameId) "
729 "WHERE File.FilenameId IS NULL";
731 printf("%s\n", query);
733 if (!make_id_list(query, &id_list)) {
736 printf("Found %d orphaned Filename records.\n", id_list.num_ids);
737 if (id_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
738 for (int i=0; i < id_list.num_ids; i++) {
739 sprintf(buf, "SELECT Name FROM Filename WHERE FilenameId=%u", id_list.Id[i]);
740 db_sql_query(db, buf, print_name_handler, NULL);
744 if (fix && id_list.num_ids > 0) {
745 printf("Deleting %d orphaned Filename records.\n", id_list.num_ids);
746 delete_id_list("DELETE FROM Filename WHERE FilenameId=%u", &id_list);
750 static void eliminate_orphaned_fileset_records()
754 printf("Checking for orphaned FileSet entries. This takes some time!\n");
755 query = "SELECT FileSet.FileSetId,Job.FileSetId FROM FileSet "
756 "LEFT OUTER JOIN Job ON (FileSet.FileSetId=Job.FileSetId) "
757 "WHERE Job.FileSetId IS NULL";
759 printf("%s\n", query);
761 if (!make_id_list(query, &id_list)) {
764 printf("Found %d orphaned FileSet records.\n", id_list.num_ids);
765 if (id_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
766 for (int i=0; i < id_list.num_ids; i++) {
767 sprintf(buf, "SELECT FileSetId,FileSet,MD5 FROM FileSet "
768 "WHERE FileSetId=%u", id_list.Id[i]);
769 if (!db_sql_query(db, buf, print_fileset_handler, NULL)) {
770 printf("%s\n", db_strerror(db));
775 if (fix && id_list.num_ids > 0) {
776 printf("Deleting %d orphaned FileSet records.\n", id_list.num_ids);
777 delete_id_list("DELETE FROM FileSet WHERE FileSetId=%u", &id_list);
781 static void eliminate_orphaned_client_records()
785 printf("Checking for orphaned Client entries.\n");
787 * Wiffle through Client for every Client
788 * joining with the Job table including every Client even if
789 * there is not a match in Job (left outer join), then
790 * filter out only those where no Job points to a Client
791 * i.e. Job.Client is NULL
793 query = "SELECT Client.ClientId,Client.Name FROM Client "
794 "LEFT OUTER JOIN Job ON (Client.ClientId=Job.ClientId) "
795 "WHERE Job.ClientId IS NULL";
797 printf("%s\n", query);
799 if (!make_id_list(query, &id_list)) {
802 printf("Found %d orphaned Client records.\n", id_list.num_ids);
803 if (id_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
804 for (int i=0; i < id_list.num_ids; i++) {
805 sprintf(buf, "SELECT ClientId,Name FROM Client "
806 "WHERE ClientId=%u", id_list.Id[i]);
807 if (!db_sql_query(db, buf, print_client_handler, NULL)) {
808 printf("%s\n", db_strerror(db));
813 if (fix && id_list.num_ids > 0) {
814 printf("Deleting %d orphaned Client records.\n", id_list.num_ids);
815 delete_id_list("DELETE FROM Client WHERE ClientId=%u", &id_list);
820 static void repair_bad_filenames()
825 printf("Checking for Filenames with a trailing slash\n");
826 query = "SELECT FilenameId,Name from Filename "
827 "WHERE Name LIKE '%/'";
829 printf("%s\n", query);
831 if (!make_id_list(query, &id_list)) {
834 printf("Found %d bad Filename records.\n", id_list.num_ids);
835 if (id_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
836 for (i=0; i < id_list.num_ids; i++) {
838 "SELECT Name FROM Filename WHERE FilenameId=%u", id_list.Id[i]);
839 if (!db_sql_query(db, buf, print_name_handler, NULL)) {
840 printf("%s\n", db_strerror(db));
845 if (fix && id_list.num_ids > 0) {
846 POOLMEM *name = get_pool_memory(PM_FNAME);
848 printf("Reparing %d bad Filename records.\n", id_list.num_ids);
849 for (i=0; i < id_list.num_ids; i++) {
852 "SELECT Name FROM Filename WHERE FilenameId=%u", id_list.Id[i]);
853 if (!db_sql_query(db, buf, get_name_handler, name)) {
854 printf("%s\n", db_strerror(db));
856 /* Strip trailing slash(es) */
857 for (len=strlen(name); len > 0 && name[len-1]=='/'; len--)
865 db_escape_string(esc_name, name, len);
867 bsnprintf(buf, sizeof(buf),
868 "UPDATE Filename SET Name='%s' WHERE FilenameId=%u",
869 esc_name, id_list.Id[i]);
873 db_sql_query(db, buf, NULL, NULL);
878 static void repair_bad_paths()
883 printf("Checking for Paths without a trailing slash\n");
884 query = "SELECT PathId,Path from Path "
885 "WHERE Path NOT LIKE '%/'";
887 printf("%s\n", query);
889 if (!make_id_list(query, &id_list)) {
892 printf("Found %d bad Path records.\n", id_list.num_ids);
893 if (id_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
894 for (i=0; i < id_list.num_ids; i++) {
896 "SELECT Path FROM Path WHERE PathId=%u", id_list.Id[i]);
897 if (!db_sql_query(db, buf, print_name_handler, NULL)) {
898 printf("%s\n", db_strerror(db));
903 if (fix && id_list.num_ids > 0) {
904 POOLMEM *name = get_pool_memory(PM_FNAME);
906 printf("Reparing %d bad Filename records.\n", id_list.num_ids);
907 for (i=0; i < id_list.num_ids; i++) {
910 "SELECT Path FROM Path WHERE PathId=%u", id_list.Id[i]);
911 if (!db_sql_query(db, buf, get_name_handler, name)) {
912 printf("%s\n", db_strerror(db));
914 /* Strip trailing blanks */
915 for (len=strlen(name); len > 0 && name[len-1]==' '; len--) {
918 /* Add trailing slash */
919 len = pm_strcat(&name, "/");
920 db_escape_string(esc_name, name, len);
921 bsnprintf(buf, sizeof(buf), "UPDATE Path SET Path='%s' WHERE PathId=%u",
922 esc_name, id_list.Id[i]);
926 db_sql_query(db, buf, NULL, NULL);
935 * Gen next input command from the terminal
937 static char *get_cmd(const char *prompt)
939 static char cmd[1000];
941 printf("%s", prompt);
942 if (fgets(cmd, sizeof(cmd), stdin) == NULL)
945 strip_trailing_junk(cmd);
949 static int yes_no(const char *prompt)
952 cmd = get_cmd(prompt);
953 return strcasecmp(cmd, "yes") == 0;