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