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