- moved here from sysv/
[util-vserver.git] / src / vunify.cc
1 // $Id: vunify.cc,v 1.1.4.1 2003/10/30 15:16:30 ensc Exp $
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 #include <stdio.h>
29 #include <fcntl.h>
30 #include <sys/ioctl.h>
31 #include <sys/stat.h>
32 #include <unistd.h>
33 #include <errno.h>
34
35 #include <string>
36 #include <vector>
37 #include <list>
38 #include <algorithm>
39 #include <iostream>
40 #include "vutil.h"
41
42 using namespace std;
43
44 static bool undo = false;
45
46 static int  ext2flags = EXT2_IMMUTABLE_FL | EXT2_IUNLINK_FL;
47 struct EXCLDIR{
48         string prefix;
49         int len;
50         EXCLDIR(const char *s)
51         {
52                 prefix = s;
53                 prefix += '/';
54                 len = prefix.size();
55         }
56 };
57 static vector<EXCLDIR> excldirs;
58 static vector<EXCLDIR> incldirs;
59
60
61 static void usage()
62 {
63         cerr <<
64                 "vunify version " << VERSION <<
65                 "\n\n"
66                 "vunify [ options ] reference-server vservers ... -- packages\n"
67                 "vunify [ options ] reference-server vservers ... -- ALL\n"
68                 "\n"
69                 "--test: Show what will be done, do not do it.\n"
70                 "--undo: Put back the file in place, using copies from the\n"
71                 "        reference server.\n"
72                 "--debug: Prints some debugging messages.\n"
73                 "--noflags: Do not put any immutable flags on the file\n"
74                 "--immutable: Set the immutable_file bit on the files.\n"
75                 "--immutable-mayunlink: Sets the immutable_link flag on files.\n"
76                 "\n"
77                 "--excldir: None of the files under a given directory will be unified\n"
78                 "\tThe directory is expressed in absolute/logical form (relative\n"
79                 "\tto the vserver root (ex: /var/log)\n"
80                 "\n"
81                 "--incldir: All the files under a given directory will be unified\n"
82                 "\tThe directory is expressed in absolute/logical form (relative\n"
83                 "\tto the vserver root (ex: /var/log)\n"
84                 "\n"
85                 "By default, the immutable_file and     immutable_link flags are\n"
86                 "set on the files. So if you want no immutable flags, you must\n"
87                 "use --noflags. If you want a single flag, you must use\n"
88                 "--noflags first, then the --immutable or --immutable-mayunlink\n"
89                 "flag.\n"
90                 ;
91 }
92
93 static bool vunify_inside (vector<EXCLDIR> &dirs, const char *path)
94 {
95         bool found = false;
96         for (unsigned i=0; i<dirs.size(); i++){
97                 if (strncmp(dirs[i].prefix.c_str(),path,dirs[i].len)==0){
98                         found = true;
99                         break;
100                 }
101         }
102         return found;
103 }
104
105 class PACKAGE_UNI: public PACKAGE{
106 public:
107         list<string> files;             // Files to unify
108                                                         // This is loaded on demand
109         PACKAGE_UNI(string &_name, string &_version)
110                 : PACKAGE(_name,_version)
111         {
112         }
113         PACKAGE_UNI(const char *_name, const char *_version)
114                 : PACKAGE (_name,_version)
115         {
116         }
117         PACKAGE_UNI(const string &line)
118                 : PACKAGE (line)
119         {
120         }
121         // Load the file member of the package, but exclude configuration file
122         void loadfiles(const string &ref)
123         {
124                 if (files.empty()){
125                         if (debug) cout << "Loading files for package " << name << endl;
126                         string namever;
127                         namever = name + '-' + version;
128                         FILE *fin = vutil_execdistcmd (K_UNIFILES,ref,namever.c_str());
129                         if (fin != NULL){
130                                 char tmp[1000];
131                                 while (fgets(tmp,sizeof(tmp)-1,fin)!=NULL){
132                                         int last = strlen(tmp)-1;
133                                         if (last >= 0 && tmp[last] == '\n') tmp[last] = '\0';
134                                         bool must_unify = false;
135                                         int type = 0;   // K_UNIFILES only report unify-able files
136                                         if(type == 0 && !vunify_inside(excldirs,tmp)){
137                                                 must_unify = true;
138                                         }else if(vunify_inside(incldirs,tmp)){
139                                                 must_unify = true;
140                                         }
141                                         if (must_unify){
142                                                 files.push_front (tmp);
143                                         }else if (debug){
144                                                 cout << "Package " << name << " exclude " << tmp << endl;
145                                         }
146                                 }
147                         }
148                         if (debug) cout << "Done\n";
149                 }
150         }
151 };
152
153
154 static ostream & operator << (ostream &c, const PACKAGE_UNI &p)
155 {
156         return c << p.name << "-" << p.version;
157 }
158
159 template<class T>
160         void printit(T a){
161                 cout << "xx " << a << endl;
162         }
163
164 template<class T>
165         class printer{
166                 string title;
167                 public:
168                 printer (const char *_title): title(_title){}
169                 void operator()(T a){
170                         cout << title << " " << a << endl;
171                 }
172         };
173
174
175 /*
176         Load the list of all packages in a vserver
177 */
178 static void vunify_loadallpkg (string &refserver, list<PACKAGE_UNI> &packages)
179 {
180         FILE *fin = vutil_execdistcmd (K_PKGVERSION,refserver,NULL);
181         if (fin != NULL){
182                 char line[1000];
183                 while (fgets(line,sizeof(line)-1,fin)!=NULL){
184                         // fprintf (stderr,"line: %s",line);
185                         int last = strlen(line)-1;
186                         if (last >= 0 && line[last] == '\n') line[last] = '\0';
187                         packages.push_back (PACKAGE_UNI(line));
188                 }
189                 pclose (fin);
190         }
191 }
192
193 /*
194         Object to unify a file
195         The file is first removed, then a hard link is made  and then
196         the immutable flag is done
197 */
198 class file_unifier{
199         string &ref_server,&target_server;
200         int &ret;
201         public:
202         file_unifier(string &_ref, string &_target, int &_ret)
203                 : ref_server(_ref),target_server(_target), ret(_ret)
204         {}
205         void operator()(const string &file)
206         {
207                 string refpath = VROOTDIR "/" + ref_server + file;
208                 string dstpath = VROOTDIR "/" + target_server + file;
209                 if (debug) cout << "Unify " << refpath << " -> " << dstpath << endl;
210                 struct stat st;
211                 if (stat(refpath.c_str(),&st)==-1){
212                         if (debug) cout << "File " << refpath << " does not exist, ignored\n";
213                 }else if (setext2flag(refpath.c_str(),false,ext2flags)==-1){
214                         ret = -1;
215                 }else if (vbuild_unlink(dstpath.c_str())==-1){
216                         ret = -1;
217                         cerr << "Can't delete file " << dstpath
218                                 << " (" << strerror(errno) << ")\n";
219                 }else{
220                         if (undo){
221                                 if (vbuild_file_copy(refpath.c_str(),dstpath.c_str(),st)==-1){
222                                         ret = -1;
223                                         cerr << "Can't copy file " << refpath << " to " << dstpath
224                                                 << " (" << strerror(errno) << ")\n";
225                                 }
226                         }else{
227                                 if (vbuild_link(refpath.c_str(),dstpath.c_str())==-1){
228                                         ret = -1;
229                                         cerr << "Can't link file " << refpath << " to " << dstpath
230                                                 << " (" << strerror(errno) << ")\n";
231                                 }
232                         }
233                         // We put back the original immutable because other vservers
234                         // may be unified on it.
235                         if (setext2flag(refpath.c_str(),true,ext2flags)==-1){
236                                 ret = -1;
237                         }
238                 }
239         }
240 };
241 #if 0
242 // Check if two package have the same name (but potentially different version)
243 class same_name{
244         PACKAGE_UNI &pkg;
245 public:
246         same_name(PACKAGE_UNI &_pkg) : pkg(_pkg) {}
247         bool operator()(const PACKAGE_UNI &p)
248         {
249                 return pkg.name == p.name;
250         }
251 };
252 #endif
253 // Predicate to decide if a package must be unified
254 class package_unifier{
255 public:
256         string &ref_server,&target_server;
257         list<PACKAGE_UNI> &target_packages;
258         int &ret;
259         package_unifier(string &_ref,
260                         string &_target,
261                         list<PACKAGE_UNI> &_target_packages,
262                         int &_ret)
263                 : ref_server(_ref),target_server(_target)
264                 , target_packages(_target_packages) , ret(_ret)
265         {}
266         void operator()(PACKAGE_UNI &pkg)
267         {
268                 if (find(target_packages.begin(),target_packages.end(),pkg)
269                         !=target_packages.end()){
270                         // Ok, the package is also in the target vserver
271                         cout << "Unify pkg " << pkg << " from " << ref_server << " to "
272                                 << target_server << endl;
273
274                         if (!testmode || debug){
275                                 pkg.loadfiles(ref_server);
276                                 for_each (pkg.files.begin(),pkg.files.end()
277                                         ,file_unifier(ref_server,target_server,ret));
278                         }
279                 }else if (testmode){
280                         // The package is missing, in test mode we provide more information
281                         if (find_if(target_packages.begin(),target_packages.end(),same_name(pkg))
282                                 !=target_packages.end()){
283                                 cout << pkg << " exist in server " << target_server << " not unified\n";
284                         }else{
285                                 cout << pkg << " does not exist in server " << target_server << endl;
286                         }
287                 }
288         }
289 };
290
291 // For each vserver, find the common packages and unify them
292 class server_unifier{
293 public:
294         list<PACKAGE_UNI> &ref_packages;
295         string &ref_server;
296         int &ret;
297         server_unifier(string _ref_server, list<PACKAGE_UNI> &_packages, int &_ret)
298                 : ref_packages(_packages),ref_server(_ref_server), ret(_ret)
299                 {}
300         void operator()(string serv)
301         {
302                 list<PACKAGE_UNI> pkgs;
303                 vunify_loadallpkg (serv,pkgs);
304                 for_each(ref_packages.begin(),ref_packages.end()
305                         ,package_unifier(ref_server,serv,pkgs,ret));
306         }
307 };
308 class deleteif{
309 public:
310         char **argv0,**argvn;
311         deleteif(char **_argv0, char **_argvn): argv0(_argv0),argvn(_argvn){}
312         bool operator()(const PACKAGE_UNI &pkg)
313         {
314                 bool found = false;
315                 for (char **pt = argv0; pt < argvn; pt++){
316                         if (pkg.name == *pt){
317                                 found = true;
318                                 break;
319                         }
320                 }
321                 return !found;
322         }
323 };
324
325 int main (int argc, char *argv[])
326 {
327         int ret = -1;
328         int i;
329         for (i=1; i<argc; i++){
330                 const char *arg = argv[i];
331                 //const char *opt = argv[i+1];
332                 if (strcmp(arg,"--test")==0){
333                         testmode = true;
334                 }else if (strcmp(arg,"--undo")==0){
335                         undo = true;
336                 }else if (strcmp(arg,"--debug")==0){
337                         debug++;
338                 }else if (strcmp(arg,"--noflags")==0){
339                         ext2flags = 0;
340                 }else if (strcmp(arg,"--immutable")==0){
341                         ext2flags |= EXT2_IMMUTABLE_FL;
342                 }else if (strcmp(arg,"--immutable-mayunlink")==0){
343                         ext2flags |= EXT2_IUNLINK_FL;
344                 }else if (strcmp(arg,"--excldir")==0){
345                         i++;
346                         //excldirs[excldirs.size()] = EXCLDIR(argv[i]);
347                         excldirs.push_back (EXCLDIR(argv[i]));
348                 }else if (strcmp(arg,"--incldir")==0){
349                         i++;
350                         incldirs.push_back (EXCLDIR(argv[i]));
351                 }else{
352                         break;
353                 }
354         }
355         if (i==argc){
356                 usage();
357         }else{
358                 string refserv = argv[i++];
359                 list<string> vservers;
360                 for (; i<argc && strcmp(argv[i],"--")!=0; i++){
361                         vservers.push_front (argv[i]);
362                 }
363                 for_each (vservers.begin(),vservers.end(),printer<string>("vservers"));
364                 if (i == argc || strcmp(argv[i],"--")!=0){
365                         usage();
366                 }else{
367                         i++;
368                         if (i < argc){
369                                 list<PACKAGE_UNI> packages;
370                                 vunify_loadallpkg (refserv,packages);
371                                 if (i != argc-1 || strcmp(argv[i],"ALL")!=0){
372                                         // We keep only the packages supplied on the command line
373                                         packages.remove_if(deleteif (argv+i,argv+argc));
374                                 }
375                                 ret = 0;
376                                 umask (0);
377                                 for_each (vservers.begin(),vservers.end(),server_unifier(refserv,packages,ret));
378                         }else{
379                                 usage();
380                         }
381                 }
382         }
383         return ret;
384 }
385
386
387
388