source: trunk/packs/maint/os-checkfiles.c @ 16619

Revision 16619, 17.1 KB checked in by rbasch, 23 years ago (diff)
Do not nuke a mismatched target file if the master file cannot be opened, or has the wrong size. Make both such errors fatal -- the likely cause is a problem with the stats file and/or master tree, so they should not be trusted further.
Line 
1/* Copyright 1999 by the Massachusetts Institute of Technology.
2 *
3 * Permission to use, copy, modify, and distribute this
4 * software and its documentation for any purpose and without
5 * fee is hereby granted, provided that the above copyright
6 * notice appear in all copies and that both that copyright
7 * notice and this permission notice appear in supporting
8 * documentation, and that the name of M.I.T. not be used in
9 * advertising or publicity pertaining to distribution of the
10 * software without specific, written prior permission.
11 * M.I.T. makes no representations about the suitability of
12 * this software for any purpose.  It is provided "as is"
13 * without express or implied warranty.
14 */
15
16/* This program checks a list of files against stat information
17 * as produced by the os-statfiles program, and recreates regular
18 * files, symlinks, and directories whose stat information does
19 * not match the given data.  Files are recreated by copying from
20 * a given root directory, presumably an Athena /os hierarchy.
21 */
22
23static const char rcsid[] = "$Id: os-checkfiles.c,v 1.4 2001-09-11 21:26:49 rbasch Exp $";
24
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include <time.h>
29#include <unistd.h>
30#include <sys/types.h>
31#include <sys/stat.h>
32#include <dirent.h>
33#include <limits.h>
34#include <fcntl.h>
35#include <errno.h>
36#include "array.h"
37
38#define MAXLINE ((PATH_MAX * 2) + 256)
39
40/* Bit mask for chmod() */
41#define MODEMASK (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)
42
43char *progname;
44
45char *osroot = "/os";
46Array *exceptlist = NULL;
47int noop = 0;
48int verbose = 0;
49
50void usage();
51
52void do_file(const char *path, const struct stat *statp, mode_t mode,
53             off_t size, uid_t uid, gid_t gid, int flags, time_t since);
54
55void do_symlink(const char *from, const struct stat *statp, const char *to);
56
57void do_hardlink(const char *path, const struct stat *statp, const char *to);
58
59void do_directory(const char *path, const struct stat *statp, mode_t mode,
60                  uid_t uid, gid_t gid);
61
62void nuke(const char *path, const struct stat *statp);
63
64void nukedir(const char *path);
65
66void copyfile(const char *from, const char *to, const struct stat *to_statp,
67              off_t size);
68
69void make_parent(const char *path);
70
71void set_stat(const char *path, mode_t mode, uid_t uid, gid_t gid);
72
73Array *make_list(const char *file);
74
75int in_list(const Array *list, const char *what);
76
77int compare_string(const void *p1, const void *p2);
78
79char *estrdup(const char *s);
80
81
82void usage()
83{
84  fprintf(stderr, "Usage: %s [<options>] <statfile>\n", progname);
85  fprintf(stderr, "Options:\n");
86  fprintf(stderr, "        -n               No-op mode\n");
87  fprintf(stderr, "        -o <osroot>      OS root, default is /os\n");
88  fprintf(stderr, "        -r <target>      Target root, default is /\n");
89  fprintf(stderr, "        -t <file>        Timestamp file\n");
90  fprintf(stderr, "        -v               Verbose output mode\n");
91  fprintf(stderr, "        -x <file>        File containing exception list\n");
92  exit(1);
93}
94
95int main(int argc, char **argv)
96{
97  const char *statfile = NULL;
98  const char *timefile = NULL;
99  const char *exceptfile = NULL;
100  const char *target = "/";
101  FILE *statfp;
102  char inbuf[MAXLINE];
103  char *path;
104  char linkpath[PATH_MAX+1];
105  struct stat sb, *statp;
106  unsigned long long mode, uid, gid, size;
107  int c, len, flags;
108  time_t since;
109
110  progname = argv[0];
111
112  while ((c = getopt(argc, argv, "no:r:t:vx:")) != EOF)
113    {
114      switch(c)
115        {
116        case 'n':
117          noop = 1;
118          break;
119        case 'o':
120          osroot = optarg;
121          break;
122        case 'r':
123          target = optarg;
124          break;
125        case 't':
126          timefile = optarg;
127          break;
128        case 'v':
129          verbose = 1;
130          break;
131        case 'x':
132          exceptfile = optarg;
133          break;
134        case '?':
135          usage();
136          break;
137        }
138    }
139
140  if (optind + 1 != argc)
141    usage();
142
143  statfile = argv[optind++];
144  statfp = fopen(statfile, "r");
145  if (statfp == NULL)
146    {
147      fprintf(stderr, "%s: Cannot open %s: %s\n", progname, statfile,
148              strerror(errno));
149      exit(1);
150    }
151
152  if (stat(osroot, &sb) != 0 || !S_ISDIR(sb.st_mode))
153    {
154      fprintf(stderr, "%s: Invalid operating system root %s\n",
155              progname, osroot);
156      exit(1);
157    }
158
159  /* If given a timestamp file against which to check, record its
160   * modification time, otherwise just use the current time.
161   */
162  if (timefile != NULL)
163    {
164      if (stat(timefile, &sb) != 0)
165        {
166          fprintf(stderr, "%s: Cannot stat %s: %s\n",
167                  progname, timefile, strerror(errno));
168          exit(1);
169        }
170      since = sb.st_mtime;
171    }
172  else
173    time(&since);
174
175  if (exceptfile != NULL)
176    exceptlist = make_list(exceptfile);
177
178  /* Chdir to the target root. */
179  if (chdir(target) != 0)
180    {
181      fprintf(stderr, "%s: Cannot chdir to %s: %s\n",
182              progname, target, strerror(errno));
183      exit(1);
184    }
185
186  /* Main loop -- read entries from the stat file. */
187  while (fgets(inbuf, sizeof(inbuf), statfp) != NULL)
188    {
189      len = strlen(inbuf);
190      if (inbuf[len-1] != '\n')
191        {
192          fprintf(stderr, "%s: Invalid entry '%s', aborting\n",
193                  progname, inbuf);
194          exit(1);
195        }
196      inbuf[--len] = '\0';
197
198      /* Get the entry path, always the last field in the line.
199       * Skip it if it is in the exception list.
200       * Note now whether we can stat it.
201       */
202      for (path = &inbuf[len-1]; path > &inbuf[0] && *path != ' '; --path);
203      if (path <= &inbuf[0])
204        {
205          fprintf(stderr, "%s: Invalid entry '%s', aborting\n",
206                  progname, inbuf);
207          exit(1);
208        }
209      ++path;
210
211      if (in_list(exceptlist, path))
212        {
213          if (verbose)
214            printf("Skipping exception %s\n", path);
215          continue;
216        }
217
218      statp = (lstat(path, &sb) == 0 ? &sb : NULL);
219
220      switch (inbuf[0])
221        {
222        case 'l':
223          if (sscanf(&inbuf[2], "%s", linkpath) != 1)
224            {
225              fprintf(stderr,
226                      "%s: Invalid symlink entry '%s', aborting\n",
227                      progname, inbuf);
228              exit(1);
229            }
230          do_symlink(path, statp, linkpath);
231          break;
232        case 'f':
233          if (sscanf(&inbuf[2], "%llo %llu %llu %llu %x", &mode, &size,
234                     &uid, &gid, &flags) != 5)
235            {
236              fprintf(stderr,
237                      "%s: Invalid file entry '%s', aborting\n",
238                      progname, inbuf);
239              exit(1);
240            }
241          do_file(path, statp, mode, size, uid, gid, flags, since);
242          break;
243        case 'h':
244          if (sscanf(&inbuf[2], "%s", linkpath) != 1)
245            {
246              fprintf(stderr,
247                      "%s: Invalid hard link entry '%s', aborting\n",
248                      progname, inbuf);
249              exit(1);
250            }
251          do_hardlink(path, statp, linkpath);
252          break;
253        case 'd':
254          if (sscanf(&inbuf[2], "%llo %llu %llu", &mode, &uid, &gid) != 3)
255            {
256              fprintf(stderr,
257                      "%s: Invalid directory entry '%s', aborting\n",
258                      progname, inbuf);
259              exit(1);
260            }
261          do_directory(path, statp, mode, uid, gid);
262          break;
263        default:
264          fprintf(stderr, "%s: Unrecognized type '%c', aborting.\n",
265                  progname, inbuf[0]);
266          exit(1);
267        }
268    }
269  exit(0);
270}
271
272void do_file(const char *path, const struct stat *statp, mode_t mode,
273             off_t size, uid_t uid, gid_t gid, int flags, time_t since)
274{
275  struct stat sb;
276  char ospath[PATH_MAX+1];
277
278  if (statp == NULL
279      || !S_ISREG(statp->st_mode)
280      || statp->st_mtime > since
281      || statp->st_size != size)
282    {
283      printf("Replace regular file %s\n", path);
284      if (!noop)
285        {
286          /* Copy the file into place. */
287          sprintf(ospath, "%s/%s", osroot, path);
288          copyfile(ospath, path, statp, size);
289
290          /* Make sure new copy has expected size. */
291          if (lstat(path, &sb) != 0)
292            {
293              fprintf(stderr, "%s: Cannot stat %s after copy: %s\n",
294                      progname, path, strerror(errno));
295              exit(1);
296            }
297          if (sb.st_size != size)
298            {
299              fprintf(stderr,
300                      "%s: Size mismatch after copy of %s (%llu, %llu)\n",
301                      progname, path, (unsigned long long) size,
302                      (unsigned long long) sb.st_size);
303              exit(1);
304            }
305
306          set_stat(path, mode, uid, gid);
307        }
308    }
309
310  else if (statp->st_mode != mode
311           || statp->st_uid != uid
312           || statp->st_gid != gid)
313    {
314      /* Here to fix file permissions and ownership. */
315      printf("Set permission/ownership of regular file %s\n", path);
316      if (!noop)
317        set_stat(path, mode, uid, gid);
318    }
319
320  return;
321}
322
323void do_symlink(const char *from, const struct stat *statp, const char *to)
324{
325  char linkbuf[PATH_MAX+1];
326  int len, status;
327  int result = 0;
328
329  if (statp != NULL && S_ISLNK(statp->st_mode))
330    {
331      len = readlink(from, linkbuf, sizeof(linkbuf));
332      if (len > 0)
333        {
334          linkbuf[len] = '\0';
335          if (strcmp(linkbuf, to) == 0)
336            result = 1;
337        }
338    }
339  if (!result)
340    {
341      printf("Relink (symbolic) %s to %s\n", from, to);
342      if (!noop)
343        {
344          nuke(from, statp);
345          status = symlink(to, from);
346          if (status != 0 && (errno == ENOENT || errno == ENOTDIR))
347            {
348              /* The parent directory does not exist.  Create it and retry. */
349              make_parent(from);
350              status = symlink(to, from);
351            }
352          if (status != 0)
353            {
354              fprintf(stderr, "%s: Cannot create symlink %s: %s\n",
355                      progname, from, strerror(errno));
356              exit(1);
357            }
358        }
359    }
360}
361
362/* Handle a hard link. */
363void do_hardlink(const char *path, const struct stat *statp, const char *to)
364{
365  struct stat to_stat;
366  int status;
367
368  if (lstat(to, &to_stat) != 0)
369    {
370      fprintf(stderr,
371              "%s: Warning: %s (link to %s) does not exist\n",
372              progname, to, path);
373      return;
374    }
375  if (S_ISDIR(to_stat.st_mode))
376    {
377      fprintf(stderr,
378              "%s: Warning: %s (link to %s) is a directory\n",
379              progname, to, path);
380      return;
381    }
382
383  if (statp == NULL
384      || statp->st_ino != to_stat.st_ino
385      || statp->st_dev != to_stat.st_dev)
386    {
387      printf("Relink (hard) %s to %s\n", path, to);
388      if (!noop)
389        {
390          if (statp != NULL)
391            nuke(path, statp);
392          status = link(to, path);
393          if (status != 0 && (errno == ENOENT || errno == ENOTDIR))
394            {
395              /* The parent directory does not exist.  Create it and retry. */
396              make_parent(path);
397              status = link(to, path);
398            }
399          if (status != 0)
400            {
401              fprintf(stderr, "%s: Cannot create hard link %s to %s: %s\n",
402                      progname, path, to, strerror(errno));
403              exit(1);
404            }
405        }
406    }
407}
408
409void do_directory(const char *path, const struct stat *statp, mode_t mode,
410                  uid_t uid, gid_t gid)
411{
412  int status;
413
414  if (statp == NULL || !S_ISDIR(statp->st_mode))
415    {
416      /* Path doesn't exist, or is not a directory. Recreate it. */
417      printf("Recreate directory %s\n", path);
418      if (!noop)
419        {
420          if (statp != NULL)
421            nuke(path, statp);
422          status = mkdir(path, S_IRWXU);
423          if (status != 0 && (errno == ENOENT || errno == ENOTDIR))
424            {
425              /* The parent directory does not exist.  Create it and retry. */
426              make_parent(path);
427              status = mkdir(path, S_IRWXU);
428            }
429          if (status != 0)
430            {
431              fprintf(stderr, "%s: Cannot mkdir %s: %s\n",
432                      progname, path, strerror(errno));
433              exit(1);
434            }
435        }
436    }
437  else if (statp->st_mode == mode
438           && statp->st_uid == uid
439           && statp->st_gid == gid)
440    return;
441
442  /* Here when path is a directory, but needs permissions and ownership
443   * to be set.
444   */
445  printf("Set permission/ownership of directory %s\n", path);
446  if (!noop)
447    set_stat(path, mode, uid, gid);
448  return;
449}
450
451void nuke(const char *path, const struct stat *statp)
452{
453  if (statp == NULL)
454    return;
455  if (S_ISDIR(statp->st_mode))
456    nukedir(path);
457  else if (unlink(path) != 0)
458    {
459      fprintf(stderr, "%s: Cannot unlink %s: %s\n",
460              progname, path, strerror(errno));
461      exit(1);
462    }
463  return;
464}
465
466
467void nukedir(const char *path)
468{
469  DIR *dirp;
470  struct dirent *dp;
471  char pathbuf[PATH_MAX+1];
472  struct stat sb;
473
474  dirp = opendir(path);
475  if (dirp == NULL)
476    {
477      fprintf(stderr, "%s: Cannot open directory %s: %s\n",
478              progname, path, strerror(errno));
479      exit(1);
480    }
481  while ((dp = readdir(dirp)) != NULL)
482    {
483      if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
484        continue;
485      sprintf(pathbuf, "%s/%s", path, dp->d_name);
486      if (lstat(pathbuf, &sb) != 0)
487        {
488          fprintf(stderr, "%s: Cannot stat %s: %s\n",
489                  progname, pathbuf, strerror(errno));
490          continue;
491        }
492      /* Recurse if we encounter a subdirectory. */
493      if (S_ISDIR(sb.st_mode))
494        nukedir(pathbuf);
495      else if (unlink(pathbuf) != 0)
496        {
497          fprintf(stderr, "%s: Cannot unlink %s: %s\n",
498                  progname, pathbuf, strerror(errno));
499          exit(1);
500        }
501    }
502  closedir(dirp);
503  if (rmdir(path) != 0)
504    {
505      fprintf(stderr, "%s: Cannot rmdir %s: %s\n",
506              progname, path, strerror(errno));
507      exit(1);
508    }
509  return;
510}
511
512/* Copy a file.  If the target exists, it will be nuked before copying
513 * the file into place.
514 */
515void copyfile(const char *from, const char *to, const struct stat *to_statp,
516              off_t size)
517{
518  char buf[4096];
519  FILE *infp, *outfp;
520  int n, fd, status;
521  struct stat from_stat;
522
523  /* Open and check the input file. */
524  infp = fopen(from, "r");
525  if (infp == NULL)
526    {
527      fprintf(stderr, "%s: Cannot open %s: %s\n",
528              progname, from, strerror(errno));
529      exit(1);
530    }
531  if (fstat(fileno(infp), &from_stat) == -1)
532    {
533      fprintf(stderr, "%s: Cannot stat %s: %s\n",
534              progname, from, strerror(errno));
535      fclose(infp);
536      exit(1);
537    }
538  if (from_stat.st_size != size)
539    {
540      fprintf(stderr, "%s: Size mismatch for %s (%llu, %llu)\n",
541              progname, from, (unsigned long long) size,
542              (unsigned long long) from_stat.st_size);
543      fclose(infp);
544      exit(1);
545    }
546
547  /* Remove the existing path, if any. */
548  nuke(to, to_statp);
549
550  fd = open(to, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
551  if (fd < 0 && (errno == ENOENT || errno == ENOTDIR))
552    {
553      /* The parent directory does not exist.  Create it and retry. */
554      make_parent(to);
555      fd = open(to, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
556    }
557  if (fd < 0)
558    {
559      fprintf(stderr, "%s: Cannot create %s: %s\n",
560              progname, to, strerror(errno));
561      fclose(infp);
562      exit(1);
563    }
564  outfp = fdopen(fd, "w");
565  if (outfp == NULL)
566    {
567      fprintf(stderr, "%s: fdopen failed for %s: %s\n",
568              progname, to, strerror(errno));
569      fclose(infp);
570      close(fd);
571      exit(1);
572    }
573
574  while ((n = fread(buf, 1, sizeof(buf), infp)) > 0)
575    {
576      if (fwrite(buf, 1, n, outfp) != n)
577        {
578          fprintf(stderr, "%s: Write failed for %s: %s\n",
579                  progname, to, strerror(errno));
580          fclose(infp);
581          fclose(outfp);
582          exit(1);
583        }
584    }
585  status = ferror(infp);
586  if ((fclose(infp) != 0) || (status != 0))
587    {
588      fprintf(stderr, "%s: Error reading %s: %s\n",
589              progname, from, strerror(errno));
590      fclose(outfp);
591      exit(1);
592    }
593  status = ferror(outfp);
594  if ((fclose(outfp) != 0) || (status != 0))
595    {
596      fprintf(stderr, "%s: Error writing %s: %s\n",
597              progname, to, strerror(errno));
598      exit(1);
599    }
600  return;
601}
602
603/* Ensure that the parent directory of the given path exists, by
604 * creating all components of the path prefix, as necessary.
605 * If any path component exists but is not a directory, it is
606 * removed.
607 * Directories are created mode 755, owned by the current user.
608 */
609void make_parent(const char *path)
610{
611  char *p, *path_buf;
612  struct stat sb;
613  mode_t saved_umask;
614
615  /* Make a copy of the path string so we can modify it. */
616  path_buf = estrdup(path);
617
618  saved_umask = umask(0);
619
620  p = path_buf;
621  while (*p == '/')
622    p++;
623
624  /* Find the first component in the path prefix that does not exist. */
625  while ((p = strchr(p, '/')) != NULL)
626    {
627      *p = '\0';
628      if (stat(path_buf, &sb) != 0)
629        break;
630      if (!S_ISDIR(sb.st_mode))
631        {
632          /* Component exists, but is not a directory.  Nuke it. */
633          printf("Remove non-directory path prefix %s\n", path_buf);
634          unlink(path_buf);
635          break;
636        }
637      *p++ = '/';
638    }
639
640  /* The remaining path components do not exist -- create them. */
641  for ( ; p != NULL; p = strchr(p, '/'))
642    {
643      *p = '\0';
644      printf("Create path prefix %s\n", path_buf);
645      if (mkdir(path_buf, 0755) != 0)
646        {
647          fprintf(stderr, "%s: Cannot mkdir %s: %s\n",
648                  progname, path_buf, strerror(errno));
649          exit(1);
650        }
651      *p++ = '/';
652    }
653
654  free(path_buf);
655  umask(saved_umask);
656  return;
657}
658
659void set_stat(const char *path, mode_t mode, uid_t uid, gid_t gid)
660{
661  if (chmod(path, mode & MODEMASK) != 0)
662    {
663      fprintf(stderr, "%s: Cannot chmod %s: %s\n",
664              progname, path, strerror(errno));
665      exit(1);
666    }
667  if (chown(path, uid, gid) != 0)
668    {
669      fprintf(stderr, "%s: Cannot chown %s: %s\n",
670              progname, path, strerror(errno));
671      exit(1);
672    }
673}
674
675/* Read a file into an array, one element per line. */
676Array *make_list(const char *file)
677{
678  FILE *f;
679  Array *list;
680  char buffer[MAXLINE];
681
682  f = fopen(file, "r");
683  if (f == NULL)
684    return NULL;
685
686  list = array_new();
687  while (fgets(buffer, sizeof(buffer), f))
688    {
689      buffer[strlen(buffer) - 1] = '\0';
690      array_add(list, estrdup(buffer));
691    }
692
693  fclose(f);
694  array_sort(list, compare_string);
695  return list;
696}
697
698/* Check if the given string is an element in the given array.
699 * Returns 1 if found in the array, 0 otherwise.
700 */
701int in_list(const Array *list, const char *what)
702{
703  if (list == NULL)
704    return 0;
705
706  return (array_search(list, what) != NULL);
707}
708
709int compare_string(const void *p1, const void *p2)
710{
711  return strcmp(*((char **)p1), *((char **)p2));
712}
713
714char *estrdup(const char *s)
715{
716  char *s2;
717
718  s2 = strdup(s);
719  if (s2 == NULL)
720    {
721      fprintf(stderr, "%s: Out of memory\n", progname);
722      exit(1);
723    }
724  return s2;
725}
Note: See TracBrowser for help on using the repository browser.