1// Copyright 2018 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 <fbl/auto_call.h>
6#include <fbl/unique_fd.h>
7#include <fbl/unique_ptr.h>
8#include <zircon/device/debug.h>
9#include <zircon/types.h>
10
11#include <dirent.h>
12#include <errno.h>
13#include <fcntl.h>
14#include <stdio.h>
15#include <string.h>
16#include <sys/stat.h>
17#include <unistd.h>
18
19#include "xdc-init.h"
20
21// TODO(jocelyndang): investigate issue with larger buffer sizes.
22static constexpr uint32_t BUFFER_SIZE = 8 * 1024;
23static constexpr uint32_t DEFAULT_STREAM_ID = 1;
24
25typedef struct {
26    off_t file_size;
27} file_header_t;
28
29static void usage(const char* prog_name) {
30    printf("usage:\n");
31    printf("%s [options]\n", prog_name);
32    printf("\nOptions\n");
33    printf("  -i <stream id>  : ID of stream to transfer over, must be positive. Defaults to 1.\n"
34           "  -f <filename>   : Name of file to write to or read from.\n"
35           "  -d              : Download from xdc. This is the default if no mode is specified.\n"
36           "  -u              : Upload to xdc.\n");
37}
38
39// Reads the file header from the xdc device and stores it in out_file_header.
40static zx_status_t read_file_header(const fbl::unique_fd& xdc_fd, file_header_t* out_file_header) {
41    unsigned char* buf = reinterpret_cast<unsigned char*>(out_file_header);
42
43    ssize_t res;
44    size_t total_read = 0;
45    size_t len = sizeof(file_header_t);
46    while ((total_read < len) &&
47           ((res = read(xdc_fd.get(), buf + total_read, len - total_read)) != 0)) {
48        if (res < 0) {
49            printf("Fatal read error: %s\n", strerror(errno));
50            return ZX_ERR_IO;
51        }
52        total_read += res;
53    }
54    if (total_read != len) {
55        fprintf(stderr, "Malformed file header, only read %lu bytes, want %lu\n", total_read, len);
56        return ZX_ERR_BAD_STATE;
57    }
58    return ZX_OK;
59}
60
61// Writes the file header to the xdc device and also stores it in out_file_header.
62static zx_status_t write_file_header(const fbl::unique_fd& file_fd, fbl::unique_fd& xdc_fd,
63                                     file_header_t* out_file_header) {
64    struct stat s;
65    if (fstat(file_fd.get(), &s) < 0) {
66        fprintf(stderr, "could not get size of file, err: %s\n", strerror(errno));
67        return ZX_ERR_IO;
68    }
69    file_header_t file_header = { .file_size = s.st_size };
70    unsigned char* buf = reinterpret_cast<unsigned char*>(&file_header);
71    ssize_t res = write(xdc_fd.get(), buf, sizeof(file_header));
72    if (sizeof(res) != sizeof(file_header)) {
73        fprintf(stderr, "Fatal write err: %s\n", strerror(errno));
74        return ZX_ERR_IO;
75    }
76    ZX_DEBUG_ASSERT(out_file_header != nullptr);
77    memcpy(out_file_header, &file_header, sizeof(file_header));
78    return ZX_OK;
79}
80
81// Reads from the src_fd and writes to the dest_fd until src_len bytes has been written,
82// or a fatal error occurs while reading or writing.
83static zx_status_t transfer(fbl::unique_fd& src_fd, off_t src_len, fbl::unique_fd& dest_fd) {
84    printf("Transferring file of size %jd bytes.\n", (uintmax_t)src_len);
85
86    fbl::unique_ptr<unsigned char*[]> buf(new unsigned char*[BUFFER_SIZE]);
87    ssize_t res;
88    off_t total_read = 0;
89    while ((total_read < src_len) &&
90           ((res = read(src_fd.get(), buf.get(), BUFFER_SIZE)) != 0)) {
91        if (res < 0) {
92            fprintf(stderr, "Fatal read error: %s\n", strerror(errno));
93            return ZX_ERR_IO;
94        }
95        total_read += res;
96
97        ssize_t buf_len = res;
98        ssize_t total_written = 0;
99        while (total_written < buf_len) {
100            ssize_t res = write(dest_fd.get(), buf.get() + total_written, buf_len - total_written);
101            if (res < 0) {
102                fprintf(stderr, "Fatal write err: %s\n", strerror(errno));
103                return ZX_ERR_IO;
104            }
105            total_written += res;
106        }
107    }
108    return ZX_OK;
109}
110
111int main(int argc, char** argv) {
112    auto print_usage = fbl::MakeAutoCall([argv]() { usage(argv[0]); });
113
114    const char* filename = nullptr;
115    uint32_t stream_id = DEFAULT_STREAM_ID;
116    bool download = true;
117
118    int opt;
119    while ((opt = getopt(argc, argv, "i:f:du")) != -1) {
120        switch (opt) {
121        case 'i':
122            if (sscanf(optarg, "%u", &stream_id) != 1) {
123                fprintf(stderr, "Failed to parse stream id: \"%s\"\n", optarg);
124                return -1;
125            }
126            if (stream_id == 0) {
127                fprintf(stderr, "Stream ID must be positive\n");
128                return -1;
129            }
130            break;
131        case 'f':
132            filename = optarg;
133            break;
134        case 'd':
135            download = true;
136            break;
137        case 'u':
138            download = false;
139            break;
140        default:
141            fprintf(stderr, "Invalid option\n");
142            return -1;
143        }
144    }
145    if (!filename) {
146        fprintf(stderr, "No file specified\n");
147        return -1;
148    }
149    // Finished parsing the arguments without error.
150    print_usage.cancel();
151
152    fbl::unique_fd xdc_fd;
153    zx_status_t status = configure_xdc(stream_id, xdc_fd);
154    if (status != ZX_OK) {
155        return -1;
156    }
157
158    int file_flags = download ? (O_RDWR | O_CREAT) : O_RDONLY;
159    fbl::unique_fd file_fd(open(filename, file_flags, 0666));
160    if (!file_fd) {
161        fprintf(stderr, "Failed to open \"%s\", err %s\n", filename, strerror(errno));
162        return -1;
163    }
164
165    fbl::unique_fd src_fd;
166    fbl::unique_fd dest_fd;
167
168    file_header_t file_header;
169    if (download) {
170        if (read_file_header(xdc_fd, &file_header) != ZX_OK) {
171            return -1;
172        }
173        src_fd = fbl::move(xdc_fd);
174        dest_fd = fbl::move(file_fd);
175    } else {
176        if (write_file_header(file_fd, xdc_fd, &file_header) != ZX_OK) {
177            return -1;
178        }
179        src_fd = fbl::move(file_fd);
180        dest_fd = fbl::move(xdc_fd);
181    }
182
183    status = transfer(src_fd, file_header.file_size, dest_fd);
184    if (status != ZX_OK) {
185        return -1;
186    }
187    return 0;
188}
189