blob: fb1a9e9d5ba026bca913fc8af7c85268930bab43 [file] [log] [blame]
// Copyright 2016 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 "src/bringup/bin/netsvc/netcp.h"
#include <errno.h>
#include <fcntl.h>
#include <lib/netboot/netboot.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <zircon/processargs.h>
#include <zircon/syscalls.h>
namespace {
constexpr char TMP_SUFFIX[] = ".netsvc.tmp";
struct netcp_state {
int fd;
off_t offset;
// false: Filename is the open file and final destination
// true : Filename is final destination; open file has a magic tmp suffix
bool needs_rename;
char filename[PATH_MAX];
};
netcp_state netcp = {
.fd = -1,
.offset = 0,
.needs_rename = false,
.filename = {0},
};
int netcp_mkdir(const char* filename) {
const char* ptr = filename[0] == '/' ? filename + 1 : filename;
struct stat st;
char tmp[1024];
for (;;) {
ptr = strchr(ptr, '/');
if (!ptr) {
return 0;
}
memcpy(tmp, filename, ptr - filename);
tmp[ptr - filename] = '\0';
ptr += 1;
if (stat(tmp, &st) < 0) {
if (errno == ENOENT) {
if (mkdir(tmp, 0755) < 0) {
return -1;
}
} else {
return -1;
}
}
}
}
} // namespace
int netcp_open(const char* filename, uint32_t arg, size_t* file_size) {
if (netcp.fd >= 0) {
printf("netsvc: closing still-open '%s', replacing with '%s'\n", netcp.filename, filename);
close(netcp.fd);
netcp.fd = -1;
}
size_t len = strlen(filename);
strlcpy(netcp.filename, filename, sizeof(netcp.filename));
struct stat st;
again: // label here to catch filename=/path/to/new/directory/
if (stat(filename, &st) == 0 && S_ISDIR(st.st_mode)) {
errno = EISDIR;
goto err;
}
switch (arg) {
case O_RDONLY:
netcp.needs_rename = false;
netcp.fd = open(filename, O_RDONLY);
if (file_size) {
*file_size = st.st_size;
}
break;
case O_WRONLY: {
// If we're writing a file, actually write to "filename + TMP_SUFFIX",
// and rename to the final destination when we would close. This makes
// written files appear to atomically update.
if (len + strlen(TMP_SUFFIX) + 1 > PATH_MAX) {
errno = ENAMETOOLONG;
goto err;
}
strcat(netcp.filename, TMP_SUFFIX);
netcp.needs_rename = true;
netcp.fd = open(netcp.filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
netcp.filename[len] = '\0';
if (netcp.fd < 0 && errno == ENOENT) {
if (netcp_mkdir(filename) == 0) {
goto again;
}
}
break;
}
default:
printf("netsvc: open '%s' with invalid mode %d\n", filename, arg);
errno = EINVAL;
}
if (netcp.fd < 0) {
goto err;
} else {
strlcpy(netcp.filename, filename, sizeof(netcp.filename));
netcp.offset = 0;
}
return 0;
err:
netcp.filename[0] = '\0';
return -errno;
}
ssize_t netcp_offset_read(void* data_out, off_t offset, size_t max_len) {
if (netcp.fd < 0) {
printf("netsvc: read, but no open file\n");
return -EBADF;
}
if (offset != netcp.offset) {
if (lseek(netcp.fd, offset, SEEK_SET) != offset) {
return -errno;
}
netcp.offset = offset;
}
return netcp_read(data_out, max_len);
}
ssize_t netcp_read(void* data_out, size_t data_sz) {
if (netcp.fd < 0) {
printf("netsvc: read, but no open file\n");
return -EBADF;
}
ssize_t n = read(netcp.fd, data_out, data_sz);
if (n < 0) {
printf("netsvc: error reading '%s': %d\n", netcp.filename, errno);
int result = (errno == 0) ? -EIO : -errno;
close(netcp.fd);
netcp.fd = -1;
return result;
}
netcp.offset += n;
return n;
}
ssize_t netcp_offset_write(const char* data, off_t offset, size_t length) {
if (netcp.fd < 0) {
printf("netsvc: write, but no open file\n");
return -EBADF;
}
if (offset != netcp.offset) {
if (lseek(netcp.fd, offset, SEEK_SET) != offset) {
return -errno;
}
netcp.offset = offset;
}
return netcp_write(data, length);
}
ssize_t netcp_write(const char* data, size_t len) {
if (netcp.fd < 0) {
printf("netsvc: write, but no open file\n");
return -EBADF;
}
ssize_t n = write(netcp.fd, data, len);
if (n != static_cast<ssize_t>(len)) {
printf("netsvc: error writing %s: %d\n", netcp.filename, errno);
int result = (errno == 0) ? -EIO : -errno;
close(netcp.fd);
netcp.fd = -1;
return result;
}
netcp.offset += len;
return len;
}
int netcp_close() {
int result = 0;
if (netcp.fd < 0) {
printf("netsvc: close, but no open file\n");
} else {
if (netcp.needs_rename) {
char src[PATH_MAX];
strlcpy(src, netcp.filename, sizeof(src));
strlcat(src, TMP_SUFFIX, sizeof(src));
if (rename(src, netcp.filename)) {
printf("netsvc: failed to rename temporary file: %s\n", strerror(errno));
}
}
if (close(netcp.fd)) {
result = (errno == 0) ? -EIO : -errno;
}
netcp.fd = -1;
}
return result;
}
// Clean up if we abort before finishing a write. Close out and unlink it, rather than
// leaving an incomplete file.
void netcp_abort_write() {
if (netcp.fd < 0) {
return;
}
close(netcp.fd);
netcp.fd = -1;
char tmp[PATH_MAX];
const char* filename;
if (netcp.needs_rename) {
strlcpy(tmp, netcp.filename, sizeof(tmp));
strlcat(tmp, TMP_SUFFIX, sizeof(tmp));
filename = tmp;
} else {
filename = netcp.filename;
}
if (unlink(filename) != 0) {
printf("netsvc: failed to unlink aborted file %s\n", filename);
}
}