]> git.ozlabs.org Git - petitboot/blobdiff - discover/paths.c
discover: Be more verbose about boot failures
[petitboot] / discover / paths.c
index 54774cf3b6135cb9ffdac41b0f3f731e7699d1b0..dcd7b493e129ec134e284f38fdca275cf8768dca 100644 (file)
@@ -3,20 +3,37 @@
 #endif
 
 #include <assert.h>
+#include <netdb.h>
 #include <string.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 
 #include <talloc/talloc.h>
 #include <system/system.h>
 #include <process/process.h>
 #include <url/url.h>
 #include <log/log.h>
+#include "i18n/i18n.h"
 
 #include "paths.h"
+#include "device-handler.h"
+#include "sysinfo.h"
 
 #define DEVICE_MOUNT_BASE (LOCAL_STATE_DIR "/petitboot/mnt")
 
+
+struct list    pending_network_jobs;
+
+struct network_job {
+       struct load_task        *task;
+       int                     flags;
+
+       struct list_item        list;
+};
+
 struct load_task {
        struct pb_url           *url;
        struct process          *process;
@@ -45,13 +62,23 @@ char *join_paths(void *alloc_ctx, const char *a, const char *b)
        return full_path;
 }
 
+#ifndef PETITBOOT_TEST
+
+#ifdef WITH_BUSYBOX
+static inline bool have_busybox(void) { return true; }
+#else
+static inline bool have_busybox(void) { return false; }
+#endif
 
 static char *local_name(void *ctx)
 {
        char *ret, tmp[] = "/tmp/pb-XXXXXX";
+       mode_t oldmask;
        int fd;
 
+       oldmask = umask(0644);
        fd = mkstemp(tmp);
+       umask(oldmask);
 
        if (fd < 0)
                return NULL;
@@ -91,8 +118,14 @@ static void load_url_process_exit(struct process *process)
        } else {
                result->status = LOAD_ERROR;
                load_url_result_cleanup_local(result);
+               pb_debug("Download client stdout buffer:\n%s\n",
+                               process->stdout_buf);
        }
 
+       if (result->status == LOAD_OK && process->stdout_data)
+               device_handler_status_info(process->stdout_data,
+                               _("Download complete: %s"), task->url->file);
+
        /* The load callback may well free the ctx, which was the
         * talloc parent of the task. Therefore, we want to do our cleanup
         * before invoking it
@@ -104,6 +137,66 @@ static void load_url_process_exit(struct process *process)
        cb(result, data);
 }
 
+/*
+ * Callback to retrieve progress information from Busybox utilities.
+ * Busybox utilities use a common progress bar format which progress percentage
+ * and current size can be can be parsed from.
+ */
+static int busybox_progress_cb(void *arg)
+{
+       const char *busybox_fmt = "%*s %u%*[%* |]%u%c %*u:%*u:%*u ETA\n";
+       struct process_info *procinfo = arg;
+       char *n, *s, suffix, *line = NULL;
+       struct device_handler *handler;
+       unsigned int percentage, size;
+       struct process *p;
+       int rc;
+
+       if (!arg)
+               return -1;
+
+       p = procinfo_get_process(procinfo);
+       handler = p->stdout_data;
+
+       rc = process_stdout_custom(procinfo, &line);
+
+       if (rc) {
+               /* Unregister ourselves from progress tracking */
+               device_handler_status_download_remove(handler, procinfo);
+       }
+
+       if (rc || !line)
+               return rc;
+
+       rc = sscanf(line, busybox_fmt, &percentage, &size, &suffix);
+
+       /*
+        * Many unrecognised lines are partial updates. If we see a partial
+        * line with a newline character, see if we can match a valid line
+        * at the end of stdout_buf
+        */
+       if (rc != 3) {
+               n = strchr(line, '\n');
+               if (n)
+                       for (s = n - 1; s >= p->stdout_buf; s--)
+                               if (*s == '\n') {
+                                       rc = sscanf(s + 1, busybox_fmt,
+                                               &percentage, &size, &suffix);
+                                       break;
+                               }
+       }
+
+       if (rc != 3)
+               percentage = size = 0;
+
+       device_handler_status_download(handler, procinfo,
+                       percentage, size, suffix);
+
+       return 0;
+}
+
+
+
 static void load_process_to_local_file(struct load_task *task,
                const char **argv, int argv_local_idx)
 {
@@ -220,15 +313,16 @@ static enum tftp_type check_tftp_type(void *ctx)
        const char *argv[] = { pb_system_apps.tftp, "-V", NULL };
        struct process *process;
        enum tftp_type type;
+       int rc;
 
        process = process_create(ctx);
        process->path = pb_system_apps.tftp;
        process->argv = argv;
        process->keep_stdout = true;
        process->add_stderr = true;
-       process_run_sync(process);
+       rc = process_run_sync(process);
 
-       if (!process->stdout_buf || process->stdout_len == 0) {
+       if (rc || !process->stdout_buf || process->stdout_len == 0) {
                pb_log("Can't check TFTP client type!\n");
                type = TFTP_TYPE_BROKEN;
 
@@ -293,8 +387,9 @@ static void load_tftp(struct load_task *task)
 }
 
 enum wget_flags {
-       wget_empty = 0,
-       wget_no_check_certificate = 1,
+       wget_empty                      = 0x1,
+       wget_no_check_certificate       = 0x2,
+       wget_verbose                    = 0x4,
 };
 
 /**
@@ -310,16 +405,23 @@ static void load_wget(struct load_task *task, int flags)
                pb_system_apps.wget,
                "-O",
                NULL, /* 2: local file */
-               NULL,
-               NULL,
+               NULL, /* 3 (optional): --quiet */
+               NULL, /* 4 (optional): --no-check-certificate */
+               NULL, /* 5: URL */
                NULL,
        };
        int i;
 
+       if (task->process->stdout_cb)
+               flags |= wget_verbose;
+
        i = 3;
-#if !defined(DEBUG)
-       argv[i++] = "--quiet";
+#if defined(DEBUG)
+       flags |= wget_verbose;
 #endif
+       if ((flags & wget_verbose) == 0)
+               argv[i++] = "--quiet";
+
        if (flags & wget_no_check_certificate)
                argv[i++] = "--no-check-certificate";
 
@@ -335,18 +437,100 @@ static void load_wget(struct load_task *task, int flags)
  */
 static void load_local(struct load_task *task)
 {
+       struct load_url_result *result = task->result;
        int rc;
 
        rc = access(task->url->path, F_OK);
        if (rc) {
-               task->result->status = LOAD_ERROR;
+               result->status = LOAD_ERROR;
        } else {
-               task->result->local = talloc_strdup(task->result,
-                                                   task->url->path);
-               task->result->status = LOAD_OK;
+               result->local = talloc_strdup(task->result, task->url->path);
+               result->status = LOAD_OK;
        }
+
+       task->async_cb(task->result, task->async_data);
 }
 
+static void load_url_async_start_pending(struct load_task *task, int flags)
+{
+       pb_log("Starting pending job for %s\n", task->url->full);
+
+       switch (task->url->scheme) {
+       case pb_url_ftp:
+       case pb_url_http:
+               load_wget(task, flags);
+               break;
+       case pb_url_https:
+               flags |= wget_no_check_certificate;
+               load_wget(task, flags);
+               break;
+       case pb_url_nfs:
+               load_nfs(task);
+               break;
+       case pb_url_sftp:
+               load_sftp(task);
+               break;
+       case pb_url_tftp:
+               load_tftp(task);
+               break;
+       default:
+               /* Shouldn't be a need via this path but.. */
+               load_local(task);
+               break;
+       }
+
+       if (task->result->status == LOAD_ERROR) {
+               pb_log("Pending job failed for %s\n", task->url->full);
+               load_url_result_cleanup_local(task->result);
+               talloc_free(task->result);
+               talloc_free(task);
+       }
+}
+
+void pending_network_jobs_start(void)
+{
+       struct network_job *job, *tmp;
+
+       if (!pending_network_jobs.head.next)
+               return;
+
+       list_for_each_entry_safe(&pending_network_jobs, job, tmp, list) {
+               load_url_async_start_pending(job->task, job->flags);
+               list_remove(&job->list);
+       }
+}
+
+void pending_network_jobs_cancel(void)
+{
+       struct network_job *job, *tmp;
+
+       if (!pending_network_jobs.head.next)
+               return;
+
+       list_for_each_entry_safe(&pending_network_jobs, job, tmp, list)
+               talloc_free(job);
+       list_init(&pending_network_jobs);
+}
+
+static void pending_network_jobs_add(struct load_task *task, int flags)
+{
+       struct network_job *job;
+
+       if (!pending_network_jobs.head.next)
+               list_init(&pending_network_jobs);
+
+       job = talloc(task, struct network_job);
+       if (!job) {
+               pb_log("Failed to allocate space for pending job\n");
+               return;
+       }
+
+       job->task = task;
+       job->flags = flags;
+       list_add_tail(&pending_network_jobs, &job->list);
+}
+
+
 /**
  * load_url - Loads a (possibly) remote URL and returns the local file
  * path.
@@ -362,10 +546,13 @@ static void load_local(struct load_task *task)
  */
 
 struct load_url_result *load_url_async(void *ctx, struct pb_url *url,
-               load_url_complete async_cb, void *async_data)
+               load_url_complete async_cb, void *async_data,
+               waiter_cb stdout_cb, void *stdout_data)
 {
        struct load_url_result *result;
        struct load_task *task;
+       struct addrinfo *res;
+       int flags = 0;
 
        if (!url)
                return NULL;
@@ -375,21 +562,44 @@ struct load_url_result *load_url_async(void *ctx, struct pb_url *url,
        task->async = async_cb != NULL;
        task->result = talloc_zero(ctx, struct load_url_result);
        task->result->task = task;
+       task->result->url = url;
        task->process = process_create(task);
        if (task->async) {
                task->async_cb = async_cb;
                task->async_data = async_data;
                task->process->exit_cb = load_url_process_exit;
                task->process->data = task;
+               task->process->stdout_cb = stdout_cb;
+               task->process->stdout_data = stdout_data;
+       }
+
+       if (!stdout_cb && stdout_data && have_busybox())
+               task->process->stdout_cb = busybox_progress_cb;
+
+       /* Make sure we save output for any task that has a custom handler */
+       if (task->process->stdout_cb) {
+               task->process->add_stderr = true;
+               task->process->keep_stdout = true;
+       }
+
+       /* If the url is remote but network is not yet available queue up this
+        * load for later */
+       if (url->scheme != pb_url_file &&
+                       getaddrinfo(url->host, NULL, NULL, &res) != 0) {
+               pb_log("load task for %s queued pending network\n", url->full);
+               pending_network_jobs_add(task, flags);
+               task->result->status = LOAD_ASYNC;
+               return task->result;
        }
 
        switch (url->scheme) {
        case pb_url_ftp:
        case pb_url_http:
-               load_wget(task, 0);
+               load_wget(task, flags);
                break;
        case pb_url_https:
-               load_wget(task, wget_no_check_certificate);
+               flags |= wget_no_check_certificate;
+               load_wget(task, flags);
                break;
        case pb_url_nfs:
                load_nfs(task);
@@ -413,7 +623,7 @@ struct load_url_result *load_url_async(void *ctx, struct pb_url *url,
                return NULL;
        }
 
-       if (!task->async)
+       if (!task->async || result->status == LOAD_OK)
                talloc_free(task);
 
        return result;
@@ -421,7 +631,7 @@ struct load_url_result *load_url_async(void *ctx, struct pb_url *url,
 
 struct load_url_result *load_url(void *ctx, struct pb_url *url)
 {
-       return load_url_async(ctx, url, NULL, NULL);
+       return load_url_async(ctx, url, NULL, NULL, NULL, NULL);
 }
 
 void load_url_async_cancel(struct load_url_result *res)
@@ -442,3 +652,33 @@ void load_url_async_cancel(struct load_url_result *res)
        res->status = LOAD_CANCELLED;
        process_stop_async(task->process);
 }
+
+#else
+
+static void __attribute__((unused)) load_local(
+               struct load_task *task __attribute__((unused)))
+{
+}
+static void __attribute__((unused)) load_wget(
+               struct load_task *task __attribute__((unused)),
+               int flags __attribute__((unused)))
+{
+}
+static void __attribute__((unused)) load_tftp(
+               struct load_task *task __attribute__((unused)))
+{
+}
+static void __attribute__((unused)) load_sftp(
+               struct load_task *task __attribute__((unused)))
+{
+}
+static void __attribute__((unused)) load_nfs(
+               struct load_task *task __attribute__((unused)))
+{
+}
+static void __attribute__((unused)) load_url_process_exit(
+               struct process *process __attribute__((unused)))
+{
+}
+
+#endif