docker: Add build container files
[petitboot] / discover / paths.c
1 #if defined(HAVE_CONFIG_H)
2 #include "config.h"
3 #endif
4
5 #include <assert.h>
6 #include <string.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <sys/types.h>
10 #include <sys/stat.h>
11
12 #include <talloc/talloc.h>
13 #include <system/system.h>
14 #include <process/process.h>
15 #include <url/url.h>
16 #include <log/log.h>
17 #include "i18n/i18n.h"
18
19 #include "paths.h"
20 #include "device-handler.h"
21 #include "sysinfo.h"
22
23 #define DEVICE_MOUNT_BASE (LOCAL_STATE_DIR "/petitboot/mnt")
24
25
26 struct list     pending_network_jobs;
27
28 struct network_job {
29         struct load_task        *task;
30         int                     flags;
31
32         struct list_item        list;
33 };
34
35 struct load_task {
36         struct pb_url           *url;
37         struct process          *process;
38         struct load_url_result  *result;
39         bool                    async;
40         load_url_complete       async_cb;
41         void                    *async_data;
42 };
43
44 const char *mount_base(void)
45 {
46         return DEVICE_MOUNT_BASE;
47 }
48
49 char *join_paths(void *alloc_ctx, const char *a, const char *b)
50 {
51         char *full_path;
52
53         full_path = talloc_array(alloc_ctx, char, strlen(a) + strlen(b) + 2);
54
55         strcpy(full_path, a);
56         if (b[0] != '/' && a[strlen(a) - 1] != '/')
57                 strcat(full_path, "/");
58         strcat(full_path, b);
59
60         return full_path;
61 }
62
63 #ifndef PETITBOOT_TEST
64
65 #ifdef WITH_BUSYBOX
66 static inline bool have_busybox(void) { return true; }
67 #else
68 static inline bool have_busybox(void) { return false; }
69 #endif
70
71 static char *local_name(void *ctx)
72 {
73         char *ret, tmp[] = "/tmp/pb-XXXXXX";
74         mode_t oldmask;
75         int fd;
76
77         oldmask = umask(0644);
78         fd = mkstemp(tmp);
79         umask(oldmask);
80
81         if (fd < 0)
82                 return NULL;
83
84         close(fd);
85
86         ret = talloc_strdup(ctx, tmp);
87
88         return ret;
89 }
90
91 static void load_url_result_cleanup_local(struct load_url_result *result)
92 {
93         if (result->cleanup_local)
94                 unlink(result->local);
95 }
96
97 static void load_url_process_exit(struct process *process)
98 {
99         struct load_task *task = process->data;
100         struct load_url_result *result;
101         load_url_complete cb;
102         void *data;
103
104         pb_debug("The download client '%s' [pid %d, url %s] exited, rc %d\n",
105                         process->path, process->pid, task->url->full,
106                         process->exit_status);
107
108         result = task->result;
109         data = task->async_data;
110         cb = task->async_cb;
111
112         if (result->status == LOAD_CANCELLED) {
113                 load_url_result_cleanup_local(result);
114         } else if (process_exit_ok(process)) {
115                 result->status = LOAD_OK;
116         } else {
117                 result->status = LOAD_ERROR;
118                 load_url_result_cleanup_local(result);
119         }
120
121         if (result->status == LOAD_OK && process->stdout_data)
122                 device_handler_status_info(process->stdout_data,
123                                 _("Download complete: %s"), task->url->file);
124
125         /* The load callback may well free the ctx, which was the
126          * talloc parent of the task. Therefore, we want to do our cleanup
127          * before invoking it
128          */
129         process_release(process);
130         talloc_free(task);
131         result->task = NULL;
132
133         cb(result, data);
134 }
135
136 /*
137  * Callback to retrieve progress information from Busybox utilities.
138  * Busybox utilities use a common progress bar format which progress percentage
139  * and current size can be can be parsed from.
140  */
141 static int busybox_progress_cb(void *arg)
142 {
143         const char *busybox_fmt = "%*s %u%*[%* |]%u%c %*u:%*u:%*u ETA\n";
144         struct process_info *procinfo = arg;
145         char *n, *s, suffix, *line = NULL;
146         struct device_handler *handler;
147         unsigned int percentage, size;
148         struct process *p;
149         int rc;
150
151         if (!arg)
152                 return -1;
153
154         p = procinfo_get_process(procinfo);
155         handler = p->stdout_data;
156
157         rc = process_stdout_custom(procinfo, &line);
158
159         if (rc) {
160                 /* Unregister ourselves from progress tracking */
161                 device_handler_status_download_remove(handler, procinfo);
162         }
163
164         if (rc || !line)
165                 return rc;
166
167         rc = sscanf(line, busybox_fmt, &percentage, &size, &suffix);
168
169         /*
170          * Many unrecognised lines are partial updates. If we see a partial
171          * line with a newline character, see if we can match a valid line
172          * at the end of stdout_buf
173          */
174         if (rc != 3) {
175                 n = strchr(line, '\n');
176                 if (n)
177                         for (s = n - 1; s >= p->stdout_buf; s--)
178                                 if (*s == '\n') {
179                                         rc = sscanf(s + 1, busybox_fmt,
180                                                 &percentage, &size, &suffix);
181                                         break;
182                                 }
183         }
184
185         if (rc != 3)
186                 percentage = size = 0;
187
188         device_handler_status_download(handler, procinfo,
189                         percentage, size, suffix);
190
191         return 0;
192 }
193
194
195
196 static void load_process_to_local_file(struct load_task *task,
197                 const char **argv, int argv_local_idx)
198 {
199         int rc;
200
201         task->result->local = local_name(task->result);
202         if (!task->result->local) {
203                 task->result->status = LOAD_ERROR;
204                 return;
205         }
206         task->result->cleanup_local = true;
207
208         if (argv_local_idx)
209                 argv[argv_local_idx] = task->result->local;
210
211         task->process->argv = argv;
212         task->process->path = argv[0];
213
214         if (task->async) {
215                 rc = process_run_async(task->process);
216                 if (rc) {
217                         process_release(task->process);
218                         task->process = NULL;
219                 }
220                 task->result->status = rc ? LOAD_ERROR : LOAD_ASYNC;
221         } else {
222                 rc = process_run_sync(task->process);
223                 if (rc || !process_exit_ok(task->process))
224                         task->result->status = LOAD_ERROR;
225                 else
226                         task->result->status = LOAD_OK;
227                 process_release(task->process);
228                 task->process = NULL;
229         }
230 }
231
232 /**
233  * pb_load_nfs - Create a mountpoint, set the local file within that
234  * mountpoint, and run the appropriate mount command
235  */
236
237 static void load_nfs(struct load_task *task)
238 {
239         char *mountpoint, *opts;
240         int rc;
241         const char *argv[] = {
242                         pb_system_apps.mount,
243                         "-t", "nfs",
244                         NULL,                   /* 3: opts */
245                         task->url->host,
246                         task->url->dir,
247                         NULL,                   /* 6: mountpoint */
248                         NULL,
249         };
250
251         task->result->status = LOAD_ERROR;
252         mountpoint = local_name(task->result);
253         if (!mountpoint)
254                 return;
255         task->result->cleanup_local = true;
256         argv[6] = mountpoint;
257
258         rc = pb_mkdir_recursive(mountpoint);
259         if (rc)
260                 return;
261
262         opts = talloc_strdup(NULL, "ro,nolock,nodiratime");
263         argv[3] = opts;
264
265         if (task->url->port)
266                 opts = talloc_asprintf_append(opts, ",port=%s",
267                                                 task->url->port);
268
269         task->result->local = talloc_asprintf(task->result, "%s/%s",
270                                                         mountpoint,
271                                                         task->url->path);
272
273         task->process->path = pb_system_apps.mount;
274         task->process->argv = argv;
275
276         if (task->async) {
277                 rc = process_run_async(task->process);
278                 if (rc) {
279                         process_release(task->process);
280                         task->process = NULL;
281                 }
282                 task->result->status = rc ? LOAD_ERROR : LOAD_ASYNC;
283         } else {
284                 rc = process_run_sync(task->process);
285                 task->result->status = rc ? LOAD_ERROR : LOAD_OK;
286                 process_release(task->process);
287                 task->process = NULL;
288         }
289
290         talloc_free(opts);
291 }
292
293 static void load_sftp(struct load_task *task)
294 {
295         const char *argv[] = {
296                         pb_system_apps.sftp,
297                         NULL,           /* 1: host:path */
298                         NULL,           /* 2: local file */
299                         NULL,
300         };
301
302         argv[1] = talloc_asprintf(task, "%s:%s",
303                                 task->url->host, task->url->path);
304         load_process_to_local_file(task, argv, 2);
305 }
306
307 static enum tftp_type check_tftp_type(void *ctx)
308 {
309         const char *argv[] = { pb_system_apps.tftp, "-V", NULL };
310         struct process *process;
311         enum tftp_type type;
312         int rc;
313
314         process = process_create(ctx);
315         process->path = pb_system_apps.tftp;
316         process->argv = argv;
317         process->keep_stdout = true;
318         process->add_stderr = true;
319         rc = process_run_sync(process);
320
321         if (rc || !process->stdout_buf || process->stdout_len == 0) {
322                 pb_log("Can't check TFTP client type!\n");
323                 type = TFTP_TYPE_BROKEN;
324
325         } else if (memmem(process->stdout_buf, process->stdout_len,
326                                 "tftp-hpa", strlen("tftp-hpa"))) {
327                 pb_debug("Found TFTP client type: tftp-hpa\n");
328                 type = TFTP_TYPE_HPA;
329
330         } else if (memmem(process->stdout_buf, process->stdout_len,
331                                 "BusyBox", strlen("BusyBox"))) {
332                 pb_debug("Found TFTP client type: BusyBox tftp\n");
333                 type = TFTP_TYPE_BUSYBOX;
334
335         } else {
336                 pb_log("Unknown TFTP client type!\n");
337                 type = TFTP_TYPE_BROKEN;
338         }
339
340         process_release(process);
341         return type;
342 }
343
344 static void load_tftp(struct load_task *task)
345 {
346         const char *port = "69";
347         const char *argv[10] = {
348                 pb_system_apps.tftp,
349         };
350
351         if (task->url->port)
352                 port = task->url->port;
353
354         if (tftp_type == TFTP_TYPE_UNKNOWN)
355                 tftp_type = check_tftp_type(task);
356
357         if (tftp_type == TFTP_TYPE_BUSYBOX) {
358                 argv[1] = "-g";
359                 argv[2] = "-l";
360                 argv[3] = NULL; /* 3: local file */
361                 argv[4] = "-r";
362                 argv[5] = task->url->path;
363                 argv[6] = task->url->host;
364                 argv[7] = port;
365                 argv[8] = NULL;
366
367                 load_process_to_local_file(task, argv, 3);
368
369         } else if (tftp_type == TFTP_TYPE_HPA) {
370                 argv[1] = "-m";
371                 argv[2] = "binary";
372                 argv[3] = task->url->host;
373                 argv[4] = port;
374                 argv[5] = "-c";
375                 argv[6] = "get";
376                 argv[7] = task->url->path;
377                 argv[8] = NULL; /* 8: local file */
378                 argv[9] = NULL;
379                 load_process_to_local_file(task, argv, 8);
380
381         } else
382                 task->result->status = LOAD_ERROR;
383 }
384
385 enum wget_flags {
386         wget_empty                      = 0x1,
387         wget_no_check_certificate       = 0x2,
388         wget_verbose                    = 0x4,
389 };
390
391 /**
392  * pb_load_wget - Loads a remote file via wget and returns the local file path.
393  *
394  * Returns the local file path in a talloc'ed character string on success,
395  * or NULL on error.
396  */
397
398 static void load_wget(struct load_task *task, int flags)
399 {
400         const char *argv[] = {
401                 pb_system_apps.wget,
402                 "-O",
403                 NULL, /* 2: local file */
404                 NULL, /* 3 (optional): --quiet */
405                 NULL, /* 4 (optional): --no-check-certificate */
406                 NULL, /* 5: URL */
407                 NULL,
408         };
409         int i;
410
411         if (task->process->stdout_cb)
412                 flags |= wget_verbose;
413
414         i = 3;
415 #if defined(DEBUG)
416         flags |= wget_verbose;
417 #endif
418         if ((flags & wget_verbose) == 0)
419                 argv[i++] = "--quiet";
420
421         if (flags & wget_no_check_certificate)
422                 argv[i++] = "--no-check-certificate";
423
424         argv[i] = task->url->full;
425
426         load_process_to_local_file(task, argv, 2);
427 }
428
429 /* Although we don't need to load anything for a local path (we just return
430  * the path from the file:// URL), the other load helpers will error-out on
431  * non-existant files. So, do the same here with an access() check on the local
432  * filename.
433  */
434 static void load_local(struct load_task *task)
435 {
436         struct load_url_result *result = task->result;
437         int rc;
438
439         rc = access(task->url->path, F_OK);
440         if (rc) {
441                 result->status = LOAD_ERROR;
442         } else {
443                 result->local = talloc_strdup(task->result, task->url->path);
444                 result->status = LOAD_OK;
445         }
446
447         task->async_cb(task->result, task->async_data);
448 }
449
450 static void load_url_async_start_pending(struct load_task *task, int flags)
451 {
452         pb_log("Starting pending job for %s\n", task->url->full);
453
454         switch (task->url->scheme) {
455         case pb_url_ftp:
456         case pb_url_http:
457                 load_wget(task, flags);
458                 break;
459         case pb_url_https:
460                 flags |= wget_no_check_certificate;
461                 load_wget(task, flags);
462                 break;
463         case pb_url_nfs:
464                 load_nfs(task);
465                 break;
466         case pb_url_sftp:
467                 load_sftp(task);
468                 break;
469         case pb_url_tftp:
470                 load_tftp(task);
471                 break;
472         default:
473                 /* Shouldn't be a need via this path but.. */
474                 load_local(task);
475                 break;
476         }
477
478         if (task->result->status == LOAD_ERROR) {
479                 pb_log("Pending job failed for %s\n", task->url->full);
480                 load_url_result_cleanup_local(task->result);
481                 talloc_free(task->result);
482                 talloc_free(task);
483         }
484 }
485
486 void pending_network_jobs_start(void)
487 {
488         struct network_job *job, *tmp;
489
490         if (!pending_network_jobs.head.next)
491                 return;
492
493         list_for_each_entry_safe(&pending_network_jobs, job, tmp, list) {
494                 load_url_async_start_pending(job->task, job->flags);
495                 list_remove(&job->list);
496         }
497 }
498
499 void pending_network_jobs_cancel(void)
500 {
501         struct network_job *job, *tmp;
502
503         if (!pending_network_jobs.head.next)
504                 return;
505
506         list_for_each_entry_safe(&pending_network_jobs, job, tmp, list)
507                 talloc_free(job);
508         list_init(&pending_network_jobs);
509 }
510
511 static void pending_network_jobs_add(struct load_task *task, int flags)
512 {
513         struct network_job *job;
514
515         if (!pending_network_jobs.head.next)
516                 list_init(&pending_network_jobs);
517
518         job = talloc(task, struct network_job);
519         if (!job) {
520                 pb_log("Failed to allocate space for pending job\n");
521                 return;
522         }
523
524         job->task = task;
525         job->flags = flags;
526         list_add_tail(&pending_network_jobs, &job->list);
527 }
528
529
530 /**
531  * load_url - Loads a (possibly) remote URL and returns the local file
532  * path.
533  * @ctx: The talloc context to associate with the returned string.
534  * @url: The remote file URL.
535  * @tempfile: An optional variable pointer to be set when a temporary local
536  *  file is created.
537  * @url_cb: An optional callback pointer if the caller wants to load url
538  *  asynchronously.
539  *
540  * Returns the local file path in a talloc'ed character string on success,
541  * or NULL on error.
542  */
543
544 struct load_url_result *load_url_async(void *ctx, struct pb_url *url,
545                 load_url_complete async_cb, void *async_data,
546                 waiter_cb stdout_cb, void *stdout_data)
547 {
548         struct load_url_result *result;
549         struct load_task *task;
550         int flags = 0;
551
552         if (!url)
553                 return NULL;
554
555         task = talloc_zero(ctx, struct load_task);
556         task->url = url;
557         task->async = async_cb != NULL;
558         task->result = talloc_zero(ctx, struct load_url_result);
559         task->result->task = task;
560         task->result->url = url;
561         task->process = process_create(task);
562         if (task->async) {
563                 task->async_cb = async_cb;
564                 task->async_data = async_data;
565                 task->process->exit_cb = load_url_process_exit;
566                 task->process->data = task;
567                 task->process->stdout_cb = stdout_cb;
568                 task->process->stdout_data = stdout_data;
569         }
570
571         if (!stdout_cb && stdout_data && have_busybox())
572                 task->process->stdout_cb = busybox_progress_cb;
573
574         /* Make sure we save output for any task that has a custom handler */
575         if (task->process->stdout_cb) {
576                 task->process->add_stderr = true;
577                 task->process->keep_stdout = true;
578         }
579
580         /* If the url is remote but network is not yet available queue up this
581          * load for later */
582         if (!system_info_network_available() && url->scheme != pb_url_file) {
583                 pb_log("load task for %s queued pending network\n", url->full);
584                 pending_network_jobs_add(task, flags);
585                 task->result->status = LOAD_ASYNC;
586                 return task->result;
587         }
588
589         switch (url->scheme) {
590         case pb_url_ftp:
591         case pb_url_http:
592                 load_wget(task, flags);
593                 break;
594         case pb_url_https:
595                 flags |= wget_no_check_certificate;
596                 load_wget(task, flags);
597                 break;
598         case pb_url_nfs:
599                 load_nfs(task);
600                 break;
601         case pb_url_sftp:
602                 load_sftp(task);
603                 break;
604         case pb_url_tftp:
605                 load_tftp(task);
606                 break;
607         default:
608                 load_local(task);
609                 break;
610         }
611
612         result = task->result;
613         if (result->status == LOAD_ERROR) {
614                 load_url_result_cleanup_local(task->result);
615                 talloc_free(result);
616                 talloc_free(task);
617                 return NULL;
618         }
619
620         if (!task->async || result->status == LOAD_OK)
621                 talloc_free(task);
622
623         return result;
624 }
625
626 struct load_url_result *load_url(void *ctx, struct pb_url *url)
627 {
628         return load_url_async(ctx, url, NULL, NULL, NULL, NULL);
629 }
630
631 void load_url_async_cancel(struct load_url_result *res)
632 {
633         struct load_task *task = res->task;
634
635         /* the completion callback may have already been called; this clears
636          * res->task */
637         if (!task)
638                 return;
639
640         if (res->status == LOAD_CANCELLED)
641                 return;
642
643         assert(task->async);
644         assert(task->process);
645
646         res->status = LOAD_CANCELLED;
647         process_stop_async(task->process);
648 }
649
650 #else
651
652 static void __attribute__((unused)) load_local(
653                 struct load_task *task __attribute__((unused)))
654 {
655 }
656 static void __attribute__((unused)) load_wget(
657                 struct load_task *task __attribute__((unused)),
658                 int flags __attribute__((unused)))
659 {
660 }
661 static void __attribute__((unused)) load_tftp(
662                 struct load_task *task __attribute__((unused)))
663 {
664 }
665 static void __attribute__((unused)) load_sftp(
666                 struct load_task *task __attribute__((unused)))
667 {
668 }
669 static void __attribute__((unused)) load_nfs(
670                 struct load_task *task __attribute__((unused)))
671 {
672 }
673 static void __attribute__((unused)) load_url_process_exit(
674                 struct process *process __attribute__((unused)))
675 {
676 }
677
678 #endif