gentoo: use /var/run for new /run compatibility
[util-vserver.git] / src / vclone.c
1 // $Id$    --*- 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     struct stat         dst_st;
129
130     if (S_ISDIR(st->st_mode))
131       *is_dir = true;
132
133     if (MatchList_compare(&global_info.excludes, src_path->d) != stINCLUDE) {
134       if (Global_getVerbosity() > 1) {
135         WRITE_MSG(1, "  skipping '");
136         Vwrite(1, src_path->d, src_path->l);
137         WRITE_MSG(1, "' (excluded)\n");
138       }
139       return true;
140     }
141
142     PathInfo_append(&dst_path, src_path, dst_path_buf);
143
144     /* skip files that already exist */
145     if (lstat(dst_path.d, &dst_st)!=-1) {
146       if (Global_getVerbosity() > 1) {
147         WRITE_MSG(1, "  skipping '");
148         Vwrite(1, src_path->d, src_path->l);
149         WRITE_MSG(1, "' (exists in destination)\n");
150       }
151       res = true;
152     }
153     else {
154       /* create directory that might have been skipped */
155       if (global_info.excludes.skip_depth > 0) {
156         if (Global_getVerbosity() > 4) {
157           WRITE_MSG(1, "  creating directories for '");
158           Vwrite(1, dst_path.d, dst_path.l);
159           WRITE_MSG(1, "'\n");
160         }
161         if (mkdirRecursive(dst_path.d) == -1)
162           PERROR_Q(ENSC_WRAPPERS_PREFIX "mkdirRecursive", dst_path.d);
163       }
164
165       /* already unified file */
166       if (S_ISREG(st->st_mode) && Unify_isIUnlinkable(basename->d) == unifyBUSY) {
167         if (Global_getVerbosity() > 2) {
168           WRITE_MSG(1, "  linking unified file '");
169           Vwrite(1, src_path->d, src_path->l);
170           WRITE_MSG(1, "'\n");
171         }
172         Elink(basename->d, dst_path.d);
173         res = true;
174       }
175       /* something we have to copy */
176       else {
177         if (Global_getVerbosity() > 2) {
178           WRITE_MSG(1, "  copying non-unified file '");
179           Vwrite(1, src_path->d, src_path->l);
180           WRITE_MSG(1, "'\n");
181         }
182         if (!Unify_copy(basename->d, st, dst_path.d))
183           PERROR_Q(ENSC_WRAPPERS_PREFIX "Unify_copy", dst_path.d);
184         else if (!S_ISSOCK(st->st_mode) &&
185                  global_args->xid != VC_NOCTX &&
186                  vc_set_iattr(dst_path.d, global_args->xid, 0, VC_IATTR_XID) == -1 &&
187                  errno != EINVAL)
188           PERROR_Q(ENSC_WRAPPERS_PREFIX "vc_set_iattr", dst_path.d);
189         else
190           res = true;
191       }
192     }
193   }
194
195   return res;
196 }
197
198 /* returns 1 on error, 0 on success */
199 static uint64_t
200 visitDirEntry(struct dirent const *ent)
201 {
202   char const *                  dirname  = ent->d_name;
203   if (isDotfile(dirname)) return 0;
204
205   uint64_t                      res      = 1;
206   PathInfo                      src_path = global_info.state;
207   PathInfo                      src_d_path = {
208     .d = dirname,
209     .l = strlen(dirname)
210   };
211   char                          path_buf[ENSC_PI_APPSZ(src_path, src_d_path)];
212   struct stat                   f_stat = { .st_dev = 0 };
213   bool                          is_dir;
214
215   PathInfo_append(&src_path, &src_d_path, path_buf);
216
217   if (handleDirEntry(&src_path, &src_d_path, &is_dir, &f_stat))
218     res = 0;
219
220   if (is_dir) {
221     if (res || global_info.excludes.skip_depth > 0)
222       global_info.excludes.skip_depth++;
223     res = res + visitDir(dirname, &f_stat);
224     if (global_info.excludes.skip_depth > 0)
225       global_info.excludes.skip_depth--;
226   }
227
228   return res;
229 }
230
231 int main(int argc, char *argv[])
232 {
233   struct Arguments      args = {
234     .verbosity          =  0,
235     .xid                = VC_NOCTX,
236     .exclude_list       = NULL,
237   };
238   uint64_t              res;
239   int                   num_args;
240
241   global_args = &args;
242   while (1) {
243     int         c = getopt_long(argc, argv, "+vX:",
244                                 CMDLINE_OPTIONS, 0);
245     if (c==-1) break;
246
247     switch (c) {
248       case CMD_HELP     :  showHelp(1, argv[0], 0);
249       case CMD_VERSION  :  showVersion();
250       case 'v'          :  args.verbosity++; break;
251       case 'X'          :  args.exclude_list = optarg; break;
252       case CMD_XID      :  args.xid = Evc_xidopt2xid(optarg,true); break;
253       default           :
254         WRITE_MSG(2, "Try '");
255         WRITE_STR(2, argv[0]);
256         WRITE_MSG(2, " --help' for more information.\n");
257         return EXIT_FAILURE;
258         break;
259     }
260   }
261
262   num_args = argc - optind;
263   if (num_args < 1) {
264     WRITE_MSG(2, "Source is missing; try '");
265     WRITE_STR(2, argv[0]);
266     WRITE_MSG(2, " --help' for more information.\n");
267     return EXIT_FAILURE;
268   }
269   else if (num_args < 2) {
270     WRITE_MSG(2, "Destination is missing; try '");
271     WRITE_STR(2, argv[0]);
272     WRITE_MSG(2, " --help' for more information.\n");
273     return EXIT_FAILURE;
274   }
275   else if (num_args > 2) {
276     WRITE_MSG(2, "Too many arguments; try '");
277     WRITE_STR(2, argv[0]);
278     WRITE_MSG(2, " --help' for more information.\n");
279     return EXIT_FAILURE;
280   }
281   else if (*argv[optind+1] != '/') {
282     WRITE_MSG(2, "The destination must be an absolute path; try '");
283     WRITE_STR(2, argv[0]);
284     WRITE_MSG(2, " --help' for more information.\n");
285     return EXIT_FAILURE;
286   }
287   ENSC_PI_SETSTR(global_info.src, argv[optind]);
288   ENSC_PI_SETSTR(global_info.dst, argv[optind+1]);
289
290   if (global_args->exclude_list)
291     MatchList_initManually(&global_info.excludes, 0, strdup(argv[optind]),
292                            global_args->exclude_list);
293   else
294     MatchList_init(&global_info.excludes, argv[optind], 0);
295
296   if (global_args->verbosity>3)
297     WRITE_MSG(1, "Starting to traverse directories...\n");
298
299   Echdir(global_info.src.d);
300   res = visitDir("/", 0);
301
302   MatchList_destroy(&global_info.excludes);
303   
304   return res>0 ? 1 : 0;
305 }