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