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