| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <fbl/auto_call.h> |
| #include <fbl/unique_fd.h> |
| #include <fbl/unique_ptr.h> |
| #include <zircon/types.h> |
| |
| #include <dirent.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include <utility> |
| |
| #include "xdc-init.h" |
| |
| // TODO(jocelyndang): investigate issue with larger buffer sizes. |
| static constexpr uint32_t BUFFER_SIZE = 8 * 1024; |
| static constexpr uint32_t DEFAULT_STREAM_ID = 1; |
| |
| typedef struct { |
| off_t file_size; |
| } file_header_t; |
| |
| static void usage(const char* prog_name) { |
| printf("usage:\n"); |
| printf("%s [options]\n", prog_name); |
| printf("\nOptions\n"); |
| printf( |
| " -i <stream id> : ID of stream to transfer over, must be positive. Defaults to 1.\n" |
| " -f <filename> : Name of file to write to or read from.\n" |
| " -d : Download from xdc. This is the default if no mode is specified.\n" |
| " -u : Upload to xdc.\n"); |
| } |
| |
| // Reads the file header from the xdc device and stores it in out_file_header. |
| static zx_status_t read_file_header(const fbl::unique_fd& xdc_fd, file_header_t* out_file_header) { |
| unsigned char* buf = reinterpret_cast<unsigned char*>(out_file_header); |
| |
| ssize_t res; |
| size_t total_read = 0; |
| size_t len = sizeof(file_header_t); |
| while ((total_read < len) && |
| ((res = read(xdc_fd.get(), buf + total_read, len - total_read)) != 0)) { |
| if (res < 0) { |
| printf("Fatal read error: %s\n", strerror(errno)); |
| return ZX_ERR_IO; |
| } |
| total_read += res; |
| } |
| if (total_read != len) { |
| fprintf(stderr, "Malformed file header, only read %lu bytes, want %lu\n", total_read, len); |
| return ZX_ERR_BAD_STATE; |
| } |
| return ZX_OK; |
| } |
| |
| // Writes the file header to the xdc device and also stores it in out_file_header. |
| static zx_status_t write_file_header(const fbl::unique_fd& file_fd, fbl::unique_fd& xdc_fd, |
| file_header_t* out_file_header) { |
| struct stat s; |
| if (fstat(file_fd.get(), &s) < 0) { |
| fprintf(stderr, "could not get size of file, err: %s\n", strerror(errno)); |
| return ZX_ERR_IO; |
| } |
| file_header_t file_header = {.file_size = s.st_size}; |
| unsigned char* buf = reinterpret_cast<unsigned char*>(&file_header); |
| ssize_t res = write(xdc_fd.get(), buf, sizeof(file_header)); |
| if (sizeof(res) != sizeof(file_header)) { |
| fprintf(stderr, "Fatal write err: %s\n", strerror(errno)); |
| return ZX_ERR_IO; |
| } |
| ZX_DEBUG_ASSERT(out_file_header != nullptr); |
| memcpy(out_file_header, &file_header, sizeof(file_header)); |
| return ZX_OK; |
| } |
| |
| // Reads from the src_fd and writes to the dest_fd until src_len bytes has been written, |
| // or a fatal error occurs while reading or writing. |
| static zx_status_t transfer(fbl::unique_fd& src_fd, off_t src_len, fbl::unique_fd& dest_fd) { |
| printf("Transferring file of size %jd bytes.\n", (uintmax_t)src_len); |
| |
| fbl::unique_ptr<unsigned char*[]> buf(new unsigned char*[BUFFER_SIZE]); |
| ssize_t res; |
| off_t total_read = 0; |
| while ((total_read < src_len) && ((res = read(src_fd.get(), buf.get(), BUFFER_SIZE)) != 0)) { |
| if (res < 0) { |
| fprintf(stderr, "Fatal read error: %s\n", strerror(errno)); |
| return ZX_ERR_IO; |
| } |
| total_read += res; |
| |
| ssize_t buf_len = res; |
| ssize_t total_written = 0; |
| while (total_written < buf_len) { |
| ssize_t res = write(dest_fd.get(), buf.get() + total_written, buf_len - total_written); |
| if (res < 0) { |
| fprintf(stderr, "Fatal write err: %s\n", strerror(errno)); |
| return ZX_ERR_IO; |
| } |
| total_written += res; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| int main(int argc, char** argv) { |
| auto print_usage = fbl::MakeAutoCall([argv]() { usage(argv[0]); }); |
| |
| const char* filename = nullptr; |
| uint32_t stream_id = DEFAULT_STREAM_ID; |
| bool download = true; |
| |
| int opt; |
| while ((opt = getopt(argc, argv, "i:f:du")) != -1) { |
| switch (opt) { |
| case 'i': |
| if (sscanf(optarg, "%u", &stream_id) != 1) { |
| fprintf(stderr, "Failed to parse stream id: \"%s\"\n", optarg); |
| return -1; |
| } |
| if (stream_id == 0) { |
| fprintf(stderr, "Stream ID must be positive\n"); |
| return -1; |
| } |
| break; |
| case 'f': |
| filename = optarg; |
| break; |
| case 'd': |
| download = true; |
| break; |
| case 'u': |
| download = false; |
| break; |
| default: |
| fprintf(stderr, "Invalid option\n"); |
| return -1; |
| } |
| } |
| if (!filename) { |
| fprintf(stderr, "No file specified\n"); |
| return -1; |
| } |
| // Finished parsing the arguments without error. |
| print_usage.cancel(); |
| |
| fbl::unique_fd xdc_fd; |
| zx_status_t status = configure_xdc(stream_id, &xdc_fd); |
| if (status != ZX_OK) { |
| return -1; |
| } |
| |
| int file_flags = download ? (O_RDWR | O_CREAT) : O_RDONLY; |
| fbl::unique_fd file_fd(open(filename, file_flags, 0666)); |
| if (!file_fd) { |
| fprintf(stderr, "Failed to open \"%s\", err %s\n", filename, strerror(errno)); |
| return -1; |
| } |
| |
| fbl::unique_fd src_fd; |
| fbl::unique_fd dest_fd; |
| |
| file_header_t file_header; |
| if (download) { |
| if (read_file_header(xdc_fd, &file_header) != ZX_OK) { |
| return -1; |
| } |
| src_fd = std::move(xdc_fd); |
| dest_fd = std::move(file_fd); |
| } else { |
| if (write_file_header(file_fd, xdc_fd, &file_header) != ZX_OK) { |
| return -1; |
| } |
| src_fd = std::move(file_fd); |
| dest_fd = std::move(xdc_fd); |
| } |
| |
| status = transfer(src_fd, file_header.file_size, dest_fd); |
| if (status != ZX_OK) { |
| return -1; |
| } |
| return 0; |
| } |