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

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