proto: Fix FileIn ops that abort during the chunk upload stage.
[libguestfs.git] / daemon / upload.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 <stdint.h>
24 #include <string.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29
30 #include "guestfs_protocol.h"
31 #include "daemon.h"
32 #include "actions.h"
33
34 struct write_cb_data {
35   int fd;                       /* file descriptor */
36   uint64_t written;             /* bytes written so far */
37 };
38
39 static int
40 write_cb (void *data_vp, const void *buf, size_t len)
41 {
42   struct write_cb_data *data = data_vp;
43   int r;
44
45   r = xwrite (data->fd, buf, len);
46   if (r == -1)
47     return -1;
48
49   data->written += len;
50
51   if (progress_hint > 0)
52     notify_progress (data->written, progress_hint);
53
54   return 0;
55 }
56
57 /* Has one FileIn parameter. */
58 static int
59 upload (const char *filename, int flags, int64_t offset)
60 {
61   struct write_cb_data data = { .written = 0 };
62   int err, r, is_dev;
63
64   is_dev = STRPREFIX (filename, "/dev/");
65
66   if (!is_dev) CHROOT_IN;
67   data.fd = open (filename, flags, 0666);
68   if (!is_dev) CHROOT_OUT;
69   if (data.fd == -1) {
70     err = errno;
71     r = cancel_receive ();
72     errno = err;
73     reply_with_perror ("%s", filename);
74     return -1;
75   }
76
77   if (offset) {
78     if (lseek (data.fd, offset, SEEK_SET) == -1) {
79       err = errno;
80       r = cancel_receive ();
81       errno = err;
82       reply_with_perror ("lseek: %s", filename);
83       return -1;
84     }
85   }
86
87   r = receive_file (write_cb, &data.fd);
88   if (r == -1) {                /* write error */
89     err = errno;
90     r = cancel_receive ();
91     errno = err;
92     reply_with_error ("write error: %s", filename);
93     close (data.fd);
94     return -1;
95   }
96   if (r == -2) {                /* cancellation from library */
97     close (data.fd);
98     /* Do NOT send any error. */
99     return -1;
100   }
101
102   if (close (data.fd) == -1) {
103     err = errno;
104     if (r == -1)                /* if r == 0, file transfer ended already */
105       r = cancel_receive ();
106     errno = err;
107     reply_with_perror ("close: %s", filename);
108     return -1;
109   }
110
111   return 0;
112 }
113
114 /* Has one FileIn parameter. */
115 int
116 do_upload (const char *filename)
117 {
118   return upload (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0);
119 }
120
121 /* Has one FileIn parameter. */
122 int
123 do_upload_offset (const char *filename, int64_t offset)
124 {
125   if (offset < 0) {
126     reply_with_perror ("%s: offset in file is negative", filename);
127     return -1;
128   }
129
130   return upload (filename, O_WRONLY|O_CREAT|O_NOCTTY, offset);
131 }
132
133 /* Has one FileOut parameter. */
134 int
135 do_download (const char *filename)
136 {
137   int fd, r, is_dev;
138   char buf[GUESTFS_MAX_CHUNK_SIZE];
139
140   is_dev = STRPREFIX (filename, "/dev/");
141
142   if (!is_dev) CHROOT_IN;
143   fd = open (filename, O_RDONLY);
144   if (!is_dev) CHROOT_OUT;
145   if (fd == -1) {
146     reply_with_perror ("%s", filename);
147     return -1;
148   }
149
150   /* Calculate the size of the file or device for notification messages. */
151   uint64_t total, sent = 0;
152   if (!is_dev) {
153     struct stat statbuf;
154     if (fstat (fd, &statbuf) == -1) {
155       reply_with_perror ("%s", filename);
156       close (fd);
157       return -1;
158     }
159     total = statbuf.st_size;
160   } else {
161     int64_t size = do_blockdev_getsize64 (filename);
162     if (size == -1) {
163       /* do_blockdev_getsize64 has already sent a reply. */
164       close (fd);
165       return -1;
166     }
167     total = (uint64_t) size;
168   }
169
170   /* Now we must send the reply message, before the file contents.  After
171    * this there is no opportunity in the protocol to send any error
172    * message back.  Instead we can only cancel the transfer.
173    */
174   reply (NULL, NULL);
175
176   while ((r = read (fd, buf, sizeof buf)) > 0) {
177     if (send_file_write (buf, r) < 0) {
178       close (fd);
179       return -1;
180     }
181
182     sent += r;
183     notify_progress (sent, total);
184   }
185
186   if (r == -1) {
187     perror (filename);
188     send_file_end (1);          /* Cancel. */
189     close (fd);
190     return -1;
191   }
192
193   if (close (fd) == -1) {
194     perror (filename);
195     send_file_end (1);          /* Cancel. */
196     return -1;
197   }
198
199   if (send_file_end (0))        /* Normal end of file. */
200     return -1;
201
202   return 0;
203 }
204
205 /* Has one FileOut parameter. */
206 int
207 do_download_offset (const char *filename, int64_t offset, int64_t size)
208 {
209   int fd, r, is_dev;
210   char buf[GUESTFS_MAX_CHUNK_SIZE];
211
212   if (offset < 0) {
213     reply_with_perror ("%s: offset in file is negative", filename);
214     return -1;
215   }
216
217   if (size < 0) {
218     reply_with_perror ("%s: size is negative", filename);
219     return -1;
220   }
221   uint64_t usize = (uint64_t) size;
222
223   is_dev = STRPREFIX (filename, "/dev/");
224
225   if (!is_dev) CHROOT_IN;
226   fd = open (filename, O_RDONLY);
227   if (!is_dev) CHROOT_OUT;
228   if (fd == -1) {
229     reply_with_perror ("%s", filename);
230     return -1;
231   }
232
233   if (offset) {
234     if (lseek (fd, offset, SEEK_SET) == -1) {
235       reply_with_perror ("lseek: %s", filename);
236       return -1;
237     }
238   }
239
240   uint64_t total = usize, sent = 0;
241
242   /* Now we must send the reply message, before the file contents.  After
243    * this there is no opportunity in the protocol to send any error
244    * message back.  Instead we can only cancel the transfer.
245    */
246   reply (NULL, NULL);
247
248   while (usize > 0) {
249     r = read (fd, buf, usize > sizeof buf ? sizeof buf : usize);
250     if (r == -1) {
251       perror (filename);
252       send_file_end (1);        /* Cancel. */
253       close (fd);
254       return -1;
255     }
256
257     if (r == 0)
258       /* The documentation leaves this case undefined.  Currently we
259        * just read fewer bytes than requested.
260        */
261       break;
262
263     if (send_file_write (buf, r) < 0) {
264       close (fd);
265       return -1;
266     }
267
268     sent += r;
269     usize -= r;
270     notify_progress (sent, total);
271   }
272
273   if (close (fd) == -1) {
274     perror (filename);
275     send_file_end (1);          /* Cancel. */
276     return -1;
277   }
278
279   if (send_file_end (0))        /* Normal end of file. */
280     return -1;
281
282   return 0;
283 }