chiark / gitweb /
impot: minor cleanups
[elogind.git] / src / import / import-tar.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2015 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <sys/prctl.h>
23 #include <curl/curl.h>
24
25 #include "utf8.h"
26 #include "strv.h"
27 #include "copy.h"
28 #include "btrfs-util.h"
29 #include "util.h"
30 #include "macro.h"
31 #include "mkdir.h"
32 #include "curl-util.h"
33 #include "import-job.h"
34 #include "import-util.h"
35 #include "import-tar.h"
36
37 struct TarImport {
38         sd_event *event;
39         CurlGlue *glue;
40
41         char *image_root;
42
43         ImportJob *tar_job;
44         ImportJob *checksum_job;
45         ImportJob *signature_job;
46
47         TarImportFinished on_finished;
48         void *userdata;
49
50         char *local;
51         bool force_local;
52
53         pid_t tar_pid;
54
55         char *temp_path;
56         char *final_path;
57
58         ImportVerify verify;
59 };
60
61 TarImport* tar_import_unref(TarImport *i) {
62         if (!i)
63                 return NULL;
64
65         if (i->tar_pid > 1) {
66                 (void) kill_and_sigcont(i->tar_pid, SIGKILL);
67                 (void) wait_for_terminate(i->tar_pid, NULL);
68         }
69
70         import_job_unref(i->tar_job);
71         import_job_unref(i->checksum_job);
72         import_job_unref(i->signature_job);
73
74         curl_glue_unref(i->glue);
75         sd_event_unref(i->event);
76
77         if (i->temp_path) {
78                 (void) btrfs_subvol_remove(i->temp_path);
79                 (void) rm_rf_dangerous(i->temp_path, false, true, false);
80                 free(i->temp_path);
81         }
82
83         free(i->final_path);
84         free(i->image_root);
85         free(i->local);
86         free(i);
87
88         return NULL;
89 }
90
91 int tar_import_new(
92                 TarImport **ret,
93                 sd_event *event,
94                 const char *image_root,
95                 TarImportFinished on_finished,
96                 void *userdata) {
97
98         _cleanup_(tar_import_unrefp) TarImport *i = NULL;
99         int r;
100
101         assert(ret);
102         assert(event);
103
104         i = new0(TarImport, 1);
105         if (!i)
106                 return -ENOMEM;
107
108         i->on_finished = on_finished;
109         i->userdata = userdata;
110
111         i->image_root = strdup(image_root ?: "/var/lib/machines");
112         if (!i->image_root)
113                 return -ENOMEM;
114
115         if (event)
116                 i->event = sd_event_ref(event);
117         else {
118                 r = sd_event_default(&i->event);
119                 if (r < 0)
120                         return r;
121         }
122
123         r = curl_glue_new(&i->glue, i->event);
124         if (r < 0)
125                 return r;
126
127         i->glue->on_finished = import_job_curl_on_finished;
128         i->glue->userdata = i;
129
130         *ret = i;
131         i = NULL;
132
133         return 0;
134 }
135
136 static int tar_import_make_local_copy(TarImport *i) {
137         int r;
138
139         assert(i);
140         assert(i->tar_job);
141
142         if (!i->local)
143                 return 0;
144
145         if (!i->final_path) {
146                 r = import_make_path(i->tar_job->url, i->tar_job->etag, i->image_root, ".tar-", NULL, &i->final_path);
147                 if (r < 0)
148                         return log_oom();
149         }
150
151         r = import_make_local_copy(i->final_path, i->image_root, i->local, i->force_local);
152         if (r < 0)
153                 return r;
154
155         return 0;
156 }
157
158 static bool tar_import_is_done(TarImport *i) {
159         assert(i);
160         assert(i->tar_job);
161
162         if (i->tar_job->state != IMPORT_JOB_DONE)
163                 return false;
164         if (i->checksum_job && i->checksum_job->state != IMPORT_JOB_DONE)
165                 return false;
166         if (i->signature_job && i->signature_job->state != IMPORT_JOB_DONE)
167                 return false;
168
169         return true;
170 }
171
172 static void tar_import_job_on_finished(ImportJob *j) {
173         TarImport *i;
174         int r;
175
176         assert(j);
177         assert(j->userdata);
178
179         i = j->userdata;
180         if (j->error != 0) {
181                 if (j == i->checksum_job)
182                         log_error_errno(j->error, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)");
183                 else if (j == i->signature_job)
184                         log_error_errno(j->error, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
185                 else
186                         log_error_errno(j->error, "Failed to retrieve image file. (Wrong URL?)");
187
188                 r = j->error;
189                 goto finish;
190         }
191
192         /* This is invoked if either the download completed
193          * successfully, or the download was skipped because we
194          * already have the etag. */
195
196         if (!tar_import_is_done(i))
197                 return;
198
199         j->disk_fd = safe_close(i->tar_job->disk_fd);
200
201         if (i->tar_pid > 0) {
202                 r = wait_for_terminate_and_warn("tar", i->tar_pid, true);
203                 i->tar_pid = 0;
204                 if (r < 0)
205                         goto finish;
206         }
207
208         if (!i->tar_job->etag_exists) {
209                 /* This is a new download, verify it, and move it into place */
210
211                 r = import_verify(i->tar_job, i->checksum_job, i->signature_job);
212                 if (r < 0)
213                         goto finish;
214
215                 r = import_make_read_only(i->temp_path);
216                 if (r < 0)
217                         goto finish;
218
219                 if (rename(i->temp_path, i->final_path) < 0) {
220                         r = log_error_errno(errno, "Failed to rename to final image name: %m");
221                         goto finish;
222                 }
223
224                 free(i->temp_path);
225                 i->temp_path = NULL;
226         }
227
228         r = tar_import_make_local_copy(i);
229         if (r < 0)
230                 goto finish;
231
232         r = 0;
233
234 finish:
235         if (i->on_finished)
236                 i->on_finished(i, r, i->userdata);
237         else
238                 sd_event_exit(i->event, r);
239 }
240
241 static int tar_import_job_on_open_disk(ImportJob *j) {
242         _cleanup_close_pair_ int pipefd[2] = { -1 , -1 };
243         TarImport *i;
244         int r;
245
246         assert(j);
247         assert(j->userdata);
248
249         i = j->userdata;
250         assert(i->tar_job == j);
251         assert(!i->final_path);
252         assert(!i->temp_path);
253         assert(i->tar_pid <= 0);
254
255         r = import_make_path(j->url, j->etag, i->image_root, ".tar-", NULL, &i->final_path);
256         if (r < 0)
257                 return log_oom();
258
259         r = tempfn_random(i->final_path, &i->temp_path);
260         if (r < 0)
261                 return log_oom();
262
263         mkdir_parents_label(i->temp_path, 0700);
264
265         r = btrfs_subvol_make(i->temp_path);
266         if (r == -ENOTTY) {
267                 if (mkdir(i->temp_path, 0755) < 0)
268                         return log_error_errno(errno, "Failed to create directory %s: %m", i->temp_path);
269         } else if (r < 0)
270                 return log_error_errno(errno, "Failed to create subvolume %s: %m", i->temp_path);
271
272         if (pipe2(pipefd, O_CLOEXEC) < 0)
273                 return log_error_errno(errno, "Failed to create pipe for tar: %m");
274
275         i->tar_pid = fork();
276         if (i->tar_pid < 0)
277                 return log_error_errno(errno, "Failed to fork off tar: %m");
278         if (i->tar_pid == 0) {
279                 int null_fd;
280
281                 /* Child */
282
283                 reset_all_signal_handlers();
284                 reset_signal_mask();
285                 assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
286
287                 pipefd[1] = safe_close(pipefd[1]);
288
289                 if (dup2(pipefd[0], STDIN_FILENO) != STDIN_FILENO) {
290                         log_error_errno(errno, "Failed to dup2() fd: %m");
291                         _exit(EXIT_FAILURE);
292                 }
293
294                 if (pipefd[0] != STDIN_FILENO)
295                         pipefd[0] = safe_close(pipefd[0]);
296
297                 null_fd = open("/dev/null", O_WRONLY|O_NOCTTY);
298                 if (null_fd < 0) {
299                         log_error_errno(errno, "Failed to open /dev/null: %m");
300                         _exit(EXIT_FAILURE);
301                 }
302
303                 if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) {
304                         log_error_errno(errno, "Failed to dup2() fd: %m");
305                         _exit(EXIT_FAILURE);
306                 }
307
308                 if (null_fd != STDOUT_FILENO)
309                         null_fd = safe_close(null_fd);
310
311                 fd_cloexec(STDIN_FILENO, false);
312                 fd_cloexec(STDOUT_FILENO, false);
313                 fd_cloexec(STDERR_FILENO, false);
314
315                 execlp("tar", "tar", "--numeric-owner", "-C", i->temp_path, "-px", NULL);
316                 log_error_errno(errno, "Failed to execute tar: %m");
317                 _exit(EXIT_FAILURE);
318         }
319
320         pipefd[0] = safe_close(pipefd[0]);
321
322         j->disk_fd = pipefd[1];
323         pipefd[1] = -1;
324
325         return 0;
326 }
327
328 int tar_import_pull(TarImport *i, const char *url, const char *local, bool force_local, ImportVerify verify) {
329         int r;
330
331         assert(i);
332
333         if (!http_url_is_valid(url))
334                 return -EINVAL;
335
336         if (local && !machine_name_is_valid(local))
337                 return -EINVAL;
338
339         if (i->tar_job)
340                 return -EBUSY;
341
342         r = free_and_strdup(&i->local, local);
343         if (r < 0)
344                 return r;
345         i->force_local = force_local;
346         i->verify = verify;
347
348         r = import_job_new(&i->tar_job, url, i->glue, i);
349         if (r < 0)
350                 return r;
351
352         i->tar_job->on_finished = tar_import_job_on_finished;
353         i->tar_job->on_open_disk = tar_import_job_on_open_disk;
354         i->tar_job->calc_checksum = verify != IMPORT_VERIFY_NO;
355
356         r = import_find_old_etags(url, i->image_root, DT_DIR, ".tar-", NULL, &i->tar_job->old_etags);
357         if (r < 0)
358                 return r;
359
360         r = import_make_verification_jobs(&i->checksum_job, &i->signature_job, verify, url, i->glue, tar_import_job_on_finished, i);
361         if (r < 0)
362                 return r;
363
364         r = import_job_begin(i->tar_job);
365         if (r < 0)
366                 return r;
367
368         if (i->checksum_job) {
369                 r = import_job_begin(i->checksum_job);
370                 if (r < 0)
371                         return r;
372         }
373
374         if (i->signature_job) {
375                 r = import_job_begin(i->signature_job);
376                 if (r < 0)
377                         return r;
378         }
379
380         return 0;
381 }