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