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