made chcontext a dietlibc program and updated its SOURCES
[util-vserver.git] / util-vserver / src / vunify.cc
1 // $Id$
2
3 // Copyright (C) 2003 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
4 // based on vunify.cc by Jacques Gelinas
5 //  
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 2, or (at your option)
9 // any later version.
10 //  
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 // GNU General Public License for more details.
15 //  
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20
21 /*
22         This utility is used to unify (using hard links) two or more
23         virtual servers.
24         It compares the each vserver with the first one and for every
25         common package (RPM, same version), it does a hard link on non
26         configuration file. It turns the file immutable after that.
27 */
28 #ifdef HAVE_CONFIG_H
29 #  include <config.h>
30 #endif
31
32 #include "vserver.hh"
33 #include "util.h"
34
35 #include <getopt.h>
36 #include <fcntl.h>
37 #include <sys/ioctl.h>
38 #include <sys/stat.h>
39 #include <unistd.h>
40 #include <errno.h>
41
42 #include <string>
43 #include <vector>
44 #include <list>
45 #include <algorithm>
46 #include <iostream>
47 #include "vutil.h"
48
49 using namespace std;
50
51 static bool undo = false;
52
53 static int  ext2flags = EXT2_IMMUTABLE_FILE_FL | EXT2_IMMUTABLE_LINK_FL;
54 struct EXCLDIR{
55         string prefix;
56         int len;
57         EXCLDIR(const char *s)
58         {
59                 prefix = s;
60                 prefix += '/';
61                 len = prefix.size();
62         }
63 };
64
65 #define OPTION_TEST             1024
66 #define OPTION_UNDO             1025
67 #define OPTION_DEBUG            1026
68 #define OPTION_NOFLAGS          1027
69 #define OPTION_IMMUTABLE        1028
70 #define OPTION_IMM_UNLINK       1029
71 #define OPTION_EXCLDIR          1030
72 #define OPTION_INCLDIR          1031
73
74 static struct option const
75 CMDLINE_OPTIONS[] = {
76   { "help",                no_argument,       0, 'h' },
77   { "version",             no_argument,       0, 'v' },
78   { "test",                no_argument,       0, OPTION_TEST },
79   { "undo",                no_argument,       0, OPTION_UNDO },
80   { "debug",               no_argument,       0, OPTION_DEBUG },
81   { "noflags",             no_argument,       0, OPTION_NOFLAGS },
82   { "immutable",           no_argument,       0, OPTION_IMMUTABLE },
83   { "immutable-mayunlink", no_argument,       0, OPTION_IMM_UNLINK },
84   { "excldir",             required_argument, 0, OPTION_EXCLDIR },
85   { "incldir",             required_argument, 0, OPTION_INCLDIR },
86   { 0,0,0,0 }
87 };
88   
89   
90 static vector<EXCLDIR> excldirs;
91 static vector<EXCLDIR> incldirs;
92
93 static void
94 showHelp(std::ostream &out, char const *, int exit_code)
95 {
96   out << "Usage: \n"
97     "vunify [ options ] reference-server vservers ... -- packages\n"
98     "vunify [ options ] reference-server vservers ... -- ALL\n"
99     "\n"
100     "--test: Show what will be done, do not do it.\n"
101     "--undo: Put back the file in place, using copies from the\n"
102     "        reference server.\n"
103     "--debug: Prints some debugging messages.\n"
104     "--noflags: Do not put any immutable flags on the file\n"
105     "--immutable: Set the immutable_file bit on the files.\n"
106     "--immutable-mayunlink: Sets the immutable_link flag on files.\n"
107     "\n"
108     "--excldir: None of the files under a given directory will be unified\n"
109     "\tThe directory is expressed in absolute/logical form (relative\n"
110     "\tto the vserver root (ex: /var/log)\n"
111     "\n"
112     "--incldir: All the files under a given directory will be unified\n"
113     "\tThe directory is expressed in absolute/logical form (relative\n"
114     "\tto the vserver root (ex: /var/log)\n"
115     "\n"
116     "By default, the immutable_file and immutable_link flags are\n"
117     "set on the files. So if you want no immutable flags, you must\n"
118     "use --noflags. If you want a single flag, you must use\n"
119     "--noflags first, then the --immutable or --immutable-mayunlink\n"
120     "flag.\n";
121
122   std::exit(exit_code);
123 }
124
125 static void
126 showVersion()
127 {
128   std::cout << "vunify " VERSION " -- unifies files of vservers\n"
129     "This program is part of " PACKAGE_STRING "\n\n"
130     "Copyright (C) 2003 Enrico Scholz\n"
131     VERSION_COPYRIGHT_DISCLAIMER;
132
133   std::exit(0);
134 }
135
136 static bool vunify_inside (vector<EXCLDIR> &dirs, const char *path)
137 {
138         bool found = false;
139         for (unsigned i=0; i<dirs.size(); i++){
140                 if (strncmp(dirs[i].prefix.c_str(),path,dirs[i].len)==0){
141                         found = true;
142                         break;
143                 }
144         }
145         return found;
146 }
147
148 class PACKAGE_UNI: public Package{
149 public:
150         list<string> files;             // Files to unify
151                                                         // This is loaded on demand
152         PACKAGE_UNI(string &_name, string &_version)
153                 : Package(_name,_version)
154         {
155         }
156         PACKAGE_UNI(const char *_name, const char *_version)
157                 : Package (_name,_version)
158         {
159         }
160         PACKAGE_UNI(const string &line)
161                 : Package (line)
162         {
163         }
164         // Load the file member of the package, but exclude configuration file
165         void loadfiles(Vserver const &ref)
166         {
167                 if (files.empty()){
168                         if (debug) cout << "Loading files for package " << name << endl;
169                         string namever;
170                         namever = name + '-' + version;
171                         FILE *fin = vutil_execdistcmd (K_UNIFILES,ref,namever.c_str());
172                         if (fin != NULL){
173                                 char tmp[1000];
174                                 while (fgets(tmp,sizeof(tmp)-1,fin)!=NULL){
175                                         int last = strlen(tmp)-1;
176                                         if (last >= 0 && tmp[last] == '\n') tmp[last] = '\0';
177                                         bool must_unify = false;
178                                         int type = 0;   // K_UNIFILES only report unify-able files
179                                         if(type == 0 && !vunify_inside(excldirs,tmp)){
180                                                 must_unify = true;
181                                         }else if(vunify_inside(incldirs,tmp)){
182                                                 must_unify = true;
183                                         }
184                                         if (must_unify){
185                                                 files.push_front (tmp);
186                                         }else if (debug){
187                                                 cout << "Package " << name << " exclude " << tmp << endl;
188                                         }
189                                 }
190                         }
191                         if (debug) cout << "Done\n";
192                 }
193         }
194 };
195
196
197 static ostream & operator << (ostream &c, const PACKAGE_UNI &p)
198 {
199         return c << p.name << "-" << p.version;
200 }
201
202 template<class T>
203         void printit(T a){
204                 cout << "xx " << a << endl;
205         }
206
207 template<class T>
208         class printer{
209                 string title;
210                 public:
211                 printer (const char *_title): title(_title){}
212                 void operator()(T const &a){
213                         cout << title << " " << a << endl;
214                 }
215         };
216
217
218 /*
219         Load the list of all packages in a vserver
220 */
221 static void vunify_loadallpkg (Vserver const &refserver, list<PACKAGE_UNI> &packages)
222 {
223         FILE *fin = vutil_execdistcmd (K_PKGVERSION,refserver,NULL);
224         if (fin != NULL){
225                 char line[1000];
226                 while (fgets(line,sizeof(line)-1,fin)!=NULL){
227                         // fprintf (stderr,"line: %s",line);
228                         int last = strlen(line)-1;
229                         if (last >= 0 && line[last] == '\n') line[last] = '\0';
230                         packages.push_back (PACKAGE_UNI(line));
231                 }
232                 pclose (fin);
233         }
234 }
235
236 /*
237         Object to unify a file
238         The file is first removed, then a hard link is made  and then
239         the immutable flag is done
240 */
241 class file_unifier{
242     Vserver const       &ref_server;
243     Vserver const       &target_server;
244     int &ret;
245
246   public:
247     file_unifier(Vserver const &_ref, Vserver const &_target, int &_ret)
248       : ref_server(_ref),target_server(_target), ret(_ret)
249     {}
250
251     void operator()(const string &file)
252     {
253       std::string       refpath = ref_server.getVdir();
254       refpath          += file;
255           
256       std::string       dstpath = target_server.getVdir();
257       dstpath              += file;
258           
259       if (debug) cout << "Unify " << refpath << " -> " << dstpath << endl;
260       struct stat st;
261       if (stat(refpath.c_str(),&st)==-1){
262         if (debug) cout << "File " << refpath << " does not exist, ignored\n";
263       }else if (setext2flag(refpath.c_str(),false,ext2flags)==-1){
264         ret = -1;
265       }else if (vbuild_unlink(dstpath.c_str())==-1){
266         ret = -1;
267         cerr << "Can't delete file " << dstpath
268              << " (" << strerror(errno) << ")\n";
269       }else{
270         if (undo){
271           if (vbuild_file_copy(refpath.c_str(),dstpath.c_str(),st)==-1){
272             ret = -1;
273             cerr << "Can't copy file " << refpath << " to " << dstpath
274                  << " (" << strerror(errno) << ")\n";
275           }
276         }else{
277           if (vbuild_link(refpath.c_str(),dstpath.c_str())==-1){
278             ret = -1;
279             cerr << "Can't link file " << refpath << " to " << dstpath
280                  << " (" << strerror(errno) << ")\n";
281           }
282         }
283           // We put back the original immutable because other vservers
284           // may be unified on it.
285         if (setext2flag(refpath.c_str(),true,ext2flags)==-1){
286           ret = -1;
287         }
288       }
289     }
290 };
291
292 #if 0
293 // Check if two package have the same name (but potentially different version)
294 class same_name{
295         PACKAGE_UNI &pkg;
296 public:
297         same_name(PACKAGE_UNI &_pkg) : pkg(_pkg) {}
298         bool operator()(const PACKAGE_UNI &p)
299         {
300                 return pkg.name == p.name;
301         }
302 };
303 #endif
304   // Predicate to decide if a package must be unified
305 class package_unifier{
306   public:
307     Vserver const       &ref_server;
308     Vserver const       &target_server;
309     list<PACKAGE_UNI>   &target_packages;
310     int                 &ret;
311     
312     package_unifier(Vserver const &_ref,
313                     Vserver const &_target,
314                     list<PACKAGE_UNI> &_target_packages,
315                     int &_ret) :
316       ref_server(_ref),target_server(_target),
317       target_packages(_target_packages), ret(_ret)
318     {}
319     void operator()(PACKAGE_UNI &pkg)
320     {
321       if (find(target_packages.begin(),target_packages.end(),pkg)
322           !=target_packages.end()){
323           // Ok, the package is also in the target vserver
324         cout << "Unify pkg " << pkg << " from " << ref_server << " to "
325              << target_server << endl;
326
327         if (!testmode || debug){
328           pkg.loadfiles(ref_server);
329           for_each (pkg.files.begin(),pkg.files.end()
330                     ,file_unifier(ref_server,target_server,ret));
331         }
332       }else if (testmode){
333           // The package is missing, in test mode we provide more information
334         if (find_if(target_packages.begin(),target_packages.end(),same_name(pkg))
335             !=target_packages.end()){
336           cout << pkg << " exist in server " << target_server << " not unified\n";
337         }else{
338           cout << pkg << " does not exist in server " << target_server << endl;
339         }
340       }
341     }
342 };
343
344 // For each vserver, find the common packages and unify them
345 class server_unifier{
346 public:
347     list<PACKAGE_UNI>   &ref_packages;
348     Vserver const       &ref_server;
349     int                 &ret;
350     
351     server_unifier(Vserver const &_ref_server, list<PACKAGE_UNI> &_packages, int &_ret)
352       : ref_packages(_packages),ref_server(_ref_server), ret(_ret)
353     {}
354     
355     void operator()(Vserver const &serv)
356     {
357       list<PACKAGE_UNI> pkgs;
358       vunify_loadallpkg (serv,pkgs);
359       for_each(ref_packages.begin(),ref_packages.end()
360                ,package_unifier(ref_server,serv,pkgs,ret));
361     }
362 };
363
364 class deleteif{
365 public:
366         char **argv0,**argvn;
367         deleteif(char **_argv0, char **_argvn): argv0(_argv0),argvn(_argvn){}
368         bool operator()(const PACKAGE_UNI &pkg)
369         {
370                 bool found = false;
371                 for (char **pt = argv0; pt < argvn; pt++){
372                         if (pkg.name == *pt){
373                                 found = true;
374                                 break;
375                         }
376                 }
377                 return !found;
378         }
379 };
380
381 int main (int argc, char *argv[])
382 {
383   while (1) {
384     int         c = getopt_long(argc, argv, "hv", CMDLINE_OPTIONS, 0);
385     switch (c) {
386       case 'h'                  :  showHelp(std::cout, argv[0], 0); break;
387       case 'v'                  :  showVersion();                   break;
388       case OPTION_TEST          :  testmode = true; break;
389       case OPTION_UNDO          :  undo     = true; break;
390       case OPTION_DEBUG         :  ++debug;         break;
391       case OPTION_NOFLAGS       :  ext2flags = 0;   break;
392       case OPTION_IMMUTABLE     :  ext2flags |= EXT2_IMMUTABLE_FILE_FL; break;
393       case OPTION_IMM_UNLINK    :  ext2flags |= EXT2_IMMUTABLE_LINK_FL; break;
394       case OPTION_EXCLDIR       :  excldirs.push_back(EXCLDIR(optarg)); break;
395       case OPTION_INCLDIR       :  incldirs.push_back(EXCLDIR(optarg)); break;
396       default                   :
397         std::cerr << "Try '" << argv[0] << " --help' for more information." << std::endl;
398         return EXIT_FAILURE;
399     }
400   }
401
402   if (optind+1>=argc) {
403     std::cerr << "No vserver specified" << std::endl;
404     return EXIT_FAILURE;
405   }
406
407   Vserver                       refserv(argv[optind++]);
408   std::vector<Vserver>          vservers;
409   while (optind<argc && strcmp(argv[optind], "--")!=0)
410     vservers.push_back(Vserver(argv[optind++]));
411
412   ++optind;
413   if (optind>=argc) {
414     std::cerr << "No packages specified" << std::endl;
415     return EXIT_FAILURE;
416   }
417
418   for_each (vservers.begin(),vservers.end(),printer<Vserver>("vservers"));
419
420   std::list<PACKAGE_UNI>        packages;
421   vunify_loadallpkg(refserv, packages);
422   
423   if (optind!=argc-1 || strcmp(argv[optind], "ALL")!=0)
424     packages.remove_if(deleteif(argv+optind, argv+argc));
425
426   int           ret = EXIT_SUCCESS;
427   umask(0);
428   for_each(vservers.begin(), vservers.end(), server_unifier(refserv,packages,ret));
429
430   return ret;
431 }