added exclude-list support
[util-vserver.git] / util-vserver / src / vunify.c
1 // $Id$    --*- c -*--
2
3 // Copyright (C) 2003,2004 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
4 //  
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; version 2 of the License.
8 //  
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //  
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18
19 #ifdef HAVE_CONFIG_H
20 #  include <config.h>
21 #endif
22
23 #include "vunify.h"
24 #include "wrappers-dirent.h"
25 #include "vunify-operations.h"
26 #include "util.h"
27
28 #include <lib/vserver.h>
29
30 #include <getopt.h>
31 #include <dirent.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <unistd.h>
35 #include <stdbool.h>
36
37 int     wrapper_exit_code = 1;
38
39
40 static struct WalkdownInfo      global_info;
41 static struct Operations        operations;
42
43
44 #define CMD_HELP                0x8000
45 #define CMD_VERSION             0x8001
46 #define CMD_MANUALLY            0x8002
47
48 struct option const
49 CMDLINE_OPTIONS[] = {
50   { "help",     no_argument,  0, CMD_HELP },
51   { "version",  no_argument,  0, CMD_VERSION },
52   { "manually", no_argument,  0, CMD_MANUALLY },
53   { 0,0,0,0 }
54 };
55
56 struct Arguments {
57     enum {mdMANUALLY, mdVSERVER}        mode;
58     bool                                do_revert;
59     bool                                do_dry_run;
60     unsigned int                        verbosity;
61     bool                                local_fs;
62     bool                                do_renew;
63 };
64
65 struct Arguments const *                global_args;
66
67 static void
68 showHelp(int fd, char const *cmd, int res)
69 {
70   WRITE_MSG(fd, "Usage:\n  ");
71   WRITE_STR(fd, cmd);
72   WRITE_MSG(fd,
73             " [-Rnv] <vserver>\n    or\n  ");
74   WRITE_STR(fd, cmd);
75   WRITE_MSG(fd,
76             " --manually [-Rnvx] [--] <path> <excludelist> [<path> <excludelist>]+\n\n"
77             "  --manually      ...  unify generic paths; excludelists must be generated\n"
78             "                       manually\n"
79             "  -R              ...  revert operation; deunify files\n"
80             "  -n              ...  do not modify anything; just show what there will be\n"
81             "                       done (in combination with '-v')\n"
82             "  -v              ...  verbose mode\n"
83             "  -x              ...  do not cross filesystems; this is valid in manual\n"
84             "                       mode only and will be ignored for vserver unification\n"
85             "Please report bugs to " PACKAGE_BUGREPORT "\n");
86 #if 0       
87             "  -C              ...  use cached excludelists; usually they will be\n"
88             "                       regenerated after package installation to reflect e.g.\n"
89             "                       added/removed configuration files\n\n"
90 #endif      
91   exit(res);
92 }
93
94 static void
95 showVersion()
96 {
97   WRITE_MSG(1,
98             "vunify " VERSION " -- unifies vservers and/or directories\n"
99             "This program is part of " PACKAGE_STRING "\n\n"
100             "Copyright (C) 2003,2004 Enrico Scholz\n"
101             VERSION_COPYRIGHT_DISCLAIMER);
102   exit(0);
103 }
104
105
106 // Returns 'false' iff one of the files is not existing, or of the files are different/not unifyable
107 static bool
108 checkFstat(struct MatchList const * const mlist,
109            PathInfo const * const  basename,
110            PathInfo const * const  path,
111            struct stat const ** const result, struct stat * const result_buf)
112 {
113   assert(basename->d[0] != '/');
114
115   if (*result==0) {
116     // local file does not exist... strange
117     // TODO: message
118     if (lstat(basename->d, result_buf)==-1) return false;
119     *result = result_buf;
120   }
121
122   assert(*result!=0);
123   
124   {
125     struct stat         src_fstat;
126     PathInfo            src_path = mlist->root;
127     char                src_path_buf[ENSC_PI_APPSZ(src_path, *path)];
128
129     PathInfo_append(&src_path, path, src_path_buf);
130
131     // source file does not exist
132     if (lstat(src_path.d, &src_fstat)==-1) return false;
133
134     // these are directories
135     if (S_ISDIR((*result)->st_mode) && S_ISDIR(src_fstat.st_mode)) return true;
136
137     // both files are different, so return false
138     if (!(operations.compare)(*result, &src_fstat)) return false;
139   }
140
141   // these are the same files
142   return true;
143 }
144
145 static struct MatchList const *
146 checkDirEntry(PathInfo const *path,
147               PathInfo const *d_path, bool *is_dir, struct stat *f_stat)
148 {
149   struct WalkdownInfo const * const     info     = &global_info;
150   struct MatchList const *              mlist;
151   struct stat const *                   cache_stat = 0;
152
153   // Check if it is in the exclude/include list of the destination vserver and
154   // abort when it is not matching an allowed entry
155   if (!MatchList_compare(&info->dst_list, path->d)) return 0;
156
157   // Now, go through the reference vservers and do the lightweigt list-check
158   // first and compare then the fstat's.
159   for (mlist=info->src_lists.v; mlist<info->src_lists.v+info->src_lists.l; ++mlist) {
160     if (MatchList_compare(mlist, path->d) &&
161         checkFstat(mlist, d_path, path, &cache_stat, f_stat)) {
162
163       // Failed the check or is it a symlink which can not be handled
164       if (cache_stat==0 || S_ISLNK(f_stat->st_mode)) return 0;
165       
166       *is_dir = S_ISDIR(f_stat->st_mode);
167       return mlist;
168     }
169   }
170
171   // No luck...
172   return 0;
173 }
174
175 static bool
176 updateSkipDepth(PathInfo const *path, bool walk_down)
177 {
178   struct WalkdownInfo const * const     info   = &global_info;
179   struct MatchList *                    mlist;
180   bool                                  result = false;
181
182   for (mlist=info->src_lists.v; mlist<info->src_lists.v+info->src_lists.l; ++mlist) {
183     // The easy way... this path is being skipped already
184     if (mlist->skip_depth>0) {
185       if (walk_down) ++mlist->skip_depth;
186       else           --mlist->skip_depth;
187       continue;
188     }
189     else if (walk_down) {
190       PathInfo          src_path = mlist->root;
191       char              src_path_buf[ENSC_PI_APPSZ(src_path, *path)];
192       struct stat       src_fstat;
193
194       PathInfo_append(&src_path, path, src_path_buf);
195
196       // when the file/dir exist, we have do go deeper.
197       // else skip it in deeper runs for *this* matchlist
198       if (lstat(src_path.d, &src_fstat)!=-1) result = true;
199       else                                   ++mlist->skip_depth;
200     }
201     else {
202       // TODO: warning
203     }
204   }
205
206   return result;
207 }
208
209 inline static void
210 EsafeChdir(char const *path, struct stat const *exp_stat)
211 {
212   FatalErrnoError(safeChdir(path, exp_stat)==-1, "safeChdir()");
213 }
214
215 static bool
216 doit(struct MatchList const *mlist, PathInfo const *src_path,
217      char const *dst_path)
218 {
219   PathInfo      path = mlist->root;
220   char          path_buf[ENSC_PI_APPSZ(path, *src_path)];
221
222   if (global_args->do_dry_run || global_args->verbosity>0) {
223     if (global_args->do_revert) WRITE_MSG(1, "deunifying '");
224     else                        WRITE_MSG(1, "unifying   '");
225
226     write(1, src_path->d, src_path->l);
227     WRITE_MSG(1, "'");
228
229     if (global_args->verbosity>2) {
230       WRITE_MSG(1, " (from ");
231       if (global_args->verbosity==2 && mlist->id.d)
232         write(1, mlist->id.d, mlist->id.l);
233       else
234         write(1, mlist->root.d, mlist->root.l);
235       WRITE_MSG(1, ")");
236     }
237     WRITE_MSG(1, "\n");
238   }
239   
240   PathInfo_append(&path, src_path, path_buf);
241   return (operations.doit)(path.d, dst_path);
242 }
243
244
245 static void
246 visitDirEntry(struct dirent const *ent)
247 {
248   bool                          is_dir;
249   struct MatchList const *      match;
250   struct stat                   f_stat;
251   char const *                  dirname  = ent->d_name;
252   PathInfo                      path     = global_info.state;
253   PathInfo                      d_path = {
254     .d = dirname,
255     .l = strlen(ent->d_name)
256   };
257   char                          path_buf[ENSC_PI_APPSZ(path, d_path)];
258   bool                          is_dotfile;
259
260   PathInfo_append(&path, &d_path, path_buf);
261
262   is_dotfile = (dirname[0]=='.' &&
263                 (dirname[1]=='\0' || (dirname[1]=='.' && dirname[2]=='\0')));
264   memset(&f_stat, 0, sizeof f_stat);
265   if (is_dotfile ||
266       (match=checkDirEntry(&path, &d_path, &is_dir, &f_stat))==0) {
267     bool        is_link = is_dotfile ? false : S_ISLNK(f_stat.st_mode);
268     
269     if (global_args->verbosity>1 &&
270         ((!is_dotfile && !is_link) ||
271          (global_args->verbosity>4 && is_dotfile) ||
272          (global_args->verbosity>4 && is_link)) ) {
273       WRITE_MSG(1, "  skipping '");
274       write(1, path.d, path.l);
275       WRITE_MSG(1, "'\n");
276     }
277     return;
278   }
279
280   if (is_dir) {
281     if (updateSkipDepth(&path, true)) {
282       visitDir(dirname, &f_stat);
283       updateSkipDepth(&path, false);
284     }
285   }
286   else if (!doit(match, &path, dirname)) {
287       // TODO: message
288   }
289 }
290
291 static void
292 visitDir(char const *name, struct stat const *expected_stat)
293 {
294   int           fd = Eopen(".", O_RDONLY, 0);
295   PathInfo      old_state = global_info.state;
296   PathInfo      rhs_path = {
297     .d = name,
298     .l = strlen(name)
299   };
300   char          new_path[ENSC_PI_APPSZ(global_info.state, rhs_path)];
301   DIR *         dir;
302
303   PathInfo_append(&global_info.state, &rhs_path, new_path);
304
305   if (expected_stat!=0)
306     EsafeChdir(name, expected_stat);
307   
308   dir = Eopendir(".");
309
310   for (;;) {
311     struct dirent               *ent = Ereaddir(dir);
312     if (ent==0) break;
313
314     visitDirEntry(ent);
315   }
316
317   Eclosedir(dir);
318
319   Efchdir(fd);
320   Eclose(fd);
321
322   global_info.state = old_state;
323 }
324
325 #include "vunify-init.ic"
326
327 static void
328 initModeManually(struct Arguments const UNUSED *args, int argc, char *argv[])
329 {
330   int           i, count=argc/2;
331   
332   if (argc%2) {
333     WRITE_MSG(2, "Odd number of (path,excludelist) arguments\n");
334     exit(1);
335   }
336
337   if (count<2) {
338     WRITE_MSG(2, "No reference path(s) given\n");
339     exit(1);
340   }
341
342   initMatchList(&global_info.dst_list, 0, argv[0], argv[1]);
343
344   --count;
345   global_info.src_lists.v = Emalloc(sizeof(struct MatchList) * count);
346   global_info.src_lists.l = count;
347
348   for (i=0; i<count; ++i)
349     initMatchList(global_info.src_lists.v+i, 0, argv[2 + i*2], argv[3 + i*2]);
350 }
351
352 static int
353 selectRefserver(struct dirent const *ent)
354 {
355   return strncmp(ent->d_name, "refserver.", 10)==0;
356 }
357
358 static void
359 initModeVserver(struct Arguments const UNUSED *args, int argc, char *argv[])
360 {
361   char const    *appdir;
362   int           cur_dir = Eopen(".", O_RDONLY, 0);
363   struct dirent **entries;
364   int           count, i;
365   
366   if (argc!=1) {
367     WRITE_MSG(2, "More than one vserver is not supported\n");
368     exit(1);
369   }
370
371   if (!initMatchListByVserver(&global_info.dst_list, argv[0], &appdir)) {
372     WRITE_MSG(2, "unification not configured for this vserver\n");
373     exit(1);
374   }
375
376   Echdir(appdir);
377   count = scandir(".", &entries, selectRefserver, alphasort);
378   if (count==-1) {
379     perror("scandir()");
380     exit(1);
381   }
382
383   if (count==0) {
384     WRITE_MSG(2, "no reference vserver configured\n");
385     exit(1);
386   }
387
388   global_info.src_lists.v = Emalloc(sizeof(struct MatchList) * count);
389   global_info.src_lists.l = count;
390   for (i=0; i<count; ++i) {
391     char const          *tmp   = entries[i]->d_name;
392     size_t              l      = strlen(tmp);
393     char                vname[sizeof("./") + l];
394
395     memcpy(vname,   "./", 2);
396     memcpy(vname+2, tmp,  l+1);
397     
398     if (!initMatchListByVserver(global_info.src_lists.v+i, vname, 0)) {
399       WRITE_MSG(2, "unification for reference vserver not configured\n");
400       exit(1);
401     }
402
403     free(entries[i]);
404   }
405   free(entries);
406   free(const_cast(char *)(appdir));
407
408   Efchdir(cur_dir);
409   Eclose(cur_dir);
410 }
411
412 int main(int argc, char *argv[])
413 {
414   struct Arguments      args = {
415     .mode               =  mdVSERVER,
416     .do_revert          =  false,
417     .do_dry_run         =  false,
418     .verbosity          =  0,
419     .local_fs           =  false,
420     .do_renew           =  true,
421   };
422
423   global_args = &args;
424   while (1) {
425     int         c = getopt_long(argc, argv, "Rnvcx",
426                                 CMDLINE_OPTIONS, 0);
427     if (c==-1) break;
428
429     switch (c) {
430       case CMD_HELP             :  showHelp(1, argv[0], 0);
431       case CMD_VERSION          :  showVersion();
432       case CMD_MANUALLY         :  args.mode = mdMANUALLY; break;
433       case 'R'                  :  args.do_revert  = true; break;
434       case 'n'                  :  args.do_dry_run = true; break;
435       case 'x'                  :  args.local_fs   = true; break;
436       //case 'C'                        :  args.do_renew   = false; break;
437       case 'v'                  :  ++args.verbosity; break;
438       default           :
439         WRITE_MSG(2, "Try '");
440         WRITE_STR(2, argv[0]);
441         WRITE_MSG(2, " --help\" for more information.\n");
442         return EXIT_FAILURE;
443         break;
444     }
445   }
446
447   if (argc==optind) {
448     WRITE_MSG(2, "No directory/vserver given\n");
449     return EXIT_FAILURE;
450   }
451
452   Operations_init(&operations,
453                   args.do_revert ? opDEUNIFY : opUNIFY,
454                   args.do_dry_run);
455
456   switch (args.mode) {
457     case mdMANUALLY     :  initModeManually(&args, argc-optind, argv+optind); break;
458     case mdVSERVER      :  initModeVserver(&args,  argc-optind, argv+optind); break;
459     default             :  assert(false); return EXIT_FAILURE;
460   }
461     
462   global_info.state.d = "";
463   global_info.state.l = 0;
464
465
466   Echdir(global_info.dst_list.root.d);
467   visitDir("/", 0);
468
469 #ifndef NDEBUG
470   {
471     size_t              i;
472     MatchList_destroy(&global_info.dst_list);
473     for (i=0; i<global_info.src_lists.l; ++i)
474       MatchList_destroy(global_info.src_lists.v+i);
475
476     free(global_info.src_lists.v);
477   }
478 #endif
479 }