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

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