|
| 1 | +#include "node_file_utils.h" |
| 2 | + |
| 3 | +#include <climits> |
| 4 | +#include <cstdio> |
| 5 | +#include <cstring> |
| 6 | +#include <functional> |
| 7 | +#include <string> |
| 8 | +#include <vector> |
| 9 | + |
| 10 | +#include "util-inl.h" |
| 11 | + |
| 12 | +#ifdef _WIN32 |
| 13 | +#include <io.h> // _S_IREAD _S_IWRITE |
| 14 | +#ifndef S_IRUSR |
| 15 | +#define S_IRUSR _S_IREAD |
| 16 | +#endif // S_IRUSR |
| 17 | +#ifndef S_IWUSR |
| 18 | +#define S_IWUSR _S_IWRITE |
| 19 | +#endif // S_IWUSR |
| 20 | +#endif |
| 21 | + |
| 22 | +namespace node { |
| 23 | + |
| 24 | +int WriteFileSync(const char* path, uv_buf_t buf) { |
| 25 | + return WriteFileSync(path, &buf, 1); |
| 26 | +} |
| 27 | + |
| 28 | +constexpr int64_t kCurrentFileOffset = -1; |
| 29 | +int WriteFileSync(const char* path, uv_buf_t* bufs, size_t buf_count) { |
| 30 | + uv_fs_t req; |
| 31 | + int fd = uv_fs_open(nullptr, |
| 32 | + &req, |
| 33 | + path, |
| 34 | + O_WRONLY | O_CREAT | O_TRUNC, |
| 35 | + S_IWUSR | S_IRUSR, |
| 36 | + nullptr); |
| 37 | + uv_fs_req_cleanup(&req); |
| 38 | + if (fd < 0) { |
| 39 | + return fd; |
| 40 | + } |
| 41 | + |
| 42 | + // Handle potential partial writes by looping until all data is written. |
| 43 | + std::vector<uv_buf_t> iovs(bufs, bufs + buf_count); |
| 44 | + size_t idx = 0; |
| 45 | + |
| 46 | + while (idx < iovs.size()) { |
| 47 | + // Skip empty buffers. |
| 48 | + if (iovs[idx].len == 0) { |
| 49 | + idx++; |
| 50 | + continue; |
| 51 | + } |
| 52 | + |
| 53 | + uv_fs_write(nullptr, |
| 54 | + &req, |
| 55 | + fd, |
| 56 | + iovs.data() + idx, |
| 57 | + iovs.size() - idx, |
| 58 | + kCurrentFileOffset, |
| 59 | + nullptr); |
| 60 | + if (req.result <= 0) { // Error during write. |
| 61 | + // UV_EIO should not happen unless the file system is full. |
| 62 | + int err = req.result < 0 ? req.result : UV_EIO; |
| 63 | + uv_fs_req_cleanup(&req); |
| 64 | + uv_fs_close(nullptr, &req, fd, nullptr); |
| 65 | + uv_fs_req_cleanup(&req); |
| 66 | + return err; |
| 67 | + } |
| 68 | + size_t written = req.result; |
| 69 | + uv_fs_req_cleanup(&req); |
| 70 | + |
| 71 | + // Consume written bytes from buffers. |
| 72 | + while (written > 0 && idx < iovs.size()) { |
| 73 | + if (written >= iovs[idx].len) { |
| 74 | + written -= iovs[idx].len; |
| 75 | + idx++; |
| 76 | + } else { |
| 77 | + iovs[idx].base += written; |
| 78 | + iovs[idx].len -= written; |
| 79 | + written = 0; |
| 80 | + } |
| 81 | + } |
| 82 | + } |
| 83 | + |
| 84 | + int err = uv_fs_close(nullptr, &req, fd, nullptr); |
| 85 | + uv_fs_req_cleanup(&req); |
| 86 | + return err; |
| 87 | +} |
| 88 | + |
| 89 | +int WriteFileSync(v8::Isolate* isolate, |
| 90 | + const char* path, |
| 91 | + v8::Local<v8::String> string) { |
| 92 | + node::Utf8Value utf8(isolate, string); |
| 93 | + uv_buf_t buf = uv_buf_init(utf8.out(), utf8.length()); |
| 94 | + return WriteFileSync(path, buf); |
| 95 | +} |
| 96 | + |
| 97 | +// Default size used if fstat reports a file size of 0 for special files. |
| 98 | +static constexpr size_t kDefaultReadSize = 8192; |
| 99 | + |
| 100 | +// The resize_buffer callback is called with the file size after fstat, and must |
| 101 | +// return a pointer to a buffer of at least that size. If the file grows during |
| 102 | +// reading, resize_buffer may be called again with a larger size; the callback |
| 103 | +// must preserve existing content and release old storage if needed. |
| 104 | +// After reading completes, resize_buffer may be called with the actual bytes |
| 105 | +// read. |
| 106 | +template <typename ResizeBuffer> |
| 107 | +int ReadFileSyncImpl(const char* path, ResizeBuffer resize_buffer) { |
| 108 | + uv_fs_t req; |
| 109 | + |
| 110 | + uv_file file = uv_fs_open(nullptr, &req, path, O_RDONLY, 0, nullptr); |
| 111 | + if (req.result < 0) { |
| 112 | + int err = req.result; |
| 113 | + uv_fs_req_cleanup(&req); |
| 114 | + return err; |
| 115 | + } |
| 116 | + uv_fs_req_cleanup(&req); |
| 117 | + |
| 118 | + // Get the file size first, which should be cheap enough on an already opened |
| 119 | + // files, and saves us from repeated reallocations/reads. |
| 120 | + int err = uv_fs_fstat(nullptr, &req, file, nullptr); |
| 121 | + if (err < 0) { |
| 122 | + uv_fs_req_cleanup(&req); |
| 123 | + uv_fs_close(nullptr, &req, file, nullptr); |
| 124 | + uv_fs_req_cleanup(&req); |
| 125 | + return err; |
| 126 | + } |
| 127 | + // SIZE_MAX is ~18 exabytes on 64-bit and ~4 GB on 32-bit systems. |
| 128 | + // In both cases, the process is unlikely to have that much memory |
| 129 | + // to hold the file content, so we just error with UV_EFBIG. |
| 130 | + if (req.statbuf.st_size > static_cast<uint64_t>(SIZE_MAX)) { |
| 131 | + uv_fs_req_cleanup(&req); |
| 132 | + uv_fs_close(nullptr, &req, file, nullptr); |
| 133 | + uv_fs_req_cleanup(&req); |
| 134 | + return UV_EFBIG; |
| 135 | + } |
| 136 | + size_t size = static_cast<size_t>(req.statbuf.st_size); |
| 137 | + uv_fs_req_cleanup(&req); |
| 138 | + |
| 139 | + // If the file is reported as 0 bytes for special files, use a default |
| 140 | + // size to start reading. |
| 141 | + if (size == 0) { |
| 142 | + size = kDefaultReadSize; |
| 143 | + } |
| 144 | + |
| 145 | + char* buffer = resize_buffer(size); |
| 146 | + if (buffer == nullptr) { |
| 147 | + uv_fs_close(nullptr, &req, file, nullptr); |
| 148 | + uv_fs_req_cleanup(&req); |
| 149 | + return UV_ENOMEM; |
| 150 | + } |
| 151 | + size_t total_read = 0; |
| 152 | + while (true) { |
| 153 | + size_t remaining = size - total_read; |
| 154 | + // On Windows, uv_buf_t uses ULONG which may truncate the |
| 155 | + // length for large buffers. Limit the individual read request size to |
| 156 | + // INT_MAX to be safe. The loop will issue multiple reads for larger files. |
| 157 | + if (remaining > INT_MAX) { |
| 158 | + remaining = INT_MAX; |
| 159 | + } |
| 160 | + uv_buf_t buf = uv_buf_init(buffer + total_read, remaining); |
| 161 | + uv_fs_read( |
| 162 | + nullptr, &req, file, &buf, 1 /* nbufs */, kCurrentFileOffset, nullptr); |
| 163 | + ssize_t bytes_read = req.result; |
| 164 | + uv_fs_req_cleanup(&req); |
| 165 | + if (bytes_read < 0) { |
| 166 | + uv_fs_close(nullptr, &req, file, nullptr); |
| 167 | + uv_fs_req_cleanup(&req); |
| 168 | + return bytes_read; |
| 169 | + } |
| 170 | + if (bytes_read == 0) { |
| 171 | + // EOF, stop reading. |
| 172 | + break; |
| 173 | + } |
| 174 | + total_read += bytes_read; |
| 175 | + if (total_read == size) { |
| 176 | + // Buffer is full, the file may have grown during reading. |
| 177 | + // Try increasing the buffer size and reading more. |
| 178 | + if (size == SIZE_MAX) { |
| 179 | + uv_fs_close(nullptr, &req, file, nullptr); |
| 180 | + uv_fs_req_cleanup(&req); |
| 181 | + return UV_EFBIG; |
| 182 | + } |
| 183 | + if (size > SIZE_MAX / 2) { |
| 184 | + size = SIZE_MAX; |
| 185 | + } else { |
| 186 | + size *= 2; |
| 187 | + } |
| 188 | + buffer = resize_buffer(size); |
| 189 | + if (buffer == nullptr) { |
| 190 | + uv_fs_close(nullptr, &req, file, nullptr); |
| 191 | + uv_fs_req_cleanup(&req); |
| 192 | + return UV_ENOMEM; |
| 193 | + } |
| 194 | + } |
| 195 | + } |
| 196 | + |
| 197 | + int close_err = uv_fs_close(nullptr, &req, file, nullptr); |
| 198 | + uv_fs_req_cleanup(&req); |
| 199 | + if (close_err < 0) { |
| 200 | + return close_err; |
| 201 | + } |
| 202 | + |
| 203 | + // Truncate the actual size read if necessary. |
| 204 | + if (total_read != size) { |
| 205 | + buffer = resize_buffer(total_read); |
| 206 | + if (buffer == nullptr && total_read != 0) { |
| 207 | + return UV_ENOMEM; |
| 208 | + } |
| 209 | + } |
| 210 | + return 0; |
| 211 | +} |
| 212 | + |
| 213 | +int ReadFileSync(const char* path, std::string* result) { |
| 214 | + return ReadFileSyncImpl(path, [result](size_t size) { |
| 215 | + result->resize(size); |
| 216 | + return result->data(); |
| 217 | + }); |
| 218 | +} |
| 219 | + |
| 220 | +// Legacy interface. TODO(joyeecheung): update the callers to pass path first, |
| 221 | +// output parameters second. |
| 222 | +int ReadFileSync(std::string* result, const char* path) { |
| 223 | + return ReadFileSync(path, result); |
| 224 | +} |
| 225 | + |
| 226 | +int ReadFileSync(const char* path, std::vector<uint8_t>* result) { |
| 227 | + return ReadFileSyncImpl(path, [result](size_t size) { |
| 228 | + result->resize(size); |
| 229 | + return reinterpret_cast<char*>(result->data()); |
| 230 | + }); |
| 231 | +} |
| 232 | + |
| 233 | +std::vector<char> ReadFileSync(FILE* fp) { |
| 234 | + CHECK_EQ(ftell(fp), 0); |
| 235 | + int err = fseek(fp, 0, SEEK_END); |
| 236 | + CHECK_EQ(err, 0); |
| 237 | + size_t size = ftell(fp); |
| 238 | + CHECK_NE(size, static_cast<size_t>(-1L)); |
| 239 | + err = fseek(fp, 0, SEEK_SET); |
| 240 | + CHECK_EQ(err, 0); |
| 241 | + |
| 242 | + std::vector<char> contents(size); |
| 243 | + size_t num_read = fread(contents.data(), size, 1, fp); |
| 244 | + CHECK_EQ(num_read, 1); |
| 245 | + return contents; |
| 246 | +} |
| 247 | + |
| 248 | +} // namespace node |
0 commit comments