Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
'src/node_errors.cc',
'src/node_external_reference.cc',
'src/node_file.cc',
'src/node_file_utils.cc',
'src/node_http_parser.cc',
'src/node_http2.cc',
'src/node_i18n.cc',
Expand Down
255 changes: 255 additions & 0 deletions src/node_file_utils.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
#include "node_file_utils.h"

#include <climits>
#include <cstdio>
#include <cstring>
#include <functional>
#include <string>
#include <vector>

#include "util-inl.h"

#ifdef _WIN32
#include <io.h> // _S_IREAD _S_IWRITE
#ifndef S_IRUSR
#define S_IRUSR _S_IREAD
#endif // S_IRUSR
#ifndef S_IWUSR
#define S_IWUSR _S_IWRITE
#endif // S_IWUSR
#endif

namespace node {

int WriteFileSync(const char* path, uv_buf_t buf) {
return WriteFileSync(path, &buf, 1);
}

constexpr int64_t kCurrentFileOffset = -1;
int WriteFileSync(const char* path, uv_buf_t* bufs, size_t buf_count) {
uv_fs_t req;
int fd = uv_fs_open(nullptr,
&req,
path,
O_WRONLY | O_CREAT | O_TRUNC,
S_IWUSR | S_IRUSR,
nullptr);
uv_fs_req_cleanup(&req);
if (fd < 0) {
return fd;
}

// Handle potential partial writes by looping until all data is written.
std::vector<uv_buf_t> iovs(bufs, bufs + buf_count);
size_t idx = 0;

while (idx < iovs.size()) {
// Skip empty buffers.
while (idx < iovs.size() && iovs[idx].len == 0) {
idx++;
}
if (idx >= iovs.size()) { // No non-empty buffers left.
break;
}
Comment on lines +48 to +53
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took me a moment to understand the connection between these and the outer loop. Perhaps a clearer version might simply be:

Suggested change
while (idx < iovs.size() && iovs[idx].len == 0) {
idx++;
}
if (idx >= iovs.size()) { // No non-empty buffers left.
break;
}
if (iovs[idx].len == 0) {
idx++;
continue;
}


uv_fs_write(nullptr,
&req,
fd,
iovs.data() + idx,
iovs.size() - idx,
kCurrentFileOffset,
nullptr);
if (req.result < 0) { // Error during write.
int err = req.result;
uv_fs_req_cleanup(&req);
uv_fs_close(nullptr, &req, fd, nullptr);
uv_fs_req_cleanup(&req);
return err;
}
if (req.result == 0) { // Should not happen unless the file system is full.
uv_fs_req_cleanup(&req);
uv_fs_close(nullptr, &req, fd, nullptr);
uv_fs_req_cleanup(&req);
return UV_EIO;
}
Comment on lines +62 to +74
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could merge those to:

Suggested change
if (req.result < 0) { // Error during write.
int err = req.result;
uv_fs_req_cleanup(&req);
uv_fs_close(nullptr, &req, fd, nullptr);
uv_fs_req_cleanup(&req);
return err;
}
if (req.result == 0) { // Should not happen unless the file system is full.
uv_fs_req_cleanup(&req);
uv_fs_close(nullptr, &req, fd, nullptr);
uv_fs_req_cleanup(&req);
return UV_EIO;
}
if (req.result <= 0) { // Error during write.
// UV_EIO should not happen unless the file system is full.
int err = req.result < 0 ? req.result : UV_EIO;
uv_fs_req_cleanup(&req);
uv_fs_close(nullptr, &req, fd, nullptr);
uv_fs_req_cleanup(&req);
return err;
}

size_t written = req.result;
uv_fs_req_cleanup(&req);

// Consume written bytes from buffers.
while (written > 0 && idx < iovs.size()) {
if (written >= iovs[idx].len) {
written -= iovs[idx].len;
idx++;
} else {
iovs[idx].base += written;
iovs[idx].len -= written;
written = 0;
}
}
}

int err = uv_fs_close(nullptr, &req, fd, nullptr);
uv_fs_req_cleanup(&req);
return err;
}

int WriteFileSync(v8::Isolate* isolate,
const char* path,
v8::Local<v8::String> string) {
node::Utf8Value utf8(isolate, string);
uv_buf_t buf = uv_buf_init(utf8.out(), utf8.length());
return WriteFileSync(path, buf);
}

// Default size used if fstat reports a file size of 0 for special files.
static constexpr size_t kDefaultReadSize = 8192;

// The resize_buffer callback is called with the file size after fstat, and must
// return a pointer to a buffer of at least that size. If the file grows during
// reading, resize_buffer may be called again with a larger size; the callback
// must preserve existing content and release old storage if needed.
// After reading completes, resize_buffer may be called with the actual bytes
// read.
template <typename ResizeBuffer>
int ReadFileSyncImpl(const char* path, ResizeBuffer resize_buffer) {
uv_fs_t req;

uv_file file = uv_fs_open(nullptr, &req, path, O_RDONLY, 0, nullptr);
if (req.result < 0) {
int err = req.result;
uv_fs_req_cleanup(&req);
return err;
}
uv_fs_req_cleanup(&req);

// Get the file size first, which should be cheap enough on an already opened
// files, and saves us from repeated reallocations/reads.
int err = uv_fs_fstat(nullptr, &req, file, nullptr);
if (err < 0) {
uv_fs_req_cleanup(&req);
uv_fs_close(nullptr, &req, file, nullptr);
uv_fs_req_cleanup(&req);
return err;
}
// SIZE_MAX is ~18 exabytes on 64-bit and ~4 GB on 32-bit systems.
// In both cases, the process is unlikely to have that much memory
// to hold the file content, so we just error with UV_EFBIG.
if (req.statbuf.st_size > static_cast<uint64_t>(SIZE_MAX)) {
uv_fs_req_cleanup(&req);
uv_fs_close(nullptr, &req, file, nullptr);
uv_fs_req_cleanup(&req);
return UV_EFBIG;
}
size_t size = static_cast<size_t>(req.statbuf.st_size);
uv_fs_req_cleanup(&req);

// If the file is reported as 0 bytes for special files, use a default
// size to start reading.
if (size == 0) {
size = kDefaultReadSize;
}

char* buffer = resize_buffer(size);
if (buffer == nullptr) {
uv_fs_close(nullptr, &req, file, nullptr);
uv_fs_req_cleanup(&req);
return UV_ENOMEM;
}
size_t total_read = 0;
while (true) {
size_t remaining = size - total_read;
// On Windows, uv_buf_t uses ULONG which may truncate the
// length for large buffers. Limit the individual read request size to
// INT_MAX to be safe. The loop will issue multiple reads for larger files.
if (remaining > INT_MAX) {
remaining = INT_MAX;
}
uv_buf_t buf = uv_buf_init(buffer + total_read, remaining);
uv_fs_read(
nullptr, &req, file, &buf, 1 /* nbufs */, kCurrentFileOffset, nullptr);
ssize_t bytes_read = req.result;
uv_fs_req_cleanup(&req);
if (bytes_read < 0) {
uv_fs_close(nullptr, &req, file, nullptr);
uv_fs_req_cleanup(&req);
return bytes_read;
}
if (bytes_read == 0) {
// EOF, stop reading.
break;
}
total_read += bytes_read;
if (total_read == size) {
// Buffer is full, the file may have grown during reading.
// Try increasing the buffer size and reading more.
if (size == SIZE_MAX) {
uv_fs_close(nullptr, &req, file, nullptr);
uv_fs_req_cleanup(&req);
return UV_EFBIG;
}
if (size > SIZE_MAX / 2) {
size = SIZE_MAX;
} else {
size *= 2;
}
buffer = resize_buffer(size);
if (buffer == nullptr) {
uv_fs_close(nullptr, &req, file, nullptr);
uv_fs_req_cleanup(&req);
return UV_ENOMEM;
}
}
}

int close_err = uv_fs_close(nullptr, &req, file, nullptr);
uv_fs_req_cleanup(&req);
if (close_err < 0) {
return close_err;
}

// Truncate the actual size read if necessary.
if (total_read != size) {
buffer = resize_buffer(total_read);
if (buffer == nullptr && total_read != 0) {
return UV_ENOMEM;
}
}
return 0;
}

int ReadFileSync(const char* path, std::string* result) {
return ReadFileSyncImpl(path, [result](size_t size) {
result->resize(size);
return result->data();
});
}

// Legacy interface. TODO(joyeecheung): update the callers to pass path first,
// output parameters second.
int ReadFileSync(std::string* result, const char* path) {
return ReadFileSync(path, result);
}

int ReadFileSync(const char* path, std::vector<uint8_t>* result) {
return ReadFileSyncImpl(path, [result](size_t size) {
result->resize(size);
return reinterpret_cast<char*>(result->data());
});
}

std::vector<char> ReadFileSync(FILE* fp) {
CHECK_EQ(ftell(fp), 0);
int err = fseek(fp, 0, SEEK_END);
CHECK_EQ(err, 0);
size_t size = ftell(fp);
CHECK_NE(size, static_cast<size_t>(-1L));
err = fseek(fp, 0, SEEK_SET);
CHECK_EQ(err, 0);

std::vector<char> contents(size);
size_t num_read = fread(contents.data(), size, 1, fp);
CHECK_EQ(num_read, 1);
return contents;
}

} // namespace node
39 changes: 39 additions & 0 deletions src/node_file_utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#ifndef SRC_NODE_FILE_UTILS_H_
#define SRC_NODE_FILE_UTILS_H_

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include <cstdio>
#include <functional>
#include <string>
#include <vector>

#include "uv.h"
#include "v8.h"

namespace node {

// Synchronously writes to a file. If the file exists, it is replaced
// (truncated).
int WriteFileSync(const char* path, uv_buf_t buf);
int WriteFileSync(const char* path, uv_buf_t* bufs, size_t buf_count);
int WriteFileSync(v8::Isolate* isolate,
const char* path,
v8::Local<v8::String> string);

// Synchronously reads the entire contents of a file.
int ReadFileSync(const char* path, std::string* result);
int ReadFileSync(const char* path, std::vector<uint8_t>* result);

// Legacy interface. TODO(joyeecheung): update the callers to pass path first,
// output parameters second.
int ReadFileSync(std::string* result, const char* path);

// This is currently only used by embedder APIs that take a FILE*.
std::vector<char> ReadFileSync(FILE* fp);

} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#endif // SRC_NODE_FILE_UTILS_H_
6 changes: 1 addition & 5 deletions src/node_internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "env.h"
#include "node.h"
#include "node_binding.h"
#include "node_file_utils.h"
#include "node_mutex.h"
#include "tracing/trace_event.h"
#include "util.h"
Expand Down Expand Up @@ -411,11 +412,6 @@ typedef struct tm TIME_TYPE;
#endif

double GetCurrentTimeInMicroseconds();
int WriteFileSync(const char* path, uv_buf_t* bufs, size_t buf_count);
int WriteFileSync(const char* path, uv_buf_t buf);
int WriteFileSync(v8::Isolate* isolate,
const char* path,
v8::Local<v8::String> string);

class DiagnosticFilename {
public:
Expand Down
7 changes: 2 additions & 5 deletions src/node_sea_bin.cc
Original file line number Diff line number Diff line change
Expand Up @@ -405,8 +405,8 @@ ExitCode BuildSingleExecutable(const std::string& sea_config_path,
int src_mode = static_cast<int>(req.statbuf.st_mode);
uv_fs_req_cleanup(&req);

std::string exe;
r = ReadFileSync(&exe, config.executable_path.c_str());
std::vector<uint8_t> exe_data;
r = ReadFileSync(config.executable_path.c_str(), &exe_data);
if (r != 0) {
FPrintF(stderr,
"Error: Couldn't read executable %s: %s\n",
Expand All @@ -415,9 +415,6 @@ ExitCode BuildSingleExecutable(const std::string& sea_config_path,
return ExitCode::kGenericUserError;
}

// TODO(joyeecheung): add a variant of ReadFileSync that reads into
// vector<uint8_t> directly and avoid this copy.
std::vector<uint8_t> exe_data(exe.begin(), exe.end());
std::vector<char> sea_blob;
ExitCode code =
GenerateSingleExecutableBlob(&sea_blob, config, args, exec_args);
Expand Down
Loading
Loading