vshost-util-vserver

Build script and sources for util-vserver.
git clone https://ccx.te2000.cz/git/vshost-util-vserver
Log | Files | Refs

vunify.c (12293B)


      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 <sys/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 }