discover: Add load_url_cancel
[petitboot] / discover / paths.c
1 #define _GNU_SOURCE
2
3 #include <assert.h>
4 #include <string.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7
8 #include <talloc/talloc.h>
9 #include <system/system.h>
10 #include <process/process.h>
11 #include <url/url.h>
12 #include <log/log.h>
13
14 #include "paths.h"
15
16 #define DEVICE_MOUNT_BASE (LOCAL_STATE_DIR "/petitboot/mnt")
17
18 struct load_task {
19         struct pb_url           *url;
20         struct process          *process;
21         struct load_url_result  *result;
22         bool                    async;
23         load_url_complete       async_cb;
24         void                    *async_data;
25 };
26
27 const char *mount_base(void)
28 {
29         return DEVICE_MOUNT_BASE;
30 }
31
32 char *join_paths(void *alloc_ctx, const char *a, const char *b)
33 {
34         char *full_path;
35
36         full_path = talloc_array(alloc_ctx, char, strlen(a) + strlen(b) + 2);
37
38         strcpy(full_path, a);
39         if (b[0] != '/' && a[strlen(a) - 1] != '/')
40                 strcat(full_path, "/");
41         strcat(full_path, b);
42
43         return full_path;
44 }
45
46
47 static char *local_name(void *ctx)
48 {
49         char *tmp, *ret;
50
51         tmp = tempnam(NULL, "pb-");
52
53         if (!tmp)
54                 return NULL;
55
56         ret = talloc_strdup(ctx, tmp);
57         free(tmp);
58
59         return ret;
60 }
61
62 static void load_url_result_cleanup_local(struct load_url_result *result)
63 {
64         if (result->cleanup_local)
65                 unlink(result->local);
66 }
67
68 static void load_url_process_exit(struct process *process)
69 {
70         struct load_task *task = process->data;
71         struct load_url_result *result;
72         load_url_complete cb;
73         void *data;
74
75         pb_debug("The download client '%s' [pid %d, url %s] exited, rc %d\n",
76                         process->path, process->pid, task->url->full,
77                         process->exit_status);
78
79         result = task->result;
80         data = task->async_data;
81         cb = task->async_cb;
82
83         if (result->status == LOAD_CANCELLED) {
84                 load_url_result_cleanup_local(result);
85         } else if (process->exit_status == 0) {
86                 result->status = LOAD_OK;
87         } else {
88                 result->status = LOAD_ERROR;
89                 load_url_result_cleanup_local(result);
90         }
91
92         /* The load callback may well free the ctx, which was the
93          * talloc parent of the task. Therefore, we want to do our cleanup
94          * before invoking it
95          */
96         process_release(process);
97         talloc_free(task);
98         result->task = NULL;
99
100         cb(result, data);
101 }
102
103 static void load_process_to_local_file(struct load_task *task,
104                 const char **argv, int argv_local_idx)
105 {
106         int rc;
107
108         task->result->local = local_name(task->result);
109         if (!task->result->local) {
110                 task->result->status = LOAD_ERROR;
111                 return;
112         }
113         task->result->cleanup_local = true;
114
115         if (argv_local_idx)
116                 argv[argv_local_idx] = task->result->local;
117
118         task->process->argv = argv;
119         task->process->path = argv[0];
120
121         if (task->async) {
122                 rc = process_run_async(task->process);
123                 if (rc) {
124                         process_release(task->process);
125                         task->process = NULL;
126                 }
127                 task->result->status = rc ? LOAD_ERROR : LOAD_ASYNC;
128         } else {
129                 rc = process_run_sync(task->process);
130                 task->result->status = rc ? LOAD_ERROR : LOAD_OK;
131                 process_release(task->process);
132                 task->process = NULL;
133         }
134 }
135
136 /**
137  * pb_load_nfs - Create a mountpoint, set the local file within that
138  * mountpoint, and run the appropriate mount command
139  */
140
141 static void load_nfs(struct load_task *task)
142 {
143         char *mountpoint, *opts;
144         int rc;
145         const char *argv[] = {
146                         pb_system_apps.mount,
147                         "-t", "nfs",
148                         NULL,                   /* 3: opts */
149                         task->url->host,
150                         task->url->dir,
151                         NULL,                   /* 6: mountpoint */
152                         NULL,
153         };
154
155         task->result->status = LOAD_ERROR;
156         mountpoint = local_name(task->result);
157         if (!mountpoint)
158                 return;
159         task->result->cleanup_local = true;
160         argv[6] = mountpoint;
161
162         rc = pb_mkdir_recursive(mountpoint);
163         if (rc)
164                 return;
165
166         opts = talloc_strdup(NULL, "ro,nolock,nodiratime");
167         argv[3] = opts;
168
169         if (task->url->port)
170                 opts = talloc_asprintf_append(opts, ",port=%s",
171                                                 task->url->port);
172
173         task->result->local = talloc_asprintf(task->result, "%s/%s",
174                                                         mountpoint,
175                                                         task->url->path);
176
177         task->process->path = pb_system_apps.mount;
178         task->process->argv = argv;
179
180         if (task->async) {
181                 rc = process_run_async(task->process);
182                 if (rc) {
183                         process_release(task->process);
184                         task->process = NULL;
185                 }
186                 task->result->status = rc ? LOAD_ERROR : LOAD_ASYNC;
187         } else {
188                 rc = process_run_sync(task->process);
189                 task->result->status = rc ? LOAD_ERROR : LOAD_OK;
190                 process_release(task->process);
191                 task->process = NULL;
192         }
193
194         talloc_free(opts);
195 }
196
197 static void load_sftp(struct load_task *task)
198 {
199         const char *argv[] = {
200                         pb_system_apps.sftp,
201                         NULL,           /* 1: host:path */
202                         NULL,           /* 2: local file */
203                         NULL,
204         };
205
206         argv[1] = talloc_asprintf(task, "%s:%s",
207                                 task->url->host, task->url->path);
208         load_process_to_local_file(task, argv, 2);
209 }
210
211 static enum tftp_type check_tftp_type(void *ctx)
212 {
213         const char *argv[] = { pb_system_apps.tftp, "-V", NULL };
214         struct process *process;
215         enum tftp_type type;
216
217         process = process_create(ctx);
218         process->path = pb_system_apps.tftp;
219         process->argv = argv;
220         process->keep_stdout = true;
221         process_run_sync(process);
222
223         if (!process->stdout_buf || process->stdout_len == 0) {
224                 pb_log("Can't check TFTP client type!\n");
225                 type = TFTP_TYPE_BROKEN;
226
227         } else if (memmem(process->stdout_buf, process->stdout_len,
228                                 "tftp-hpa", strlen("tftp-hpa"))) {
229                 pb_debug("Found TFTP client type: tftp-hpa\n");
230                 type = TFTP_TYPE_HPA;
231
232         } else if (memmem(process->stdout_buf, process->stdout_len,
233                                 "BusyBox", strlen("BusyBox"))) {
234                 pb_debug("Found TFTP client type: BusyBox tftp\n");
235                 type = TFTP_TYPE_BUSYBOX;
236
237         } else {
238                 pb_log("Unknown TFTP client type!\n");
239                 type = TFTP_TYPE_BROKEN;
240         }
241
242         process_release(process);
243         return type;
244 }
245
246 static void load_tftp(struct load_task *task)
247 {
248         const char *port = "69";
249         const char *argv[10] = {
250                 pb_system_apps.tftp,
251         };
252
253         if (task->url->port)
254                 port = task->url->port;
255
256         if (tftp_type == TFTP_TYPE_UNKNOWN)
257                 tftp_type = check_tftp_type(task);
258
259         if (tftp_type == TFTP_TYPE_BUSYBOX) {
260                 argv[1] = "-g";
261                 argv[2] = "-l";
262                 argv[3] = NULL; /* 3: local file */
263                 argv[4] = "-r";
264                 argv[5] = task->url->path;
265                 argv[6] = task->url->host;
266                 argv[7] = port;
267                 argv[8] = NULL;
268
269                 load_process_to_local_file(task, argv, 3);
270
271         } else if (tftp_type == TFTP_TYPE_HPA) {
272                 argv[1] = "-m";
273                 argv[2] = "binary";
274                 argv[3] = task->url->host;
275                 argv[4] = port;
276                 argv[5] = "-c";
277                 argv[6] = "get";
278                 argv[7] = task->url->path;
279                 argv[8] = NULL; /* 8: local file */
280                 argv[9] = NULL;
281                 load_process_to_local_file(task, argv, 8);
282
283         } else
284                 task->result->status = LOAD_ERROR;
285 }
286
287 enum wget_flags {
288         wget_empty = 0,
289         wget_no_check_certificate = 1,
290 };
291
292 /**
293  * pb_load_wget - Loads a remote file via wget and returns the local file path.
294  *
295  * Returns the local file path in a talloc'ed character string on success,
296  * or NULL on error.
297  */
298
299 static void load_wget(struct load_task *task, int flags)
300 {
301         const char *argv[] = {
302                 pb_system_apps.wget,
303                 "-O",
304                 NULL, /* 2: local file */
305                 NULL,
306                 NULL,
307                 NULL,
308         };
309         int i;
310
311         i = 3;
312 #if !defined(DEBUG)
313         argv[i++] = "--quiet";
314 #endif
315         if (flags & wget_no_check_certificate)
316                 argv[i++] = "--no-check-certificate";
317
318         argv[i] = task->url->full;
319
320         load_process_to_local_file(task, argv, 2);
321 }
322
323 /* Although we don't need to load anything for a local path (we just return
324  * the path from the file:// URL), the other load helpers will error-out on
325  * non-existant files. So, do the same here with an access() check on the local
326  * filename.
327  */
328 static void load_local(struct load_task *task)
329 {
330         int rc;
331
332         rc = access(task->url->path, F_OK);
333         if (rc) {
334                 task->result->status = LOAD_ERROR;
335         } else {
336                 task->result->local = talloc_strdup(task->result,
337                                                     task->url->path);
338                 task->result->status = LOAD_OK;
339         }
340 }
341
342 /**
343  * load_url - Loads a (possibly) remote URL and returns the local file
344  * path.
345  * @ctx: The talloc context to associate with the returned string.
346  * @url: The remote file URL.
347  * @tempfile: An optional variable pointer to be set when a temporary local
348  *  file is created.
349  * @url_cb: An optional callback pointer if the caller wants to load url
350  *  asynchronously.
351  *
352  * Returns the local file path in a talloc'ed character string on success,
353  * or NULL on error.
354  */
355
356 struct load_url_result *load_url_async(void *ctx, struct pb_url *url,
357                 load_url_complete async_cb, void *async_data)
358 {
359         struct load_url_result *result;
360         struct load_task *task;
361
362         if (!url)
363                 return NULL;
364
365         task = talloc_zero(ctx, struct load_task);
366         task->url = url;
367         task->async = async_cb != NULL;
368         task->result = talloc_zero(ctx, struct load_url_result);
369         task->result->task = task;
370         task->process = process_create(task);
371         if (task->async) {
372                 task->async_cb = async_cb;
373                 task->async_data = async_data;
374                 task->process->exit_cb = load_url_process_exit;
375                 task->process->data = task;
376         }
377
378         switch (url->scheme) {
379         case pb_url_ftp:
380         case pb_url_http:
381                 load_wget(task, 0);
382                 break;
383         case pb_url_https:
384                 load_wget(task, wget_no_check_certificate);
385                 break;
386         case pb_url_nfs:
387                 load_nfs(task);
388                 break;
389         case pb_url_sftp:
390                 load_sftp(task);
391                 break;
392         case pb_url_tftp:
393                 load_tftp(task);
394                 break;
395         default:
396                 load_local(task);
397                 break;
398         }
399
400         result = task->result;
401         if (result->status == LOAD_ERROR) {
402                 load_url_result_cleanup_local(task->result);
403                 talloc_free(result);
404                 talloc_free(task);
405                 return NULL;
406         }
407
408         if (!task->async)
409                 talloc_free(task);
410
411         return result;
412 }
413
414 struct load_url_result *load_url(void *ctx, struct pb_url *url)
415 {
416         return load_url_async(ctx, url, NULL, NULL);
417 }
418
419 void load_url_async_cancel(struct load_url_result *res)
420 {
421         struct load_task *task = res->task;
422
423         /* the completion callback may have already been called; this clears
424          * res->task */
425         if (!task)
426                 return;
427
428         if (res->status == LOAD_CANCELLED)
429                 return;
430
431         assert(task->async);
432         assert(task->process);
433
434         res->status = LOAD_CANCELLED;
435         process_stop_async(task->process);
436 }