todo: Remove discussion of copy-in/copy-out.
[libguestfs.git] / daemon / tar.c
1 /* libguestfs - the guestfsd daemon
2  * Copyright (C) 2009 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 <fcntl.h>
25
26 #include "read-file.h"
27
28 #include "../src/guestfs_protocol.h"
29 #include "daemon.h"
30 #include "actions.h"
31 #include "optgroups.h"
32
33 int
34 optgroup_xz_available (void)
35 {
36   int r = access ("/usr/bin/xz", X_OK);
37   return r == 0;
38 }
39
40 /* Redirect errors from the tar command to the error file, then
41  * provide functions for reading it in.  We overwrite the file each
42  * time, and since it's small and stored on the appliance we don't
43  * bother to delete it.
44  */
45 static const char *error_file = "/tmp/error";
46
47 static char *
48 read_error_file (void)
49 {
50   size_t len;
51   char *str = read_file (error_file, &len);
52   if (str == NULL) {
53     str = strdup ("(no error)");
54     if (str == NULL) {
55       perror ("strdup");
56       exit (EXIT_FAILURE);
57     }
58     len = strlen (str);
59   }
60
61   /* Remove trailing \n character if any. */
62   if (len > 0 && str[len-1] == '\n')
63     str[--len] = '\0';
64
65   return str;                   /* caller frees */
66 }
67
68 static int
69 write_cb (void *fd_ptr, const void *buf, size_t len)
70 {
71   int fd = *(int *)fd_ptr;
72   return xwrite (fd, buf, len);
73 }
74
75 /* Has one FileIn parameter. */
76 static int
77 do_tXz_in (const char *dir, const char *filter)
78 {
79   int err, r;
80   FILE *fp;
81   char *cmd;
82
83   /* "tar -C /sysroot%s -xf -" but we have to quote the dir. */
84   if (asprintf_nowarn (&cmd, "tar -C %R -%sxf - 2> %s",
85                        dir, filter, error_file) == -1) {
86     err = errno;
87     r = cancel_receive ();
88     errno = err;
89     if (r != -2) reply_with_perror ("asprintf");
90     return -1;
91   }
92
93   if (verbose)
94     fprintf (stderr, "%s\n", cmd);
95
96   fp = popen (cmd, "w");
97   if (fp == NULL) {
98     err = errno;
99     r = cancel_receive ();
100     errno = err;
101     if (r != -2) reply_with_perror ("%s", cmd);
102     free (cmd);
103     return -1;
104   }
105   free (cmd);
106
107   /* The semantics of fwrite are too undefined, so write to the
108    * file descriptor directly instead.
109    */
110   int fd = fileno (fp);
111
112   r = receive_file (write_cb, &fd);
113   if (r == -1) {                /* write error */
114     if (cancel_receive () != -2) {
115       char *errstr = read_error_file ();
116       reply_with_error ("write error on directory: %s: %s", dir, errstr);
117       free (errstr);
118     }
119     pclose (fp);
120     return -1;
121   }
122   if (r == -2) {                /* cancellation from library */
123     pclose (fp);
124     /* Do NOT send any error. */
125     return -1;
126   }
127
128   if (pclose (fp) != 0) {
129     if (r == -1)                /* if r == 0, file transfer ended already */
130       r = cancel_receive ();
131     if (r != -2) {
132       char *errstr = read_error_file ();
133       reply_with_error ("tar subcommand failed on directory: %s: %s",
134                         dir, errstr);
135       free (errstr);
136     }
137     return -1;
138   }
139
140   return 0;
141 }
142
143 /* Has one FileIn parameter. */
144 int
145 do_tar_in (const char *dir)
146 {
147   return do_tXz_in (dir, "");
148 }
149
150 /* Has one FileIn parameter. */
151 int
152 do_tgz_in (const char *dir)
153 {
154   return do_tXz_in (dir, "z");
155 }
156
157 /* Has one FileIn parameter. */
158 int
159 do_txz_in (const char *dir)
160 {
161   return do_tXz_in (dir, "J");
162 }
163
164 /* Has one FileOut parameter. */
165 static int
166 do_tXz_out (const char *dir, const char *filter)
167 {
168   int r;
169   FILE *fp;
170   char *cmd;
171   char buf[GUESTFS_MAX_CHUNK_SIZE];
172
173   /* "tar -C /sysroot%s -zcf - ." but we have to quote the dir. */
174   if (asprintf_nowarn (&cmd, "tar -C %R -%scf - .", dir, filter) == -1) {
175     reply_with_perror ("asprintf");
176     return -1;
177   }
178
179   if (verbose)
180     fprintf (stderr, "%s\n", cmd);
181
182   fp = popen (cmd, "r");
183   if (fp == NULL) {
184     reply_with_perror ("%s", cmd);
185     free (cmd);
186     return -1;
187   }
188   free (cmd);
189
190   /* Now we must send the reply message, before the file contents.  After
191    * this there is no opportunity in the protocol to send any error
192    * message back.  Instead we can only cancel the transfer.
193    */
194   reply (NULL, NULL);
195
196   while ((r = fread (buf, 1, sizeof buf, fp)) > 0) {
197     if (send_file_write (buf, r) < 0) {
198       pclose (fp);
199       return -1;
200     }
201   }
202
203   if (ferror (fp)) {
204     perror (dir);
205     send_file_end (1);          /* Cancel. */
206     pclose (fp);
207     return -1;
208   }
209
210   if (pclose (fp) != 0) {
211     perror (dir);
212     send_file_end (1);          /* Cancel. */
213     return -1;
214   }
215
216   if (send_file_end (0))        /* Normal end of file. */
217     return -1;
218
219   return 0;
220 }
221
222 /* Has one FileOut parameter. */
223 int
224 do_tar_out (const char *dir)
225 {
226   return do_tXz_out (dir, "");
227 }
228
229 /* Has one FileOut parameter. */
230 int
231 do_tgz_out (const char *dir)
232 {
233   return do_tXz_out (dir, "z");
234 }
235
236 /* Has one FileOut parameter. */
237 int
238 do_txz_out (const char *dir)
239 {
240   return do_tXz_out (dir, "J");
241 }