Add progress notification messages to upload and upload-offset APIs.
[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     if (r != -2) 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       if (r != -2) 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     if (r != -2) 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     if (r != -2)
108       reply_with_perror ("close: %s", filename);
109     return -1;
110   }
111
112   return 0;
113 }
114
115 /* Has one FileIn parameter. */
116 int
117 do_upload (const char *filename)
118 {
119   return upload (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0);
120 }
121
122 /* Has one FileIn parameter. */
123 int
124 do_upload_offset (const char *filename, int64_t offset)
125 {
126   if (offset < 0) {
127     reply_with_perror ("%s: offset in file is negative", filename);
128     return -1;
129   }
130
131   return upload (filename, O_WRONLY|O_CREAT|O_NOCTTY, offset);
132 }
133
134 /* Has one FileOut parameter. */
135 int
136 do_download (const char *filename)
137 {
138   int fd, r, is_dev;
139   char buf[GUESTFS_MAX_CHUNK_SIZE];
140
141   is_dev = STRPREFIX (filename, "/dev/");
142
143   if (!is_dev) CHROOT_IN;
144   fd = open (filename, O_RDONLY);
145   if (!is_dev) CHROOT_OUT;
146   if (fd == -1) {
147     reply_with_perror ("%s", filename);
148     return -1;
149   }
150
151   /* Calculate the size of the file or device for notification messages. */
152   uint64_t total, sent = 0;
153   if (!is_dev) {
154     struct stat statbuf;
155     if (fstat (fd, &statbuf) == -1) {
156       reply_with_perror ("%s", filename);
157       close (fd);
158       return -1;
159     }
160     total = statbuf.st_size;
161   } else {
162     int64_t size = do_blockdev_getsize64 (filename);
163     if (size == -1) {
164       /* do_blockdev_getsize64 has already sent a reply. */
165       close (fd);
166       return -1;
167     }
168     total = (uint64_t) size;
169   }
170
171   /* Now we must send the reply message, before the file contents.  After
172    * this there is no opportunity in the protocol to send any error
173    * message back.  Instead we can only cancel the transfer.
174    */
175   reply (NULL, NULL);
176
177   while ((r = read (fd, buf, sizeof buf)) > 0) {
178     if (send_file_write (buf, r) < 0) {
179       close (fd);
180       return -1;
181     }
182
183     sent += r;
184     notify_progress (sent, total);
185   }
186
187   if (r == -1) {
188     perror (filename);
189     send_file_end (1);          /* Cancel. */
190     close (fd);
191     return -1;
192   }
193
194   if (close (fd) == -1) {
195     perror (filename);
196     send_file_end (1);          /* Cancel. */
197     return -1;
198   }
199
200   if (send_file_end (0))        /* Normal end of file. */
201     return -1;
202
203   return 0;
204 }
205
206 /* Has one FileOut parameter. */
207 int
208 do_download_offset (const char *filename, int64_t offset, int64_t size)
209 {
210   int fd, r, is_dev;
211   char buf[GUESTFS_MAX_CHUNK_SIZE];
212
213   if (offset < 0) {
214     reply_with_perror ("%s: offset in file is negative", filename);
215     return -1;
216   }
217
218   if (size < 0) {
219     reply_with_perror ("%s: size is negative", filename);
220     return -1;
221   }
222   uint64_t usize = (uint64_t) size;
223
224   is_dev = STRPREFIX (filename, "/dev/");
225
226   if (!is_dev) CHROOT_IN;
227   fd = open (filename, O_RDONLY);
228   if (!is_dev) CHROOT_OUT;
229   if (fd == -1) {
230     reply_with_perror ("%s", filename);
231     return -1;
232   }
233
234   if (offset) {
235     if (lseek (fd, offset, SEEK_SET) == -1) {
236       reply_with_perror ("lseek: %s", filename);
237       return -1;
238     }
239   }
240
241   uint64_t total = usize, sent = 0;
242
243   /* Now we must send the reply message, before the file contents.  After
244    * this there is no opportunity in the protocol to send any error
245    * message back.  Instead we can only cancel the transfer.
246    */
247   reply (NULL, NULL);
248
249   while (usize > 0) {
250     r = read (fd, buf, usize > sizeof buf ? sizeof buf : usize);
251     if (r == -1) {
252       perror (filename);
253       send_file_end (1);        /* Cancel. */
254       close (fd);
255       return -1;
256     }
257
258     if (r == 0)
259       /* The documentation leaves this case undefined.  Currently we
260        * just read fewer bytes than requested.
261        */
262       break;
263
264     if (send_file_write (buf, r) < 0) {
265       close (fd);
266       return -1;
267     }
268
269     sent += r;
270     usize -= r;
271     notify_progress (sent, total);
272   }
273
274   if (close (fd) == -1) {
275     perror (filename);
276     send_file_end (1);          /* Cancel. */
277     return -1;
278   }
279
280   if (send_file_end (0))        /* Normal end of file. */
281     return -1;
282
283   return 0;
284 }