ready for tagging
[util-vserver.git] / src / vclone.c
1 // $Id: vclone.c 2569 2007-07-22 17:24:29Z dhozac $    --*- c -*--
2
3 // Copyright (C) 2007 Daniel Hokka Zakrisson
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 "util.h"
24 #include "vserver.h"
25
26 #include "lib_internal/pathinfo.h"
27 #include "lib_internal/unify.h"
28 #include "lib_internal/matchlist.h"
29
30 #include <unistd.h>
31 #include <getopt.h>
32 #include <fcntl.h>
33 #include <dirent.h>
34 #include <errno.h>
35 #include <assert.h>
36 #include <utime.h>
37 #include <libgen.h>
38 #include <sys/param.h>
39
40 #define ENSC_WRAPPERS_PREFIX    "vclone: "
41 #define ENSC_WRAPPERS_UNISTD    1
42 #define ENSC_WRAPPERS_FCNTL     1
43 #define ENSC_WRAPPERS_DIRENT    1
44 #define ENSC_WRAPPERS_VSERVER   1
45 #include <wrappers.h>
46
47 #define CMD_HELP                0x8000
48 #define CMD_VERSION             0x8001
49 #define CMD_XID                 0x8002
50
51 struct WalkdownInfo
52 {
53     PathInfo            state;
54     PathInfo            src;
55     PathInfo            dst;
56     struct MatchList    excludes;
57 };
58
59 struct Arguments {
60     unsigned int        verbosity;
61     xid_t               xid;
62     const char *        exclude_list;
63 };
64
65 static struct WalkdownInfo              global_info;
66 static struct Arguments const *         global_args;
67
68 int wrapper_exit_code = 1;
69
70 struct option const
71 CMDLINE_OPTIONS[] = {
72   { "help",         no_argument,       0, CMD_HELP },
73   { "version",      no_argument,       0, CMD_VERSION },
74   { "xid",          required_argument, 0, CMD_XID },
75   { "exclude-from", required_argument, 0, 'X' },
76   { 0,0,0,0 }
77 };
78
79
80 static void
81 showHelp(int fd, char const *cmd, int res)
82 {
83   VSERVER_DECLARE_CMD(cmd);
84   
85   WRITE_MSG(fd, "Usage:\n  ");
86   WRITE_STR(fd, cmd);
87   WRITE_MSG(fd,
88             " [--xid <xid>] [--exclude-from <exclude-list>]\n"
89             "         <source> <absolute path to destination>\n\n"
90             "Please report bugs to " PACKAGE_BUGREPORT "\n");
91   exit(res);
92 }
93
94 static void
95 showVersion()
96 {
97   WRITE_MSG(1,
98             "vclone " VERSION " -- clones a guest\n"
99             "This program is part of " PACKAGE_STRING "\n\n"
100             "Copyright (C) 2007 Daniel Hokka Zakrisson\n"
101             VERSION_COPYRIGHT_DISCLAIMER);
102   exit(0);
103 }
104
105 int Global_getVerbosity() {
106   return global_args->verbosity;
107 }
108
109 bool Global_doRenew() {
110   return true;
111 }
112
113 #include "vserver-visitdir.hc"
114
115 static bool
116 handleDirEntry(const PathInfo *src_path, const PathInfo *basename,
117               bool *is_dir, struct stat *st)
118 {
119   bool res = false;
120
121   *is_dir = false;
122
123   if (lstat(basename->d, st)==-1)
124     PERROR_Q(ENSC_WRAPPERS_PREFIX "lstat", src_path->d);
125   else {
126     PathInfo            dst_path = global_info.dst;
127     char                dst_path_buf[ENSC_PI_APPSZ(dst_path, *src_path)];
128
129     if (S_ISDIR(st->st_mode))
130       *is_dir = true;
131
132     if (MatchList_compare(&global_info.excludes, src_path->d) != stINCLUDE) {
133       if (Global_getVerbosity() > 1) {
134         WRITE_MSG(1, "  skipping '");
135         Vwrite(1, src_path->d, src_path->l);
136         WRITE_MSG(1, "' (excluded)\n");
137       }
138       return false;
139     }
140
141     PathInfo_append(&dst_path, src_path, dst_path_buf);
142
143     /* skip files that already exist */
144     if (access(dst_path.d, F_OK)!=-1) {
145       if (Global_getVerbosity() > 1) {
146         WRITE_MSG(1, "  skipping '");
147         Vwrite(1, src_path->d, src_path->l);
148         WRITE_MSG(1, "' (exists in destination)\n");
149       }
150       res = true;
151     }
152     else {
153       /* create directory that might have been skipped */
154       if (global_info.excludes.skip_depth > 0) {
155         if (Global_getVerbosity() > 4) {
156           WRITE_MSG(1, "  creating directories for '");
157           Vwrite(1, dst_path.d, dst_path.l);
158           WRITE_MSG(1, "'\n");
159         }
160         if (mkdirRecursive(dst_path.d) == -1)
161           PERROR_Q(ENSC_WRAPPERS_PREFIX "mkdirRecursive", dst_path.d);
162       }
163
164       /* already unified file */
165       if (S_ISREG(st->st_mode) && Unify_isIUnlinkable(basename->d) == unifyBUSY) {
166         if (Global_getVerbosity() > 2) {
167           WRITE_MSG(1, "  linking unified file '");
168           Vwrite(1, src_path->d, src_path->l);
169           WRITE_MSG(1, "'\n");
170         }
171         Elink(basename->d, dst_path.d);
172         res = true;
173       }
174       /* something we have to copy */
175       else {
176         if (Global_getVerbosity() > 2) {
177           WRITE_MSG(1, "  copying non-unified file '");
178           Vwrite(1, src_path->d, src_path->l);
179           WRITE_MSG(1, "'\n");
180         }
181         if (!Unify_copy(basename->d, st, dst_path.d))
182           PERROR_Q(ENSC_WRAPPERS_PREFIX "Unify_copy", dst_path.d);
183         else if (global_args->xid != VC_NOCTX &&
184                  vc_set_iattr(dst_path.d, global_args->xid, 0, VC_IATTR_XID) == -1)
185           PERROR_Q(ENSC_WRAPPERS_PREFIX "vc_set_iattr", dst_path.d);
186         else
187           res = true;
188       }
189     }
190   }
191
192   return res;
193 }
194
195 /* returns 1 on error, 0 on success */
196 static uint64_t
197 visitDirEntry(struct dirent const *ent)
198 {
199   char const *                  dirname  = ent->d_name;
200   if (isDotfile(dirname)) return 0;
201
202   uint64_t                      res      = 1;
203   PathInfo                      src_path = global_info.state;
204   PathInfo                      src_d_path = {
205     .d = dirname,
206     .l = strlen(dirname)
207   };
208   char                          path_buf[ENSC_PI_APPSZ(src_path, src_d_path)];
209   struct stat                   f_stat = { .st_dev = 0 };
210   bool                          is_dir;
211
212   PathInfo_append(&src_path, &src_d_path, path_buf);
213
214   if (handleDirEntry(&src_path, &src_d_path, &is_dir, &f_stat))
215     res = 0;
216
217   if (is_dir) {
218     if (res || global_info.excludes.skip_depth > 0)
219       global_info.excludes.skip_depth++;
220     res = res + visitDir(dirname, &f_stat);
221     if (global_info.excludes.skip_depth > 0)
222       global_info.excludes.skip_depth--;
223   }
224
225   return res;
226 }
227
228 int main(int argc, char *argv[])
229 {
230   struct Arguments      args = {
231     .verbosity          =  0,
232     .xid                = VC_NOCTX,
233     .exclude_list       = NULL,
234   };
235   uint64_t              res;
236   int                   num_args;
237
238   global_args = &args;
239   while (1) {
240     int         c = getopt_long(argc, argv, "+vX:",
241                                 CMDLINE_OPTIONS, 0);
242     if (c==-1) break;
243
244     switch (c) {
245       case CMD_HELP     :  showHelp(1, argv[0], 0);
246       case CMD_VERSION  :  showVersion();
247       case 'v'          :  args.verbosity++; break;
248       case 'X'          :  args.exclude_list = optarg; break;
249       case CMD_XID      :  args.xid = Evc_xidopt2xid(optarg,true); break;
250       default           :
251         WRITE_MSG(2, "Try '");
252         WRITE_STR(2, argv[0]);
253         WRITE_MSG(2, " --help' for more information.\n");
254         return EXIT_FAILURE;
255         break;
256     }
257   }
258
259   num_args = argc - optind;
260   if (num_args < 1) {
261     WRITE_MSG(2, "Source is missing; try '");
262     WRITE_STR(2, argv[0]);
263     WRITE_MSG(2, " --help' for more information.\n");
264     return EXIT_FAILURE;
265   }
266   else if (num_args < 2) {
267     WRITE_MSG(2, "Destination is missing; try '");
268     WRITE_STR(2, argv[0]);
269     WRITE_MSG(2, " --help' for more information.\n");
270     return EXIT_FAILURE;
271   }
272   else if (num_args > 2) {
273     WRITE_MSG(2, "Too many arguments; try '");
274     WRITE_STR(2, argv[0]);
275     WRITE_MSG(2, " --help' for more information.\n");
276     return EXIT_FAILURE;
277   }
278   else if (*argv[optind+1] != '/') {
279     WRITE_MSG(2, "The destination must be an absolute path; try '");
280     WRITE_STR(2, argv[0]);
281     WRITE_MSG(2, " --help' for more information.\n");
282     return EXIT_FAILURE;
283   }
284   ENSC_PI_SETSTR(global_info.src, argv[optind]);
285   ENSC_PI_SETSTR(global_info.dst, argv[optind+1]);
286
287   if (global_args->exclude_list)
288     MatchList_initManually(&global_info.excludes, 0, strdup(argv[optind]),
289                            global_args->exclude_list);
290   else
291     MatchList_init(&global_info.excludes, argv[optind], 0);
292
293   if (global_args->verbosity>3)
294     WRITE_MSG(1, "Starting to traverse directories...\n");
295
296   Echdir(global_info.src.d);
297   res = visitDir("/", 0);
298
299   MatchList_destroy(&global_info.excludes);
300   
301   return res>0 ? 1 : 0;
302 }