2 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3 * Copyright (c) 1991-2000 University of Maryland at College Park
6 * Permission to use, copy, modify, distribute, and sell this software and its
7 * documentation for any purpose is hereby granted without fee, provided that
8 * the above copyright notice appear in all copies and that both that
9 * copyright notice and this permission notice appear in supporting
10 * documentation, and that the name of U.M. not be used in advertising or
11 * publicity pertaining to distribution of the software without specific,
12 * written prior permission. U.M. makes no representations about the
13 * suitability of this software for any purpose. It is provided "as is"
14 * without express or implied warranty.
16 * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
18 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
21 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 * Authors: the Amanda Development Team. Its members are listed in a
24 * file named AUTHORS, in the root directory of this distribution.
27 * $Id: amcheck.c,v 1.149 2006/08/24 01:57:16 paddy_s Exp $
29 * checks for common problems in server and clients
46 #include "taperscan.h"
47 #include "server_util.h"
48 #include "pipespawn.h"
49 #include "amfeatures.h"
51 #define BUFFER_SIZE 32768
53 static time_t conf_ctimeout;
56 static disklist_t origq;
58 static uid_t uid_dumpuser;
63 pid_t start_client_checks(int fd);
64 pid_t start_server_check(int fd, int do_localchk, int do_tapechk);
65 int main(int argc, char **argv);
66 int test_server_pgm(FILE *outf, char *dir, char *pgm, int suid, uid_t dumpuid);
71 error("Usage: amcheck%s [-am] [-w] [-sclt] [-M <address>] <conf> [host [disk]* ]* [-o configoption]*", versionsuffix());
75 static unsigned long malloc_hist_1, malloc_size_1;
76 static unsigned long malloc_hist_2, malloc_size_2;
78 static am_feature_t *our_features = NULL;
79 static char *our_feature_string = NULL;
80 static char *displayunit;
81 static long int unitdivisor;
88 char buffer[BUFFER_SIZE];
90 char *mainfname = NULL;
91 char pid_str[NUM_STR_SIZE];
92 int do_clientchk, client_probs;
93 int do_localchk, do_tapechk, server_probs;
94 pid_t clientchk_pid, serverchk_pid;
95 int opt, tempfd, mainfd;
104 char *tempfname = NULL;
110 int new_argc, my_argc;
111 char **new_argv, **my_argv;
117 set_pname("amcheck");
119 /* Don't die when child closes pipe */
120 signal(SIGPIPE, SIG_IGN);
122 dbopen(DBG_SUBDIR_SERVER);
124 memset(buffer, 0, sizeof(buffer));
125 malloc_size_1 = malloc_inuse(&malloc_hist_1);
127 snprintf(pid_str, SIZEOF(pid_str), "%ld", (long)getpid());
129 erroutput_type = ERR_INTERACTIVE;
131 our_features = am_init_feature_set();
132 our_feature_string = am_feature_to_string(our_features);
139 alwaysmail = mailout = overwrite = 0;
140 do_localchk = do_tapechk = do_clientchk = 0;
141 server_probs = client_probs = 0;
142 tempfd = mainfd = -1;
144 parse_conf(argc, argv, &new_argc, &new_argv);
148 /* process arguments */
150 while((opt = getopt(my_argc, my_argv, "M:mawsclt")) != EOF) {
152 case 'M': mailto=stralloc(optarg);
153 if(!validate_mailto(mailto)){
154 printf("Invalid characters in mail address\n");
162 printf("You can't use -%c because configure didn't find a mailer.\n",
172 printf("You can't use -%c because configure didn't find a mailer.\n",
177 case 's': do_localchk = do_tapechk = 1;
179 case 'c': do_clientchk = 1;
181 case 'l': do_localchk = 1;
183 case 'w': overwrite = 1;
185 case 't': do_tapechk = 1;
192 my_argc -= optind, my_argv += optind;
193 if(my_argc < 1) usage();
196 if ((do_localchk | do_clientchk | do_tapechk) == 0) {
197 /* Check everything if individual checks were not asked for */
198 do_localchk = do_clientchk = do_tapechk = 1;
204 config_name = stralloc(*my_argv);
206 config_dir = vstralloc(CONFIG_DIR, "/", config_name, "/", NULL);
207 conffile = stralloc2(config_dir, CONFFILE_NAME);
208 if(read_conffile(conffile)) {
209 error("errors processing config file \"%s\"", conffile);
213 dbrename(config_name, DBG_SUBDIR_SERVER);
215 report_bad_conf_arg();
218 if(mailout && !mailto &&
219 (getconf_seen(CNF_MAILTO)==0 || strlen(getconf_str(CNF_MAILTO)) == 0)) {
220 printf("\nNo mail address configured in amanda.conf\n");
222 printf("When using -a option please specify -Maddress also\n\n");
224 printf("Use -Maddress instead of -m\n\n");
227 if(mailout && !mailto)
229 if(getconf_seen(CNF_MAILTO) &&
230 strlen(getconf_str(CNF_MAILTO)) > 0) {
231 if(!validate_mailto(getconf_str(CNF_MAILTO))){
232 printf("\nMail address in amanda.conf has invalid characters");
233 printf("\nNo email will be sent\n");
238 printf("\nNo mail address configured in amanda.conf\n");
240 printf("When using -a option please specify -Maddress also\n\n");
242 printf("Use -Maddress instead of -m\n\n");
247 conf_ctimeout = (time_t)getconf_int(CNF_CTIMEOUT);
249 conf_diskfile = getconf_str(CNF_DISKFILE);
250 if (*conf_diskfile == '/') {
251 conf_diskfile = stralloc(conf_diskfile);
253 conf_diskfile = stralloc2(config_dir, conf_diskfile);
255 if(read_diskfile(conf_diskfile, &origq) < 0) {
256 error("could not load disklist %s", conf_diskfile);
259 errstr = match_disklist(&origq, my_argc-1, my_argv+1);
264 amfree(conf_diskfile);
267 * Make sure we are running as the dump user.
269 dumpuser = getconf_str(CNF_DUMPUSER);
270 if ((pw = getpwnam(dumpuser)) == NULL) {
271 error("cannot look up dump user \"%s\"", dumpuser);
274 uid_dumpuser = pw->pw_uid;
275 if ((pw = getpwuid(uid_me)) == NULL) {
276 error("cannot look up my own uid (%ld)", (long)uid_me);
279 if (uid_me != uid_dumpuser) {
280 error("running as user \"%s\" instead of \"%s\"",
281 pw->pw_name, dumpuser);
285 displayunit = getconf_str(CNF_DISPLAYUNIT);
286 unitdivisor = getconf_unit_divisor();
289 * If both server and client side checks are being done, the server
290 * check output goes to the main output, while the client check output
291 * goes to a temporary file and is copied to the main output when done.
293 * If the output is to be mailed, the main output is also a disk file,
294 * otherwise it is stdout.
296 if(do_clientchk && (do_localchk || do_tapechk)) {
297 /* we need the temp file */
298 tempfname = vstralloc(AMANDA_TMPDIR, "/amcheck.temp.", pid_str, NULL);
299 if((tempfd = open(tempfname, O_RDWR|O_CREAT|O_TRUNC, 0600)) == -1) {
300 error("could not open %s: %s", tempfname, strerror(errno));
303 unlink(tempfname); /* so it goes away on close */
308 /* the main fd is a file too */
309 mainfname = vstralloc(AMANDA_TMPDIR, "/amcheck.main.", pid_str, NULL);
310 if((mainfd = open(mainfname, O_RDWR|O_CREAT|O_TRUNC, 0600)) == -1) {
311 error("could not open %s: %s", mainfname, strerror(errno));
314 unlink(mainfname); /* so it goes away on close */
318 /* just use stdout */
321 /* start server side checks */
323 if(do_localchk || do_tapechk)
324 serverchk_pid = start_server_check(mainfd, do_localchk, do_tapechk);
328 /* start client side checks */
331 clientchk_pid = start_client_checks((do_localchk || do_tapechk) ? tempfd : mainfd);
336 /* wait for child processes and note any problems */
339 if((pid = wait(&retstat)) == -1) {
340 if(errno == EINTR) continue;
342 } else if(pid == clientchk_pid) {
343 client_probs = WIFSIGNALED(retstat) || WEXITSTATUS(retstat);
345 } else if(pid == serverchk_pid) {
346 server_probs = WIFSIGNALED(retstat) || WEXITSTATUS(retstat);
349 char number[NUM_STR_SIZE];
350 char *wait_msg = NULL;
352 snprintf(number, SIZEOF(number), "%ld", (long)pid);
353 wait_msg = vstralloc("parent: reaped bogus pid ", number, "\n",
355 if (fullwrite(mainfd, wait_msg, strlen(wait_msg)) < 0) {
356 error("write main file: %s", strerror(errno));
363 /* copy temp output to main output and write tagline */
365 if(do_clientchk && (do_localchk || do_tapechk)) {
366 if(lseek(tempfd, (off_t)0, 0) == (off_t)-1) {
367 error("seek temp file: %s", strerror(errno));
371 while((size = fullread(tempfd, buffer, SIZEOF(buffer))) > 0) {
372 if (fullwrite(mainfd, buffer, (size_t)size) < 0) {
373 error("write main file: %s", strerror(errno));
378 error("read temp file: %s", strerror(errno));
384 version_string = vstralloc("\n",
385 "(brought to you by Amanda ", version(), ")\n",
387 if (fullwrite(mainfd, version_string, strlen(version_string)) < 0) {
388 error("write main file: %s", strerror(errno));
391 amfree(version_string);
394 amfree(our_feature_string);
395 am_release_feature_set(our_features);
398 malloc_size_2 = malloc_inuse(&malloc_hist_2);
400 if(malloc_size_1 != malloc_size_2) {
401 malloc_list(fileno(stderr), malloc_hist_1, malloc_hist_2);
404 /* send mail if requested, but only if there were problems */
407 #define MAILTO_LIMIT 10
409 if((server_probs || client_probs || alwaysmail) && mailout) {
420 char *extra_info = NULL;
425 char number[NUM_STR_SIZE];
428 if(lseek(mainfd, (off_t)0, SEEK_SET) == (off_t)-1) {
429 error("lseek main file: %s", strerror(errno));
432 if(alwaysmail && !(server_probs || client_probs)) {
433 subject = stralloc2(getconf_str(CNF_ORG),
434 " AMCHECK REPORT: NO PROBLEMS FOUND");
436 subject = stralloc2(getconf_str(CNF_ORG),
437 " AMANDA PROBLEM: FIX BEFORE RUN, IF POSSIBLE");
440 * Variable arg lists are hard to deal with when we do not know
441 * ourself how many args are involved. Split the address list
442 * and hope there are not more than 9 entries.
444 * Remember that split() returns the original input string in
445 * argv[0], so we have to skip over that.
447 a = (char **) alloc((MAILTO_LIMIT + 1) * SIZEOF(char *));
448 memset(a, 0, (MAILTO_LIMIT + 1) * SIZEOF(char *));
453 r = (ssize_t)split(getconf_str(CNF_MAILTO), a, MAILTO_LIMIT, " ");
456 if((nullfd = open("/dev/null", O_RDWR)) < 0) {
457 error("nullfd: /dev/null: %s", strerror(errno));
460 pipespawn(MAILER, STDIN_PIPE | STDERR_PIPE,
461 &mailfd, &nullfd, &errfd,
464 a[1], a[2], a[3], a[4],
465 a[5], a[6], a[7], a[8], a[9],
469 * There is the potential for a deadlock here since we are writing
470 * to the process and then reading stderr, but in the normal case,
471 * nothing should be coming back to us, and hopefully in error
472 * cases, the pipe will break and we will exit out of the loop.
474 signal(SIGPIPE, SIG_IGN);
475 while((r = fullread(mainfd, buffer, SIZEOF(buffer))) > 0) {
476 if((w = fullwrite(mailfd, buffer, (size_t)r)) != (ssize_t)r) {
477 if(w < 0 && errno == EPIPE) {
478 strappend(extra_info, "EPIPE writing to mail process\n");
481 error("mailfd write: %s", strerror(errno));
484 error("mailfd write: wrote " SSIZE_T_FMT " instead of " SIZE_T_FMT, w, r);
490 ferr = fdopen(errfd, "r");
492 error("Can't fdopen: %s", strerror(errno));
495 for(; (line = agets(ferr)) != NULL; free(line)) {
498 strappend(extra_info, line);
499 strappend(extra_info, "\n");
504 while (wait(&retstat) != -1) {
505 if (WIFSIGNALED(retstat)) {
507 rc = sig = WTERMSIG(retstat);
510 rc = ret = WEXITSTATUS(retstat);
514 strappend(err, "got signal ");
517 strappend(err, "returned ");
519 snprintf(number, SIZEOF(number), "%d", ret);
520 strappend(err, number);
525 fputs(extra_info, stderr);
528 error("error running mailer %s: %s", MAILER, err);
533 free_new_argv(new_argc, new_argv);
534 free_server_config();
537 return (server_probs || client_probs);
540 /* --------------------------------------------------- */
542 int nslots, backwards, found, got_match, tapedays;
544 char *first_match_label = NULL, *first_match = NULL, *found_device = NULL;
546 char *searchlabel, *labelstr;
562 pgm = vstralloc(dir, "/", pgm, versionsuffix(), NULL);
563 quoted = quote_string(pgm);
564 if(stat(pgm, &statbuf) == -1) {
565 fprintf(outf, "ERROR: program %s: does not exist\n",
568 } else if (!S_ISREG(statbuf.st_mode)) {
569 fprintf(outf, "ERROR: program %s: not a file\n",
572 } else if (access(pgm, X_OK) == -1) {
573 fprintf(outf, "ERROR: program %s: not executable\n",
578 && (statbuf.st_uid != 0 || (statbuf.st_mode & 04000) == 0)) {
579 fprintf(outf, "ERROR: program %s: not setuid-root\n",
595 generic_fs_stats_t fs;
599 int confbad = 0, tapebad = 0, disklow = 0, logbad = 0;
600 int userbad = 0, infobad = 0, indexbad = 0, pgmbad = 0;
601 int testtape = do_tapechk;
602 tapetype_t *tp = NULL;
605 struct addrinfo *gaires;
606 struct addrinfo hints;
608 switch(pid = fork()) {
610 error("could not fork server check: %s", strerror(errno));
623 set_pname("amcheck-server");
627 if((outf = fdopen(fd, "w")) == NULL) {
628 error("fdopen %d: %s", fd, strerror(errno));
633 fprintf(outf, "Amanda Tape Server Host Check\n");
634 fprintf(outf, "-----------------------------\n");
636 if (do_localchk || testtape) {
637 tp = lookup_tapetype(getconf_str(CNF_TAPETYPE));
641 * Check various server side config file settings.
648 ColumnSpec = getconf_str(CNF_COLUMNSPEC);
649 if(SetColumDataFromString(ColumnData, ColumnSpec, &errstr) < 0) {
650 fprintf(outf, "ERROR: %s\n", errstr);
654 lbl_templ = tapetype_get_lbl_templ(tp);
655 if(strcmp(lbl_templ, "") != 0) {
656 if(strchr(lbl_templ, '/') == NULL) {
657 lbl_templ = stralloc2(config_dir, lbl_templ);
659 lbl_templ = stralloc(lbl_templ);
661 if(access(lbl_templ, R_OK) == -1) {
663 "ERROR: cannot access lbl_templ file %s: %s\n",
669 fprintf(outf, "ERROR: lbl_templ set but no LPRCMD defined, you should reconfigure amanda\n and make sure it find a lpr or lp command.\n");
674 /* Double-check that 'localhost' resolves properly */
676 hints.ai_flags = AI_CANONNAME | AI_V4MAPPED | AI_ALL;
677 hints.ai_family = AF_UNSPEC;
679 hints.ai_flags = AI_CANONNAME;
680 hints.ai_family = AF_INET;
682 hints.ai_socktype = 0;
683 hints.ai_protocol = 0;
684 hints.ai_addrlen = 0;
685 hints.ai_addr = NULL;
686 hints.ai_canonname = NULL;
687 hints.ai_next = NULL;
688 res = getaddrinfo("localhost", NULL, &hints, &gaires);
691 hints.ai_flags = AI_CANONNAME;
692 hints.ai_family = AF_UNSPEC;
693 res = getaddrinfo("localhost", NULL, &hints, &gaires);
697 fprintf(outf, _("ERROR: Cannot resolve `localhost': %s\n"), gai_strerror(res));
699 if (gaires) freeaddrinfo(gaires);
703 * Look up the programs used on the server side.
707 * entreprise version will do planner/dumper suid check
709 if(access(libexecdir, X_OK) == -1) {
710 quoted = quote_string(libexecdir);
711 fprintf(outf, "ERROR: program dir %s: not accessible\n",
716 if(test_server_pgm(outf, libexecdir, "planner", 1, uid_dumpuser))
718 if(test_server_pgm(outf, libexecdir, "dumper", 1, uid_dumpuser))
720 if(test_server_pgm(outf, libexecdir, "driver", 0, uid_dumpuser))
722 if(test_server_pgm(outf, libexecdir, "taper", 0, uid_dumpuser))
724 if(test_server_pgm(outf, libexecdir, "amtrmidx", 0, uid_dumpuser))
726 if(test_server_pgm(outf, libexecdir, "amlogroll", 0, uid_dumpuser))
729 if(access(sbindir, X_OK) == -1) {
730 quoted = quote_string(sbindir);
731 fprintf(outf, "ERROR: program dir %s: not accessible\n",
736 if(test_server_pgm(outf, sbindir, "amgetconf", 0, uid_dumpuser))
738 if(test_server_pgm(outf, sbindir, "amcheck", 1, uid_dumpuser))
740 if(test_server_pgm(outf, sbindir, "amdump", 0, uid_dumpuser))
742 if(test_server_pgm(outf, sbindir, "amreport", 0, uid_dumpuser))
745 if(access(COMPRESS_PATH, X_OK) == -1) {
746 quoted = quote_string(COMPRESS_PATH);
747 fprintf(outf, "WARNING: %s is not executable, server-compression and indexing will not work\n",
754 * Check that the directory for the tapelist file is writable, as well
755 * as the tapelist file itself (if it already exists). Also, check for
756 * a "hold" file (just because it is convenient to do it here) and warn
757 * if tapedev is set to the null device.
760 if(do_localchk || do_tapechk) {
768 conf_tapelist=getconf_str(CNF_TAPELIST);
769 if (*conf_tapelist == '/') {
770 tapefile = stralloc(conf_tapelist);
772 tapefile = stralloc2(config_dir, conf_tapelist);
775 * XXX There Really Ought to be some error-checking here... dhw
777 tape_dir = stralloc(tapefile);
778 if ((lastslash = strrchr((const char *)tape_dir, '/')) != NULL) {
781 * else whine Really Loudly about a path with no slashes??!?
784 if(access(tape_dir, W_OK) == -1) {
785 quoted = quote_string(tape_dir);
786 fprintf(outf, "ERROR: tapelist dir %s: not writable.\n", quoted);
790 else if(stat(tapefile, &statbuf) == -1) {
791 quoted = quote_string(tape_dir);
792 fprintf(outf, "ERROR: tapelist %s (%s), "
793 "you must create an empty file.\n",
794 quoted, strerror(errno));
798 else if(!S_ISREG(statbuf.st_mode)) {
799 quoted = quote_string(tapefile);
800 fprintf(outf, "ERROR: tapelist %s: should be a regular file.\n",
805 else if(access(tapefile, F_OK) != 0) {
806 quoted = quote_string(tapefile);
807 fprintf(outf, "ERROR: can't access tapelist %s\n", quoted);
810 } else if(access(tapefile, F_OK) == 0 && access(tapefile, W_OK) != 0) {
811 quoted = quote_string(tapefile);
812 fprintf(outf, "ERROR: tapelist %s: not writable\n", quoted);
815 } else if(read_tapelist(tapefile)) {
816 quoted = quote_string(tapefile);
817 fprintf(outf, "ERROR: tapelist %s: parse error\n", quoted);
821 holdfile = vstralloc(config_dir, "/", "hold", NULL);
822 if(access(holdfile, F_OK) != -1) {
823 quoted = quote_string(holdfile);
824 fprintf(outf, "WARNING: hold file %s exists\n", holdfile);
830 tapename = getconf_str(CNF_TAPEDEV);
831 if (tapename == NULL) {
832 if (getconf_str(CNF_TPCHANGER) == NULL) {
833 fprintf(outf, "WARNING: No tapedev or tpchanger specified\n");
837 } else if (strncmp_const(tapename, "null:") == 0) {
839 "WARNING: tapedev is %s, dumps will be thrown away\n",
846 /* check available disk space */
849 for(hdp = holdingdisks; hdp != NULL; hdp = hdp->next) {
850 quoted = quote_string(holdingdisk_get_diskdir(hdp));
851 if(get_fs_stats(holdingdisk_get_diskdir(hdp), &fs) == -1) {
852 fprintf(outf, "ERROR: holding dir %s (%s), "
853 "you must create a directory.\n",
854 quoted, strerror(errno));
857 else if(access(holdingdisk_get_diskdir(hdp), W_OK) == -1) {
858 fprintf(outf, "ERROR: holding disk %s: not writable: %s.\n",
859 quoted, strerror(errno));
862 else if(access(holdingdisk_get_diskdir(hdp), X_OK) == -1) {
863 fprintf(outf, "ERROR: holding disk %s: not searcheable: %s.\n",
864 quoted, strerror(errno));
867 else if(fs.avail == (off_t)-1) {
869 "WARNING: holding disk %s: "
870 "available space unknown (" OFF_T_FMT" KB requested)\n",
871 quoted, (OFF_T_FMT_TYPE)holdingdisk_get_disksize(hdp));
874 else if(holdingdisk_get_disksize(hdp) > (off_t)0) {
875 if(fs.avail < holdingdisk_get_disksize(hdp)) {
877 "WARNING: holding disk %s: "
878 "only " OFF_T_FMT " %sB free ("
879 OFF_T_FMT " %sB requested)\n", quoted,
880 (OFF_T_FMT_TYPE)(fs.avail / (off_t)unitdivisor),
882 (OFF_T_FMT_TYPE)(holdingdisk_get_disksize(hdp)/(off_t)unitdivisor),
888 "Holding disk %s: " OFF_T_FMT
889 " %sB disk space available,"
890 " using " OFF_T_FMT " %sB as requested\n",
892 (OFF_T_FMT_TYPE)(fs.avail/(off_t)unitdivisor),
894 (OFF_T_FMT_TYPE)(holdingdisk_get_disksize(hdp)/(off_t)unitdivisor),
899 if((fs.avail + holdingdisk_get_disksize(hdp)) < (off_t)0) {
901 "WARNING: holding disk %s: "
902 "only " OFF_T_FMT " %sB free, using nothing\n",
903 quoted, (OFF_T_FMT_TYPE)(fs.avail/(off_t)unitdivisor),
910 OFF_T_FMT " %sB disk space available, using "
913 (OFF_T_FMT_TYPE)(fs.avail/(off_t)unitdivisor),
915 (OFF_T_FMT_TYPE)((fs.avail + holdingdisk_get_disksize(hdp)) / (off_t)unitdivisor),
923 /* check that the log file is writable if it already exists */
929 struct stat stat_old;
932 conf_logdir = getconf_str(CNF_LOGDIR);
933 if (*conf_logdir == '/') {
934 conf_logdir = stralloc(conf_logdir);
936 conf_logdir = stralloc2(config_dir, conf_logdir);
938 logfile = vstralloc(conf_logdir, "/log", NULL);
940 quoted = quote_string(conf_logdir);
941 if(stat(conf_logdir, &statbuf) == -1) {
942 fprintf(outf, "ERROR: logdir %s (%s), you must create directory.\n",
943 quoted, strerror(errno));
946 else if(access(conf_logdir, W_OK) == -1) {
947 fprintf(outf, "ERROR: log dir %s: not writable\n", quoted);
952 if(access(logfile, F_OK) == 0) {
955 if(access(logfile, W_OK) != 0) {
956 quoted = quote_string(logfile);
957 fprintf(outf, "ERROR: log file %s: not writable\n", quoted);
962 olddir = vstralloc(conf_logdir, "/oldlog", NULL);
963 quoted = quote_string(olddir);
964 if (stat(olddir,&stat_old) == 0) { /* oldlog exist */
965 if(!(S_ISDIR(stat_old.st_mode))) {
966 fprintf(outf, "ERROR: oldlog directory %s is not a directory\n",
970 if(access(olddir, W_OK) == -1) {
971 fprintf(outf, "ERROR: oldlog dir %s: not writable\n", quoted);
975 else if(lstat(olddir,&stat_old) == 0) {
976 fprintf(outf, "ERROR: oldlog directory %s is not a directory\n",
983 logfile = newvstralloc(logfile, conf_logdir, "/amdump", NULL);
984 if (access(logfile, F_OK) == 0) {
996 /* check that the tape is a valid amanda tape */
999 tapedays = getconf_int(CNF_TAPECYCLE);
1000 labelstr = getconf_str(CNF_LABELSTR);
1001 tapename = getconf_str(CNF_TAPEDEV);
1003 if (!getconf_seen(CNF_TPCHANGER) && getconf_int(CNF_RUNTAPES) != 1) {
1005 "WARNING: if a tape changer is not available, runtapes must be set to 1\n");
1008 tape_status = taper_scan(NULL, &label, &datestamp, &tapename,
1009 FILE_taperscan_output_callback, outf);
1011 if (tape_access(tapename,F_OK) == -1) {
1012 fprintf(outf, "ERROR: Can't access device %s: %s\n", tapename,
1016 if (tape_access(tapename,R_OK) == -1) {
1017 fprintf(outf, "ERROR: Can't read device %s: %s\n", tapename,
1021 if (tape_access(tapename,W_OK) == -1) {
1022 fprintf(outf, "ERROR: Can't write to device %s: %s\n", tapename,
1027 if (tape_status < 0) {
1028 tape_t *exptape = lookup_last_reusable_tape(0);
1029 fprintf(outf, " (expecting ");
1030 if(exptape != NULL) fprintf(outf, "tape %s or ", exptape->label);
1031 fprintf(outf, "a new tape)\n");
1035 char *wrlabel_status;
1036 wrlabel_status = tape_wrlabel(tapename, "X", label,
1037 (unsigned)(tapetype_get_blocksize(tp) * 1024));
1038 if (wrlabel_status != NULL) {
1039 if (tape_status == 3) {
1041 "ERROR: Could not label brand new tape: %s\n",
1045 "ERROR: tape %s label ok, but is not writable (%s)\n",
1046 label, wrlabel_status);
1050 if (tape_status != 3) {
1051 fprintf(outf, "Tape %s is writable; rewrote label.\n", label);
1053 fprintf(outf, "Wrote label %s to brand new tape.\n", label);
1057 fprintf(outf, "NOTE: skipping tape-writable test\n");
1058 if (tape_status == 3) {
1060 "Found a brand new tape, will label it %s.\n",
1063 fprintf(outf, "Tape %s label ok\n", label);
1068 } else if (do_tapechk) {
1069 fprintf(outf, "WARNING: skipping tape test because amdump or amflush seem to be running\n");
1070 fprintf(outf, "WARNING: if they are not, you must run amcleanup\n");
1071 } else if (logbad == 2) {
1072 fprintf(outf, "WARNING: amdump or amflush seem to be running\n");
1073 fprintf(outf, "WARNING: if they are not, you must run amcleanup\n");
1075 fprintf(outf, "NOTE: skipping tape checks\n");
1079 * See if the information file and index directory for each client
1080 * and disk is OK. Since we may be seeing clients and/or disks for
1081 * the first time, these are just warnings, not errors.
1084 char *conf_infofile;
1085 char *conf_indexdir;
1086 char *hostinfodir = NULL;
1087 char *hostindexdir = NULL;
1088 char *diskdir = NULL;
1089 char *infofile = NULL;
1090 struct stat statbuf;
1093 int indexdir_checked = 0;
1094 int hostindexdir_checked = 0;
1097 int conf_tapecycle, conf_runspercycle;
1099 conf_tapecycle = getconf_int(CNF_TAPECYCLE);
1100 conf_runspercycle = getconf_int(CNF_RUNSPERCYCLE);
1102 if(conf_tapecycle <= conf_runspercycle) {
1103 fprintf(outf, "WARNING: tapecycle (%d) <= runspercycle (%d).\n",
1104 conf_tapecycle, conf_runspercycle);
1107 conf_infofile = getconf_str(CNF_INFOFILE);
1108 if (*conf_infofile == '/') {
1109 conf_infofile = stralloc(conf_infofile);
1111 conf_infofile = stralloc2(config_dir, conf_infofile);
1114 conf_indexdir = getconf_str(CNF_INDEXDIR);
1115 if (*conf_indexdir == '/') {
1116 conf_indexdir = stralloc(conf_indexdir);
1118 conf_indexdir = stralloc2(config_dir, conf_indexdir);
1122 quoted = quote_string(conf_infofile);
1123 if(stat(conf_infofile, &statbuf) == -1) {
1124 if (errno == ENOENT) {
1125 fprintf(outf, "NOTE: conf info dir %s does not exist\n",
1127 fprintf(outf, "NOTE: it will be created on the next run.\n");
1129 fprintf(outf, "ERROR: conf info dir %s (%s)\n",
1130 quoted, strerror(errno));
1133 amfree(conf_infofile);
1134 } else if (!S_ISDIR(statbuf.st_mode)) {
1135 fprintf(outf, "ERROR: info dir %s: not a directory\n", quoted);
1136 amfree(conf_infofile);
1138 } else if (access(conf_infofile, W_OK) == -1) {
1139 fprintf(outf, "ERROR: info dir %s: not writable\n", quoted);
1140 amfree(conf_infofile);
1143 char *errmsg = NULL;
1144 if (check_infofile(conf_infofile, &origq, &errmsg) == -1) {
1145 fprintf(outf, "ERROR: Can't copy infofile: %s\n", errmsg);
1149 strappend(conf_infofile, "/");
1154 while(!empty(origq)) {
1155 hostp = origq.head->host;
1156 host = sanitise_filename(hostp->hostname);
1159 hostinfodir = newstralloc2(hostinfodir, conf_infofile, host);
1160 quoted = quote_string(hostinfodir);
1161 if(stat(hostinfodir, &statbuf) == -1) {
1162 if (errno == ENOENT) {
1163 fprintf(outf, "NOTE: host info dir %s does not exist\n",
1166 "NOTE: it will be created on the next run.\n");
1168 fprintf(outf, "ERROR: host info dir %s (%s)\n",
1169 quoted, strerror(errno));
1172 amfree(hostinfodir);
1173 } else if (!S_ISDIR(statbuf.st_mode)) {
1174 fprintf(outf, "ERROR: info dir %s: not a directory\n",
1176 amfree(hostinfodir);
1178 } else if (access(hostinfodir, W_OK) == -1) {
1179 fprintf(outf, "ERROR: info dir %s: not writable\n", quoted);
1180 amfree(hostinfodir);
1183 strappend(hostinfodir, "/");
1188 for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1189 disk = sanitise_filename(dp->name);
1194 diskdir = newstralloc2(diskdir, hostinfodir, disk);
1195 infofile = vstralloc(diskdir, "/", "info", NULL);
1196 quoted = quote_string(diskdir);
1197 quotedif = quote_string(infofile);
1198 if(stat(diskdir, &statbuf) == -1) {
1199 if (errno == ENOENT) {
1200 fprintf(outf, "NOTE: info dir %s does not exist\n",
1203 "NOTE: it will be created on the next run.\n");
1205 fprintf(outf, "ERROR: info dir %s (%s)\n",
1206 quoted, strerror(errno));
1209 } else if (!S_ISDIR(statbuf.st_mode)) {
1210 fprintf(outf, "ERROR: info dir %s: not a directory\n",
1213 } else if (access(diskdir, W_OK) == -1) {
1214 fprintf(outf, "ERROR: info dir %s: not writable\n",
1217 } else if(stat(infofile, &statbuf) == -1) {
1218 if (errno == ENOENT) {
1219 fprintf(outf, "NOTE: info file %s does not exist\n",
1221 fprintf(outf, "NOTE: it will be created on the next run.\n");
1223 fprintf(outf, "ERROR: info dir %s (%s)\n",
1224 quoted, strerror(errno));
1227 } else if (!S_ISREG(statbuf.st_mode)) {
1228 fprintf(outf, "ERROR: info file %s: not a file\n",
1231 } else if (access(infofile, R_OK) == -1) {
1232 fprintf(outf, "ERROR: info file %s: not readable\n",
1242 if(! indexdir_checked) {
1243 quoted = quote_string(conf_indexdir);
1244 if(stat(conf_indexdir, &statbuf) == -1) {
1245 if (errno == ENOENT) {
1246 fprintf(outf, "NOTE: index dir %s does not exist\n",
1248 fprintf(outf, "NOTE: it will be created on the next run.\n");
1250 fprintf(outf, "ERROR: index dir %s (%s)\n",
1251 quoted, strerror(errno));
1254 amfree(conf_indexdir);
1255 } else if (!S_ISDIR(statbuf.st_mode)) {
1256 fprintf(outf, "ERROR: index dir %s: not a directory\n",
1258 amfree(conf_indexdir);
1260 } else if (access(conf_indexdir, W_OK) == -1) {
1261 fprintf(outf, "ERROR: index dir %s: not writable\n",
1263 amfree(conf_indexdir);
1266 strappend(conf_indexdir, "/");
1268 indexdir_checked = 1;
1272 if(! hostindexdir_checked) {
1273 hostindexdir = stralloc2(conf_indexdir, host);
1274 quoted = quote_string(hostindexdir);
1275 if(stat(hostindexdir, &statbuf) == -1) {
1276 if (errno == ENOENT) {
1277 fprintf(outf, "NOTE: index dir %s does not exist\n",
1279 fprintf(outf, "NOTE: it will be created on the next run.\n");
1281 fprintf(outf, "ERROR: index dir %s (%s)\n",
1282 quoted, strerror(errno));
1285 amfree(hostindexdir);
1286 } else if (!S_ISDIR(statbuf.st_mode)) {
1287 fprintf(outf, "ERROR: index dir %s: not a directory\n",
1289 amfree(hostindexdir);
1291 } else if (access(hostindexdir, W_OK) == -1) {
1292 fprintf(outf, "ERROR: index dir %s: not writable\n",
1294 amfree(hostindexdir);
1297 strappend(hostindexdir, "/");
1299 hostindexdir_checked = 1;
1303 diskdir = newstralloc2(diskdir, hostindexdir, disk);
1304 quoted = quote_string(diskdir);
1305 if(stat(diskdir, &statbuf) == -1) {
1306 if (errno == ENOENT) {
1307 fprintf(outf, "NOTE: index dir %s does not exist\n",
1309 fprintf(outf, "NOTE: it will be created on the next run.\n");
1311 fprintf(outf, "ERROR: index dir %s (%s)\n",
1312 quoted, strerror(errno));
1315 } else if (!S_ISDIR(statbuf.st_mode)) {
1316 fprintf(outf, "ERROR: index dir %s: not a directory\n",
1319 } else if (access(diskdir, W_OK) == -1) {
1320 fprintf(outf, "ERROR: index dir %s: is not writable\n",
1329 if ( dp->encrypt == ENCRYPT_SERV_CUST ) {
1330 if ( dp->srv_encrypt[0] == '\0' ) {
1331 fprintf(outf, "ERROR: server encryption program not specified\n");
1334 else if(access(dp->srv_encrypt, X_OK) == -1) {
1335 fprintf(outf, "ERROR: %s is not executable, server encryption will not work\n",
1340 if ( dp->compress == COMP_SERVER_CUST ) {
1341 if ( dp->srvcompprog[0] == '\0' ) {
1342 fprintf(outf, "ERROR: server custom compression program not specified\n");
1345 else if(access(dp->srvcompprog, X_OK) == -1) {
1346 quoted = quote_string(dp->srvcompprog);
1348 fprintf(outf, "ERROR: %s is not executable, server custom compression will not work\n",
1356 remove_disk(&origq, dp);
1359 amfree(hostindexdir);
1360 hostindexdir_checked = 0;
1363 amfree(hostinfodir);
1364 amfree(conf_infofile);
1365 amfree(conf_indexdir);
1371 amfree(config_name);
1373 fprintf(outf, "Server check took %s seconds\n", walltime_str(curclock()));
1377 malloc_size_2 = malloc_inuse(&malloc_hist_2);
1379 if(malloc_size_1 != malloc_size_2) {
1380 malloc_list(fd, malloc_hist_1, malloc_hist_2);
1395 /* --------------------------------------------------- */
1400 static void handle_result(void *, pkt_t *, security_handle_t *);
1401 void start_host(am_host_t *hostp);
1403 #define HOST_READY ((void *)0) /* must be 0 */
1404 #define HOST_ACTIVE ((void *)1)
1405 #define HOST_DONE ((void *)2)
1407 #define DISK_READY ((void *)0) /* must be 0 */
1408 #define DISK_ACTIVE ((void *)1)
1409 #define DISK_DONE ((void *)2)
1419 const security_driver_t *secdrv;
1420 char number[NUM_STR_SIZE];
1422 if(hostp->up != HOST_READY) {
1426 if (strcmp(hostp->hostname,"localhost") == 0) {
1428 "WARNING: Usage of fully qualified hostname recommended for Client %s.\n",
1433 * The first time through here we send a "noop" request. This will
1434 * return the feature list from the client if it supports that.
1435 * If it does not, handle_result() will set the feature list to an
1436 * empty structure. In either case, we do the disks on the second
1437 * (and subsequent) pass(es).
1440 if(hostp->features != NULL) { /* selfcheck service */
1441 int has_features = am_has_feature(hostp->features,
1442 fe_req_options_features);
1443 int has_hostname = am_has_feature(hostp->features,
1444 fe_req_options_hostname);
1445 int has_maxdumps = am_has_feature(hostp->features,
1446 fe_req_options_maxdumps);
1447 int has_config = am_has_feature(hostp->features,
1448 fe_req_options_config);
1450 if(!am_has_feature(hostp->features, fe_selfcheck_req) &&
1451 !am_has_feature(hostp->features, fe_selfcheck_req_device)) {
1453 "ERROR: Client %s does not support selfcheck REQ packet.\n",
1456 if(!am_has_feature(hostp->features, fe_selfcheck_rep)) {
1458 "ERROR: Client %s does not support selfcheck REP packet.\n",
1461 if(!am_has_feature(hostp->features, fe_sendsize_req_options) &&
1462 !am_has_feature(hostp->features, fe_sendsize_req_no_options) &&
1463 !am_has_feature(hostp->features, fe_sendsize_req_device)) {
1465 "ERROR: Client %s does not support sendsize REQ packet.\n",
1468 if(!am_has_feature(hostp->features, fe_sendsize_rep)) {
1470 "ERROR: Client %s does not support sendsize REP packet.\n",
1473 if(!am_has_feature(hostp->features, fe_sendbackup_req) &&
1474 !am_has_feature(hostp->features, fe_sendbackup_req_device)) {
1476 "ERROR: Client %s does not support sendbackup REQ packet.\n",
1479 if(!am_has_feature(hostp->features, fe_sendbackup_rep)) {
1481 "ERROR: Client %s does not support sendbackup REP packet.\n",
1485 snprintf(number, SIZEOF(number), "%d", hostp->maxdumps);
1486 req = vstralloc("SERVICE ", "selfcheck", "\n",
1488 has_features ? "features=" : "",
1489 has_features ? our_feature_string : "",
1490 has_features ? ";" : "",
1491 has_maxdumps ? "maxdumps=" : "",
1492 has_maxdumps ? number : "",
1493 has_maxdumps ? ";" : "",
1494 has_hostname ? "hostname=" : "",
1495 has_hostname ? hostp->hostname : "",
1496 has_hostname ? ";" : "",
1497 has_config ? "config=" : "",
1498 has_config ? config_name : "",
1499 has_config ? ";" : "",
1503 req_len = strlen(req);
1504 req_len += 128; /* room for SECURITY ... */
1505 req_len += 256; /* room for non-disk answers */
1506 for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1514 if(dp->up != DISK_READY || dp->todo != 1) {
1517 o = optionstr(dp, hostp->features, outf);
1522 qname = quote_string(dp->name);
1523 qdevice = quote_string(dp->device);
1524 if ((dp->name && qname[0] == '"') ||
1525 (dp->device && qdevice[0] == '"')) {
1526 if(!am_has_feature(hostp->features, fe_interface_quoted_text)) {
1528 "WARNING: %s:%s:%s host does not support quoted text\n",
1529 hostp->hostname, qname, qdevice);
1534 if(!am_has_feature(hostp->features, fe_selfcheck_req_device)) {
1536 "ERROR: %s:%s (%s): selfcheck does not support device.\n",
1537 hostp->hostname, qname, dp->device);
1539 if(!am_has_feature(hostp->features, fe_sendsize_req_device)) {
1541 "ERROR: %s:%s (%s): sendsize does not support device.\n",
1542 hostp->hostname, qname, dp->device);
1544 if(!am_has_feature(hostp->features, fe_sendbackup_req_device)) {
1546 "ERROR: %s:%s (%s): sendbackup does not support device.\n",
1547 hostp->hostname, qname, dp->device);
1550 if(strcmp(dp->program,"DUMP") == 0 ||
1551 strcmp(dp->program,"GNUTAR") == 0) {
1552 if(strcmp(dp->program, "DUMP") == 0 &&
1553 !am_has_feature(hostp->features, fe_program_dump)) {
1554 fprintf(outf, "ERROR: %s:%s does not support DUMP.\n",
1555 hostp->hostname, qname);
1557 if(strcmp(dp->program, "GNUTAR") == 0 &&
1558 !am_has_feature(hostp->features, fe_program_gnutar)) {
1559 fprintf(outf, "ERROR: %s:%s does not support GNUTAR.\n",
1560 hostp->hostname, qname);
1562 if(dp->estimate == ES_CALCSIZE &&
1563 !am_has_feature(hostp->features, fe_calcsize_estimate)) {
1564 fprintf(outf, "ERROR: %s:%s does not support CALCSIZE for estimate, using CLIENT.\n",
1565 hostp->hostname, qname);
1566 dp->estimate = ES_CLIENT;
1568 if(dp->estimate == ES_CALCSIZE &&
1569 am_has_feature(hostp->features, fe_selfcheck_calcsize))
1570 calcsize = "CALCSIZE ";
1574 if(dp->compress == COMP_CUST &&
1575 !am_has_feature(hostp->features, fe_options_compress_cust)) {
1577 "ERROR: Client %s does not support custom compression.\n",
1580 if(dp->encrypt == ENCRYPT_CUST ) {
1581 if ( !am_has_feature(hostp->features, fe_options_encrypt_cust)) {
1583 "ERROR: Client %s does not support data encryption.\n",
1586 } else if ( dp->compress == COMP_SERVER_FAST ||
1587 dp->compress == COMP_SERVER_BEST ||
1588 dp->compress == COMP_SERVER_CUST ) {
1590 "ERROR: %s: Client encryption with server compression is not supported. See amanda.conf(5) for detail.\n", hostp->hostname);
1595 l = vstralloc(calcsize,
1605 l = vstralloc(calcsize,
1614 if(!am_has_feature(hostp->features, fe_program_backup_api)) {
1615 fprintf(outf, "ERROR: %s:%s does not support BACKUP-API.\n",
1616 hostp->hostname, qname);
1619 l = vstralloc("BACKUP ",
1630 l = vstralloc("BACKUP ",
1648 dp->up = DISK_ACTIVE;
1652 else { /* noop service */
1653 req = vstralloc("SERVICE ", "noop", "\n",
1655 "features=", our_feature_string, ";",
1658 for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1659 if(dp->up != DISK_READY || dp->todo != 1) {
1666 if(disk_count == 0) {
1668 hostp->up = HOST_DONE;
1672 secdrv = security_getdriver(hostp->disks->security_driver);
1673 if (secdrv == NULL) {
1674 error("could not find security driver '%s' for host '%s'",
1675 hostp->disks->security_driver, hostp->hostname);
1678 protocol_sendreq(hostp->hostname, secdrv, amhost_get_security_conf,
1679 req, conf_ctimeout, handle_result, hostp);
1683 hostp->up = HOST_ACTIVE;
1687 start_client_checks(
1696 switch(pid = fork()) {
1698 error("could not fork client check: %s", strerror(errno));
1711 set_pname("amcheck-clients");
1715 if((outf = fdopen(fd, "w")) == NULL) {
1716 error("fdopen %d: %s", fd, strerror(errno));
1721 fprintf(outf, "\nAmanda Backup Client Hosts Check\n");
1722 fprintf(outf, "--------------------------------\n");
1726 hostcount = remote_errors = 0;
1728 for(dp = origq.head; dp != NULL; dp = dp->next) {
1730 if(hostp->up == HOST_READY && dp->todo == 1) {
1740 "Client check: %d host%s checked in %s seconds, %d problem%s found\n",
1741 hostcount, (hostcount == 1) ? "" : "s",
1742 walltime_str(curclock()),
1743 remote_errors, (remote_errors == 1) ? "" : "s");
1747 amfree(config_name);
1749 malloc_size_2 = malloc_inuse(&malloc_hist_2);
1751 if(malloc_size_1 != malloc_size_2) {
1752 malloc_list(fd, malloc_hist_1, malloc_hist_2);
1755 exit(userbad || remote_errors > 0);
1764 security_handle_t * sech)
1774 hostp = (am_host_t *)datap;
1775 hostp->up = HOST_READY;
1779 "WARNING: %s: selfcheck request failed: %s\n", hostp->hostname,
1780 security_geterror(sech));
1782 hostp->up = HOST_DONE;
1787 fprintf(errf, "got response from %s:\n----\n%s----\n\n",
1788 hostp->hostname, pkt->body);
1795 skip_quoted_line(s, ch);
1796 if (s[-2] == '\n') {
1800 if(strncmp_const(line, "OPTIONS ") == 0) {
1802 t = strstr(line, "features=");
1803 if(t != NULL && (isspace((int)t[-1]) || t[-1] == ';')) {
1804 t += SIZEOF("features=")-1;
1805 am_release_feature_set(hostp->features);
1806 if((hostp->features = am_string_to_feature(t)) == NULL) {
1807 fprintf(outf, "ERROR: %s: bad features value: %s\n",
1808 hostp->hostname, line);
1815 if(strncmp_const(line, "OK ") == 0) {
1820 if(strncmp_const_skip(line, "ERROR ", t, tch) == 0) {
1821 skip_whitespace(t, tch);
1823 * If the "error" is that the "noop" service is unknown, it
1824 * just means the client is "old" (does not support the service).
1825 * We can ignore this.
1827 if(!((hostp->features == NULL) && (pkt->type == P_NAK)
1828 && ((strcmp(t - 1, "unknown service: noop") == 0)
1829 || (strcmp(t - 1, "noop: invalid service") == 0)))) {
1830 fprintf(outf, "ERROR: %s%s: %s\n",
1831 (pkt->type == P_NAK) ? "NAK " : "",
1835 hostp->up = HOST_DONE;
1840 fprintf(outf, "ERROR: %s: unknown response: %s\n",
1841 hostp->hostname, line);
1843 hostp->up = HOST_DONE;
1845 if(hostp->up == HOST_READY && hostp->features == NULL) {
1847 * The client does not support the features list, so give it an
1850 dbprintf(("%s: no feature set from host %s\n",
1851 debug_prefix_time(NULL), hostp->hostname));
1852 hostp->features = am_set_default_feature_set();
1854 for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1855 if(dp->up == DISK_ACTIVE) {
1860 if(hostp->up == HOST_DONE)
1861 security_close_connection(sech, hostp->hostname);