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"
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.ic"
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 result, struct stat * const result_buf)
101 {
102   assert(basename->d[0] != '/');
103
104   if (*result==0) {
105     // local file does not exist... strange
106     // TODO: message
107     skip_reason.r = rsFSTAT;
108     if (lstat(basename->d, result_buf)==-1) return false;
109     *result = result_buf;
110   }
111
112   assert(*result!=0);
113   
114   {
115     struct stat         src_fstat;
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((*result)->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(*result, &src_fstat)) ||
131         ( global_args->do_revert && !compareDeUnify(*result, &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, struct stat *f_stat)
142 {
143   struct WalkdownInfo const * const     info     = &global_info;
144   struct MatchList const *              mlist;
145   struct stat const *                   cache_stat;
146
147   // Check if it is in the exclude/include list of the destination vserver and
148   // abort when it is not matching an allowed entry
149   skip_reason.r      = rsEXCL_DST;
150   skip_reason.d.list = &info->dst_list;
151   if (!MatchList_compare(&info->dst_list, path->d)) return 0;
152
153   // Now, go through the reference vservers and do the lightweigt list-check
154   // first and compare then the fstat's.
155   for (mlist=info->src_lists.v; mlist<info->src_lists.v+info->src_lists.l; ++mlist) {
156     cache_stat = 0;
157     skip_reason.r      = rsEXCL_SRC;
158     skip_reason.d.list = mlist;
159     if (MatchList_compare(mlist, path->d) &&
160         checkFstat(mlist, d_path, path, &cache_stat, f_stat)) {
161
162       // Failed the check or is it a symlink which can not be handled
163       if (cache_stat==0) return 0;
164
165       skip_reason.r = rsSYMLINK;
166       if (S_ISLNK(f_stat->st_mode)) return 0;
167       
168       *is_dir = S_ISDIR(f_stat->st_mode);
169       return mlist;
170     }
171     else if (cache_stat!=0 && !global_args->do_revert &&
172              skip_reason.r == rsDIFFERENT &&
173              compareDeUnify(cache_stat, f_stat)) {
174       skip_reason.r      = rsUNIFIED;
175       skip_reason.d.list = mlist;
176       return 0;
177     }
178   }
179
180   // No luck...
181   return 0;
182 }
183
184 static bool
185 updateSkipDepth(PathInfo const *path, bool walk_down)
186 {
187   struct WalkdownInfo const * const     info   = &global_info;
188   struct MatchList *                    mlist;
189   bool                                  result = false;
190
191   for (mlist=info->src_lists.v; mlist<info->src_lists.v+info->src_lists.l; ++mlist) {
192     // The easy way... this path is being skipped already
193     if (mlist->skip_depth>0) {
194       if (walk_down) ++mlist->skip_depth;
195       else           --mlist->skip_depth;
196       continue;
197     }
198     else if (walk_down) {
199       PathInfo          src_path = mlist->root;
200       char              src_path_buf[ENSC_PI_APPSZ(src_path, *path)];
201       struct stat       src_fstat;
202
203       PathInfo_append(&src_path, path, src_path_buf);
204
205       // when the file/dir exist, we have do go deeper.
206       // else skip it in deeper runs for *this* matchlist
207       if (lstat(src_path.d, &src_fstat)!=-1) result = true;
208       else                                   ++mlist->skip_depth;
209     }
210     else {
211       // TODO: warning
212     }
213   }
214
215   return result;
216 }
217
218 inline static void
219 EsafeChdir(char const *path, struct stat const *exp_stat)
220 {
221   FatalErrnoError(safeChdir(path, exp_stat)==-1, "safeChdir()");
222 }
223
224 #include "vunify-doit.ic"
225
226 static bool
227 doit(struct MatchList const *mlist, PathInfo const *src_path,
228      char const *dst_path)
229 {
230   PathInfo      path = mlist->root;
231   char          path_buf[ENSC_PI_APPSZ(path, *src_path)];
232
233   if (global_args->do_dry_run || global_args->verbosity>0) {
234     if (global_args->do_revert) WRITE_MSG(1, "deunifying '");
235     else                        WRITE_MSG(1, "unifying   '");
236
237     write(1, src_path->d, src_path->l);
238     WRITE_MSG(1, "'");
239
240     if (global_args->verbosity>2) {
241       WRITE_MSG(1, " (from ");
242       if (global_args->verbosity==2 && mlist->id.d)
243         write(1, mlist->id.d, mlist->id.l);
244       else
245         write(1, mlist->root.d, mlist->root.l);
246       WRITE_MSG(1, ")");
247     }
248     WRITE_MSG(1, "\n");
249   }
250   
251   PathInfo_append(&path, src_path, path_buf);
252   return (global_args->do_dry_run ||
253           (!global_args->do_revert && doitUnify(path.d, dst_path)) ||
254           ( global_args->do_revert && doitDeUnify(path.d, dst_path)));
255 }
256
257 static void
258 printListId(struct MatchList const *l)
259 {
260   if (l->id.l>0) {
261     WRITE_MSG(1, "'");
262     write(1, l->id.d, l->id.l);
263     WRITE_MSG(1, "'");
264   }
265   else if (l->root.l>0) {
266     write(1, l->root.d, l->root.l);
267   }
268   else
269     WRITE_MSG(1, "???");
270 }
271
272 static void
273 printSkipReason()
274 {
275   WRITE_MSG(1, " (");
276   switch (skip_reason.r) {
277     case rsDOTFILE      :  WRITE_MSG(1, "dotfile"); break;
278     case rsEXCL_DST     :
279     case rsEXCL_SRC     :
280       WRITE_MSG(1, "excluded by ");
281       printListId(skip_reason.d.list);
282       break;
283     case rsFSTAT        :  WRITE_MSG(1, "fstat error"); break;
284     case rsNOEXISTS     :  WRITE_MSG(1, "does not exists in refserver(s)"); break;
285     case rsSYMLINK      :  WRITE_MSG(1, "symlink"); break;
286     case rsUNIFIED      :  WRITE_MSG(1, "already unified"); break;
287     case rsDIFFERENT    :  WRITE_MSG(1, "different"); break;
288     default             :  assert(false); abort();
289   }
290   WRITE_MSG(1, ")");
291 }
292
293 static void
294 visitDirEntry(struct dirent const *ent)
295 {
296   bool                          is_dir;
297   struct MatchList const *      match;
298   struct stat                   f_stat;
299   char const *                  dirname  = ent->d_name;
300   PathInfo                      path     = global_info.state;
301   PathInfo                      d_path = {
302     .d = dirname,
303     .l = strlen(ent->d_name)
304   };
305   char                          path_buf[ENSC_PI_APPSZ(path, d_path)];
306   bool                          is_dotfile;
307
308   PathInfo_append(&path, &d_path, path_buf);
309
310   is_dotfile = (dirname[0]=='.' &&
311                 (dirname[1]=='\0' || (dirname[1]=='.' && dirname[2]=='\0')));
312   memset(&f_stat, 0, sizeof f_stat);
313   skip_reason.r = rsDOTFILE;
314
315   if (is_dotfile ||
316       (match=checkDirEntry(&path, &d_path, &is_dir, &f_stat))==0) {
317     bool        is_link = is_dotfile ? false : S_ISLNK(f_stat.st_mode);
318     
319     if (global_args->verbosity>1 &&
320         ((!is_dotfile && !is_link) ||
321          (global_args->verbosity>4 && is_dotfile) ||
322          (global_args->verbosity>4 && is_link)) ) {
323       WRITE_MSG(1, "  skipping '");
324       write(1, path.d, path.l);
325       WRITE_MSG(1, "'");
326       if (global_args->verbosity>2) printSkipReason();
327       WRITE_MSG(1, "\n");
328     }
329     return;
330   }
331
332   if (is_dir) {
333     if (updateSkipDepth(&path, true)) {
334       visitDir(dirname, &f_stat);
335       updateSkipDepth(&path, false);
336     }
337   }
338   else if (!doit(match, &path, dirname)) {
339       // TODO: message
340   }
341 }
342
343 static void
344 visitDir(char const *name, struct stat const *expected_stat)
345 {
346   int           fd = Eopen(".", O_RDONLY, 0);
347   PathInfo      old_state = global_info.state;
348   PathInfo      rhs_path = {
349     .d = name,
350     .l = strlen(name)
351   };
352   char          new_path[ENSC_PI_APPSZ(global_info.state, rhs_path)];
353   DIR *         dir;
354
355   PathInfo_append(&global_info.state, &rhs_path, new_path);
356
357   if (expected_stat!=0)
358     EsafeChdir(name, expected_stat);
359   
360   dir = Eopendir(".");
361
362   for (;;) {
363     struct dirent               *ent = Ereaddir(dir);
364     if (ent==0) break;
365
366     visitDirEntry(ent);
367   }
368
369   Eclosedir(dir);
370
371   Efchdir(fd);
372   Eclose(fd);
373
374   global_info.state = old_state;
375 }
376
377 #include "vunify-init.ic"
378
379 int main(int argc, char *argv[])
380 {
381   struct Arguments      args = {
382     .mode               =  mdVSERVER,
383     .do_revert          =  false,
384     .do_dry_run         =  false,
385     .verbosity          =  0,
386     .local_fs           =  false,
387     .do_renew           =  true,
388   };
389
390   global_args = &args;
391   while (1) {
392     int         c = getopt_long(argc, argv, "Rnvcx",
393                                 CMDLINE_OPTIONS, 0);
394     if (c==-1) break;
395
396     switch (c) {
397       case CMD_HELP             :  showHelp(1, argv[0], 0);
398       case CMD_VERSION          :  showVersion();
399       case CMD_MANUALLY         :  args.mode = mdMANUALLY; break;
400       case 'R'                  :  args.do_revert  = true; break;
401       case 'n'                  :  args.do_dry_run = true; break;
402       case 'x'                  :  args.local_fs   = true; break;
403       //case 'C'                        :  args.do_renew   = false; break;
404       case 'v'                  :  ++args.verbosity; break;
405       default           :
406         WRITE_MSG(2, "Try '");
407         WRITE_STR(2, argv[0]);
408         WRITE_MSG(2, " --help\" for more information.\n");
409         return EXIT_FAILURE;
410         break;
411     }
412   }
413
414   if (argc==optind) {
415     WRITE_MSG(2, "No directory/vserver given\n");
416     return EXIT_FAILURE;
417   }
418
419   switch (args.mode) {
420     case mdMANUALLY     :  initModeManually(&args, argc-optind, argv+optind); break;
421     case mdVSERVER      :  initModeVserver (&args, argc-optind, argv+optind); break;
422     default             :  assert(false); return EXIT_FAILURE;
423   }
424     
425   global_info.state.d = "";
426   global_info.state.l = 0;
427
428
429   if (global_args->verbosity>3) WRITE_MSG(1, "Starting to traverse directories...\n");
430   Echdir(global_info.dst_list.root.d);
431   visitDir("/", 0);
432
433 #ifndef NDEBUG
434   {
435     size_t              i;
436     MatchList_destroy(&global_info.dst_list);
437     for (i=0; i<global_info.src_lists.l; ++i)
438       MatchList_destroy(global_info.src_lists.v+i);
439
440     free(global_info.src_lists.v);
441   }
442 #endif
443 }