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 }