todo: Remove obsolete items from TODO file.
[libguestfs.git] / fish / copy.c
1 /* guestfish - the filesystem interactive shell
2  * Copyright (C) 2010 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
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., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <sys/wait.h>
28
29 #include "fish.h"
30
31 static int make_tar_from_local (const char *local);
32 static int make_tar_output (const char *local, const char *basename);
33 static int split_path (char *buf, size_t buf_size, const char *path, const char **dirname, const char **basename);
34
35 int
36 run_copy_in (const char *cmd, size_t argc, char *argv[])
37 {
38   if (argc < 2) {
39     fprintf (stderr,
40              _("use 'copy-in <local> [<local>...] <remotedir>' to copy files into the image\n"));
41     return -1;
42   }
43
44   /* Remote directory is always the last arg. */
45   const char *remote = argv[argc-1];
46   int nr_locals = argc-1;
47
48   int remote_is_dir = guestfs_is_dir (g, remote);
49   if (remote_is_dir == -1)
50     return -1;
51
52   if (!remote_is_dir) {
53     fprintf (stderr, _("copy-in: target '%s' is not a directory\n"), remote);
54     return -1;
55   }
56
57   /* Upload each local one at a time using tar-in. */
58   int i;
59   for (i = 0; i < nr_locals; ++i) {
60     int fd = make_tar_from_local (argv[i]);
61     if (fd == -1)
62       return -1;
63
64     char fdbuf[64];
65     snprintf (fdbuf, sizeof fdbuf, "/dev/fd/%d", fd);
66
67     int r = guestfs_tar_in (g, fdbuf, remote);
68
69     if (close (fd) == -1) {
70       perror ("close (tar-from-local subprocess)");
71       r = -1;
72     }
73
74     int status;
75     if (wait (&status) == -1) {
76       perror ("wait (tar-from-local subprocess)");
77       return -1;
78     }
79     if (!(WIFEXITED (status) && WEXITSTATUS (status) == 0))
80       return -1;
81
82     if (r == -1)
83       return -1;
84   }
85
86   return 0;
87 }
88
89 static void tar_create (const char *dir, const char *path) __attribute__((noreturn));
90
91 /* This creates a subprocess which feeds a tar file back to the
92  * main guestfish process.
93  */
94 static int
95 make_tar_from_local (const char *local)
96 {
97   int fd[2];
98
99   if (pipe (fd) == -1) {
100     perror ("pipe");
101     return -1;
102   }
103
104   pid_t pid = fork ();
105   if (pid == -1) {
106     perror ("fork");
107     return -1;
108   }
109
110   if (pid > 0) {                /* Parent */
111     close (fd[1]);
112     return fd[0];
113   }
114
115   /* Child. */
116   close (fd[0]);
117   dup2 (fd[1], 1);
118   close (fd[1]);
119
120   char buf[PATH_MAX];
121   const char *dirname, *basename;
122   if (split_path (buf, sizeof buf, local, &dirname, &basename) == -1)
123     _exit (EXIT_FAILURE);
124
125   tar_create (dirname, basename);
126 }
127
128 /* Split path into directory name and base name, using the buffer
129  * provided as a working area.  If there is no directory name
130  * (eg. path == "/") then this can return dirname as NULL.
131  */
132 static int
133 split_path (char *buf, size_t buf_size,
134             const char *path, const char **dirname, const char **basename)
135 {
136   size_t len = strlen (path);
137   if (len == 0 || len > buf_size - 1) {
138     fprintf (stderr, _("error: argument is zero length or longer than maximum permitted\n"));
139     return -1;
140   }
141
142   strcpy (buf, path);
143
144   if (len >= 2 && buf[len-1] == '/') {
145     buf[len-1] = '\0';
146     len--;
147   }
148
149   char *p = strrchr (buf, '/');
150   if (p && p > buf) {           /* "foo/bar" */
151     *p = '\0';
152     p++;
153     if (dirname) *dirname = buf;
154     if (basename) *basename = p;
155   } else if (p && p == buf) {   /* "/foo" */
156     if (dirname) *dirname = "/";
157     if (basename) *basename = buf+1;
158   } else {
159     if (dirname) *dirname = NULL;
160     if (basename) *basename = buf;
161   }
162
163   return 0;
164 }
165
166 static void
167 tar_create (const char *dir, const char *path)
168 {
169   if (dir)
170     execlp ("tar", "tar", "-C", dir, "-cf", "-", path, NULL);
171   else
172     execlp ("tar", "tar", "-cf", "-", path, NULL);
173
174   perror ("execlp: tar");
175   _exit (EXIT_FAILURE);
176 }
177
178 int
179 run_copy_out (const char *cmd, size_t argc, char *argv[])
180 {
181   if (argc < 2) {
182     fprintf (stderr,
183              _("use 'copy-out <remote> [<remote>...] <localdir>' to copy files out of the image\n"));
184     return -1;
185   }
186
187   /* Local directory is always the last arg. */
188   const char *local = argv[argc-1];
189   int nr_remotes = argc-1;
190
191   struct stat statbuf;
192   if (stat (local, &statbuf) == -1 ||
193       ! (S_ISDIR (statbuf.st_mode))) {
194     fprintf (stderr, _("copy-in: target '%s' is not a directory\n"), local);
195     return -1;
196   }
197
198   /* Download each remote one at a time using tar-out. */
199   int i, r;
200   for (i = 0; i < nr_remotes; ++i) {
201     /* If the remote is a file, download it.  If it's a directory,
202      * create the directory in local first before using tar-out.
203      */
204     r = guestfs_is_file (g, argv[i]);
205     if (r == -1)
206       return -1;
207     if (r == 1) {               /* is file */
208       char buf[PATH_MAX];
209       const char *basename;
210       if (split_path (buf, sizeof buf, argv[i], NULL, &basename) == -1)
211         return -1;
212
213       char filename[PATH_MAX];
214       snprintf (filename, sizeof filename, "%s/%s", local, basename);
215       if (guestfs_download (g, argv[i], filename) == -1)
216         return -1;
217     }
218     else {                      /* not a regular file */
219       r = guestfs_is_dir (g, argv[i]);
220       if (r == -1)
221         return -1;
222
223       if (r == 0) {
224         fprintf (stderr, _("copy-out: '%s' is not a file or directory\n"),
225                  argv[i]);
226         return -1;
227       }
228
229       char buf[PATH_MAX];
230       const char *basename;
231       if (split_path (buf, sizeof buf, argv[i], NULL, &basename) == -1)
232         return -1;
233
234       int fd = make_tar_output (local, basename);
235       if (fd == -1)
236         return -1;
237
238       char fdbuf[64];
239       snprintf (fdbuf, sizeof fdbuf, "/dev/fd/%d", fd);
240
241       int r = guestfs_tar_out (g, argv[i], fdbuf);
242
243       if (close (fd) == -1) {
244         perror ("close (tar-output subprocess)");
245         r = -1;
246       }
247
248       int status;
249       if (wait (&status) == -1) {
250         perror ("wait (tar-output subprocess)");
251         return -1;
252       }
253       if (!(WIFEXITED (status) && WEXITSTATUS (status) == 0))
254         return -1;
255
256       if (r == -1)
257         return -1;
258     }
259   }
260
261   return 0;
262 }
263
264 /* This creates a subprocess which takes a tar file from the main
265  * guestfish process and unpacks it into the 'local/basename'
266  * directory.
267  */
268 static int
269 make_tar_output (const char *local, const char *basename)
270 {
271   int fd[2];
272
273   if (pipe (fd) == -1) {
274     perror ("pipe");
275     return -1;
276   }
277
278   pid_t pid = fork ();
279   if (pid == -1) {
280     perror ("fork");
281     return -1;
282   }
283
284   if (pid > 0) {                /* Parent */
285     close (fd[0]);
286     return fd[1];
287   }
288
289   /* Child. */
290   close (fd[1]);
291   dup2 (fd[0], 0);
292   close (fd[0]);
293
294   if (chdir (local) == -1) {
295     perror (local);
296     _exit (EXIT_FAILURE);
297   }
298
299   mkdir (basename, 0777);
300
301   if (chdir (basename) == -1) {
302     perror (basename);
303     _exit (EXIT_FAILURE);
304   }
305
306   execlp ("tar", "tar", "-xf", "-", NULL);
307   perror ("execlp: tar");
308   _exit (EXIT_FAILURE);
309 }