1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <errno.h>
6#include <fcntl.h>
7#include <stddef.h>
8#include <stdio.h>
9#include <string.h>
10#include <threads.h>
11#include <unistd.h>
12
13#include <inet6/inet6.h>
14#include <lib/fdio/spawn.h>
15#include <lib/sync/completion.h>
16#include <tftp/tftp.h>
17#include <zircon/assert.h>
18#include <zircon/boot/netboot.h>
19#include <zircon/process.h>
20#include <zircon/status.h>
21#include <zircon/syscalls.h>
22#include <zircon/time.h>
23
24#include "netsvc.h"
25
26#define SCRATCHSZ 2048
27
28#define TFTP_TIMEOUT_SECS 1
29
30#define NB_IMAGE_PREFIX_LEN (strlen(NB_IMAGE_PREFIX))
31#define NB_FILENAME_PREFIX_LEN (strlen(NB_FILENAME_PREFIX))
32
33// Identifies what the file being streamed over TFTP should be
34// used for.
35typedef enum netfile_type {
36    netboot, // A bootfs file
37    paver,   // A disk image which should be paved to disk
38} netfile_type_t;
39
40typedef struct {
41    bool is_write;
42    char filename[PATH_MAX + 1];
43    netfile_type_t type;
44    union {
45        nbfile* netboot_file;
46        struct {
47            int fd;                     // Pipe to paver process
48            size_t size;                // Total size of file
49            zx_handle_t process;
50
51            // Buffer used for stashing data from tftp until it can be written out to the paver
52            zx_handle_t buffer_handle;
53            uint8_t* buffer;
54            atomic_uint buf_refcount;
55            atomic_size_t offset;       // Buffer write offset (read offset is stored locally)
56            thrd_t buf_copy_thrd;
57            sync_completion_t data_ready;    // Allows read thread to block on buffer writes
58        } paver;
59    };
60} file_info_t;
61
62typedef struct {
63    ip6_addr_t dest_addr;
64    uint16_t dest_port;
65    uint32_t timeout_ms;
66} transport_info_t;
67
68static char tftp_session_scratch[SCRATCHSZ];
69char tftp_out_scratch[SCRATCHSZ];
70
71static size_t last_msg_size = 0;
72static tftp_session* session = NULL;
73static file_info_t file_info;
74static transport_info_t transport_info;
75
76atomic_bool paving_in_progress = false;
77zx_time_t tftp_next_timeout = ZX_TIME_INFINITE;
78
79static ssize_t file_open_read(const char* filename, void* cookie) {
80    // Make sure all in-progress paving options have completed
81    if (atomic_load(&paving_in_progress) == true) {
82        return TFTP_ERR_SHOULD_WAIT;
83    }
84    file_info_t* file_info = cookie;
85    file_info->is_write = false;
86    strncpy(file_info->filename, filename, PATH_MAX);
87    file_info->filename[PATH_MAX] = '\0';
88    file_info->netboot_file = NULL;
89    size_t file_size;
90    if (netfile_open(filename, O_RDONLY, &file_size) == 0) {
91        return (ssize_t)file_size;
92    }
93    return TFTP_ERR_NOT_FOUND;
94}
95
96static zx_status_t alloc_paver_buffer(file_info_t* file_info, size_t size) {
97    zx_status_t status;
98    status = zx_vmo_create(size, 0, &file_info->paver.buffer_handle);
99    if (status != ZX_OK) {
100        printf("netsvc: unable to allocate buffer VMO\n");
101        return status;
102    }
103    zx_object_set_property(file_info->paver.buffer_handle, ZX_PROP_NAME, "paver", 5);
104    uintptr_t buffer;
105    status = zx_vmar_map(zx_vmar_root_self(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
106                         0, file_info->paver.buffer_handle, 0, size, &buffer);
107    if (status != ZX_OK) {
108        printf("netsvc: unable to map buffer\n");
109        zx_handle_close(file_info->paver.buffer_handle);
110        return status;
111    }
112    file_info->paver.buffer = (uint8_t*)buffer;
113    return ZX_OK;
114}
115
116static zx_status_t dealloc_paver_buffer(file_info_t* file_info) {
117    zx_status_t status = zx_vmar_unmap(zx_vmar_root_self(), (uintptr_t)file_info->paver.buffer,
118                                       file_info->paver.size);
119    if (status != ZX_OK) {
120        printf("netsvc: failed to unmap paver buffer: %s\n", zx_status_get_string(status));
121        goto done;
122    }
123
124    status = zx_handle_close(file_info->paver.buffer_handle);
125    if (status != ZX_OK) {
126        printf("netsvc: failed to close paver buffer handle: %s\n", zx_status_get_string(status));
127    }
128
129done:
130    file_info->paver.buffer = NULL;
131    return status;
132}
133
134static int drain_pipe(void* arg) {
135    char buf[4096];
136    int fd = (uintptr_t)arg;
137
138    ssize_t sz;
139    while ((sz = read(fd, buf, sizeof(buf) - 1)) > 0) {
140        // ensure null termination
141        buf[sz] = '\0';
142        printf("%s", buf);
143    }
144
145    close(fd);
146    return sz;
147}
148
149// Pushes all data from the paver buffer (filled by netsvc) into the paver input pipe. When
150// there's no data to copy, blocks on data_ready until more data is written into the buffer.
151static int paver_copy_buffer(void* arg) {
152    file_info_t* file_info = arg;
153    size_t read_ndx = 0;
154    int result = 0;
155    zx_time_t last_reported = zx_clock_get_monotonic();
156    while (read_ndx < file_info->paver.size) {
157        sync_completion_reset(&file_info->paver.data_ready);
158        size_t write_ndx = atomic_load(&file_info->paver.offset);
159        if (write_ndx == read_ndx) {
160            // Wait for more data to be written -- we are allowed up to 3 tftp timeouts before
161            // a connection is dropped, so we should wait at least that long before giving up.
162            if (sync_completion_wait(&file_info->paver.data_ready, ZX_SEC(5 * TFTP_TIMEOUT_SECS))
163                == ZX_OK) {
164                continue;
165            }
166            printf("netsvc: timed out while waiting for data in paver-copy thread\n");
167            result = TFTP_ERR_TIMED_OUT;
168            goto done;
169        }
170        while(read_ndx < write_ndx) {
171            int r = write(file_info->paver.fd, &file_info->paver.buffer[read_ndx],
172                          write_ndx - read_ndx);
173            if (r <= 0) {
174                printf("netsvc: couldn't write to paver fd: %d\n", r);
175                result = TFTP_ERR_IO;
176                goto done;
177            }
178            read_ndx += r;
179            zx_time_t curr_time = zx_clock_get_monotonic();
180            if (zx_time_sub_time(curr_time, last_reported) >= ZX_SEC(1)) {
181                float complete = ((float)read_ndx / (float)file_info->paver.size) * 100.0;
182                printf("netsvc: paver write progress %0.1f%%\n", complete);
183                last_reported = curr_time;
184            }
185        }
186    }
187done:
188    close(file_info->paver.fd);
189
190    unsigned int refcount = atomic_fetch_sub(&file_info->paver.buf_refcount, 1);
191    if (refcount == 1) {
192        dealloc_paver_buffer(file_info);
193    }
194    // If all of the data has been written out to the paver process wait for it to complete
195    if (result == 0) {
196        zx_signals_t signals;
197        zx_object_wait_one(file_info->paver.process, ZX_TASK_TERMINATED,
198                           zx_deadline_after(ZX_SEC(10)), &signals);
199    }
200    zx_handle_close(file_info->paver.process);
201
202    // Extra protection against double-close.
203    file_info->filename[0] = '\0';
204    atomic_store(&paving_in_progress, false);
205    return result;
206}
207
208static tftp_status paver_open_write(const char* filename, size_t size, file_info_t* file_info) {
209    // paving an image to disk
210    const char* argv[] = {"/boot/bin/install-disk-image", NULL, NULL};
211
212    if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_FVM_HOST_FILENAME)) {
213        printf("netsvc: Running FVM Paver\n");
214        argv[1] = "install-fvm";
215    } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_BOOTLOADER_HOST_FILENAME)) {
216        printf("netsvc: Running BOOTLOADER Paver\n");
217        argv[1] = "install-bootloader";
218    } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_EFI_HOST_FILENAME)) {
219        printf("netsvc: Running EFI Paver\n");
220        argv[1] = "install-efi";
221    } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_KERNC_HOST_FILENAME)) {
222        printf("netsvc: Running KERN-C Paver\n");
223        argv[1] = "install-kernc";
224    } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_ZIRCONA_HOST_FILENAME)) {
225        printf("netsvc: Running ZIRCON-A Paver\n");
226        argv[1] = "install-zircona";
227    } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_ZIRCONB_HOST_FILENAME)) {
228        printf("netsvc: Running ZIRCON-B Paver\n");
229        argv[1] = "install-zirconb";
230    } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_ZIRCONR_HOST_FILENAME)) {
231        printf("netsvc: Running ZIRCON-R Paver\n");
232        argv[1] = "install-zirconr";
233    } else {
234        fprintf(stderr, "netsvc: Unknown Paver\n");
235        return TFTP_ERR_IO;
236    }
237
238    int fds[2];
239    if (pipe(fds)) {
240        return TFTP_ERR_IO;
241    }
242
243    int logfds[2];
244    if (pipe(logfds)) {
245        close(fds[0]);
246        close(fds[1]);
247        return TFTP_ERR_IO;
248    }
249
250    fdio_spawn_action_t actions[] = {
251        {.action = FDIO_SPAWN_ACTION_SET_NAME, .name = {.data = "paver"}},
252        {.action = FDIO_SPAWN_ACTION_TRANSFER_FD,
253         .fd = {.local_fd = fds[0], .target_fd = STDIN_FILENO}},
254        {.action = FDIO_SPAWN_ACTION_TRANSFER_FD,
255         .fd = {.local_fd = logfds[1], .target_fd = STDERR_FILENO}},
256    };
257
258    zx_status_t status = fdio_spawn_etc(ZX_HANDLE_INVALID, FDIO_SPAWN_CLONE_ALL,
259                                        argv[0], argv, NULL, countof(actions), actions,
260                                        &file_info->paver.process, NULL);
261
262    if (status != ZX_OK) {
263        printf("netsvc: tftp couldn't launch paver\n");
264        goto err_close_fds;
265    }
266
267    thrd_t log_thrd;
268    if ((thrd_create(&log_thrd, drain_pipe, (void*)(uintptr_t)logfds[0])) == thrd_success) {
269        thrd_detach(log_thrd);
270    } else {
271        printf("netsvc: couldn't create paver log message redirection thread\n");
272        goto err_close_fds;
273    }
274
275    if ((status = alloc_paver_buffer(file_info, size)) != ZX_OK) {
276        goto err_close_fds;
277    }
278
279    file_info->type = paver;
280    file_info->paver.fd = fds[1];
281    file_info->paver.size = size;
282    // Both the netsvc thread and the paver copy thread access the buffer, and either
283    // may be done with it first so we use a refcount to decide when to deallocate it
284    atomic_store(&file_info->paver.buf_refcount, 2);
285    atomic_store(&file_info->paver.offset, 0);
286    atomic_store(&paving_in_progress, true);
287
288    if ((thrd_create(&file_info->paver.buf_copy_thrd, paver_copy_buffer, (void*)file_info))
289        != thrd_success) {
290        printf("netsvc: unable to launch buffer copy thread\n");
291        status = ZX_ERR_NO_RESOURCES;
292        goto dealloc_buffer;
293    }
294    thrd_detach(file_info->paver.buf_copy_thrd);
295
296    return TFTP_NO_ERROR;
297
298dealloc_buffer:
299    dealloc_paver_buffer(file_info);
300
301err_close_fds:
302    close(fds[1]);
303    close(logfds[0]);
304    return status;
305}
306
307static tftp_status file_open_write(const char* filename, size_t size,
308                                   void* cookie) {
309    // Make sure all in-progress paving options have completed
310    if (atomic_load(&paving_in_progress) == true) {
311        return TFTP_ERR_SHOULD_WAIT;
312    }
313    file_info_t* file_info = cookie;
314    file_info->is_write = true;
315    strncpy(file_info->filename, filename, PATH_MAX);
316    file_info->filename[PATH_MAX] = '\0';
317
318    if (netbootloader && !strncmp(filename, NB_FILENAME_PREFIX, NB_FILENAME_PREFIX_LEN)) {
319        // netboot
320        file_info->type = netboot;
321        file_info->netboot_file = netboot_get_buffer(filename, size);
322        if (file_info->netboot_file != NULL) {
323            return TFTP_NO_ERROR;
324        }
325    } else if (netbootloader & !strncmp(filename, NB_IMAGE_PREFIX, NB_IMAGE_PREFIX_LEN)) {
326        // paver
327        tftp_status status = paver_open_write(filename, size, file_info);
328        if (status != TFTP_NO_ERROR) {
329            file_info->filename[0] = '\0';
330        }
331        return status;
332    } else {
333        // netcp
334        if (netfile_open(filename, O_WRONLY, NULL) == 0) {
335            return TFTP_NO_ERROR;
336        }
337    }
338    return TFTP_ERR_INVALID_ARGS;
339}
340
341static tftp_status file_read(void* data, size_t* length, off_t offset, void* cookie) {
342    if (length == NULL) {
343        return TFTP_ERR_INVALID_ARGS;
344    }
345    int read_len = netfile_offset_read(data, offset, *length);
346    if (read_len < 0) {
347        return TFTP_ERR_IO;
348    }
349    *length = read_len;
350    return TFTP_NO_ERROR;
351}
352
353static tftp_status file_write(const void* data, size_t* length, off_t offset, void* cookie) {
354    if (length == NULL) {
355        return TFTP_ERR_INVALID_ARGS;
356    }
357    file_info_t* file_info = cookie;
358    if (file_info->type == netboot && file_info->netboot_file != NULL) {
359        nbfile* nb_file = file_info->netboot_file;
360        if (((size_t)offset > nb_file->size) || (offset + *length) > nb_file->size) {
361            return TFTP_ERR_INVALID_ARGS;
362        }
363        memcpy(nb_file->data + offset, data, *length);
364        nb_file->offset = offset + *length;
365        return TFTP_NO_ERROR;
366    } else if (file_info->type == paver) {
367        if (!atomic_load(&paving_in_progress)) {
368          printf("netsvc: paver exited prematurely\n");
369          return TFTP_ERR_IO;
370        }
371
372        if (((size_t)offset > file_info->paver.size)
373            || (offset + *length) > file_info->paver.size) {
374            return TFTP_ERR_INVALID_ARGS;
375        }
376        memcpy(&file_info->paver.buffer[offset], data, *length);
377        size_t new_offset = offset + *length;
378        atomic_store(&file_info->paver.offset, new_offset);
379        // Wake the paver thread, if it is waiting for data
380        sync_completion_signal(&file_info->paver.data_ready);
381        return TFTP_NO_ERROR;
382    } else {
383        int write_result = netfile_offset_write(data, offset, *length);
384        if ((size_t)write_result == *length) {
385            return TFTP_NO_ERROR;
386        }
387        if (write_result == -EBADF) {
388            return TFTP_ERR_BAD_STATE;
389        }
390        return TFTP_ERR_IO;
391    }
392}
393
394static void file_close(void* cookie) {
395    file_info_t* file_info = cookie;
396    if (file_info->type == netboot && file_info->netboot_file == NULL) {
397        netfile_close();
398    } else if (file_info->type == paver) {
399        unsigned int refcount = atomic_fetch_sub(&file_info->paver.buf_refcount, 1);
400        if (refcount == 1) {
401            dealloc_paver_buffer(file_info);
402        }
403    }
404}
405
406static tftp_status transport_send(void* data, size_t len, void* transport_cookie) {
407    transport_info_t* transport_info = transport_cookie;
408    zx_status_t status = udp6_send(data, len, &transport_info->dest_addr,
409                                   transport_info->dest_port, NB_TFTP_OUTGOING_PORT, true);
410    if (status != ZX_OK) {
411        return TFTP_ERR_IO;
412    }
413
414    // The timeout is relative to sending instead of receiving a packet, since there are some
415    // received packets we want to ignore (duplicate ACKs).
416    if (transport_info->timeout_ms != 0) {
417        tftp_next_timeout = zx_deadline_after(ZX_MSEC(transport_info->timeout_ms));
418        update_timeouts();
419    }
420    return TFTP_NO_ERROR;
421}
422
423static int transport_timeout_set(uint32_t timeout_ms, void* transport_cookie) {
424    transport_info_t* transport_info = transport_cookie;
425    transport_info->timeout_ms = timeout_ms;
426    return 0;
427}
428
429static void initialize_connection(const ip6_addr_t* saddr, uint16_t sport) {
430    int ret = tftp_init(&session, tftp_session_scratch,
431                        sizeof(tftp_session_scratch));
432    if (ret != TFTP_NO_ERROR) {
433        printf("netsvc: failed to initiate tftp session\n");
434        session = NULL;
435        return;
436    }
437
438    // Initialize file interface
439    tftp_file_interface file_ifc = {file_open_read, file_open_write,
440                                    file_read, file_write, file_close};
441    tftp_session_set_file_interface(session, &file_ifc);
442
443    // Initialize transport interface
444    memcpy(&transport_info.dest_addr, saddr, sizeof(ip6_addr_t));
445    transport_info.dest_port = sport;
446    transport_info.timeout_ms = TFTP_TIMEOUT_SECS * 1000;
447    tftp_transport_interface transport_ifc = {transport_send, NULL, transport_timeout_set};
448    tftp_session_set_transport_interface(session, &transport_ifc);
449}
450
451static void end_connection(void) {
452    session = NULL;
453    tftp_next_timeout = ZX_TIME_INFINITE;
454}
455
456void tftp_timeout_expired(void) {
457    tftp_status result = tftp_timeout(session, tftp_out_scratch, &last_msg_size,
458                                      sizeof(tftp_out_scratch), &transport_info.timeout_ms,
459                                      &file_info);
460    if (result == TFTP_ERR_TIMED_OUT) {
461        printf("netsvc: excessive timeouts, dropping tftp connection\n");
462        file_close(&file_info);
463        end_connection();
464        netfile_abort_write();
465    } else if (result < 0) {
466        printf("netsvc: failed to generate timeout response, dropping tftp connection\n");
467        file_close(&file_info);
468        end_connection();
469        netfile_abort_write();
470    } else {
471        if (last_msg_size > 0) {
472            tftp_status send_result = transport_send(tftp_out_scratch, last_msg_size,
473                                                     &transport_info);
474            if (send_result != TFTP_NO_ERROR) {
475                printf("netsvc: failed to send tftp timeout response (err = %d)\n", send_result);
476            }
477        }
478    }
479}
480
481void tftp_recv(void* data, size_t len,
482               const ip6_addr_t* daddr, uint16_t dport,
483               const ip6_addr_t* saddr, uint16_t sport) {
484    if (dport == NB_TFTP_INCOMING_PORT) {
485        if (session != NULL) {
486            printf("netsvc: only one simultaneous tftp session allowed\n");
487            // ignore attempts to connect when a session is in progress
488            return;
489        }
490        initialize_connection(saddr, sport);
491    } else if (!session) {
492        // Ignore anything sent to the outgoing port unless we've already
493        // established a connection.
494        return;
495    }
496
497    last_msg_size = sizeof(tftp_out_scratch);
498
499    char err_msg[128];
500    tftp_handler_opts handler_opts = {.inbuf = data,
501                                      .inbuf_sz = len,
502                                      .outbuf = tftp_out_scratch,
503                                      .outbuf_sz = &last_msg_size,
504                                      .err_msg = err_msg,
505                                      .err_msg_sz = sizeof(err_msg)};
506    tftp_status status = tftp_handle_msg(session, &transport_info, &file_info,
507                                         &handler_opts);
508    switch (status) {
509    case TFTP_NO_ERROR:
510        return;
511    case TFTP_TRANSFER_COMPLETED:
512        printf("netsvc: tftp %s of file %s completed\n",
513               file_info.is_write ? "write" : "read",
514               file_info.filename);
515        break;
516    case TFTP_ERR_SHOULD_WAIT:
517        break;
518    default:
519        printf("netsvc: %s\n", err_msg);
520        netfile_abort_write();
521        file_close(&file_info);
522        break;
523    }
524    end_connection();
525}
526
527bool tftp_has_pending(void) {
528    return session && tftp_session_has_pending(session);
529}
530
531void tftp_send_next(void) {
532    last_msg_size = sizeof(tftp_out_scratch);
533    tftp_prepare_data(session, tftp_out_scratch, &last_msg_size, &transport_info.timeout_ms,
534                      &file_info);
535    if (last_msg_size) {
536        transport_send(tftp_out_scratch, last_msg_size, &transport_info);
537    }
538}
539