| 235 | } |
| 236 | |
| 237 | Result io::copyFile(const std::string& srcPath, const std::string& dstPath, ProgressSink& sink, u64 commitWriteLimit, u64* bytesCopied, u32* crcOut) |
| 238 | { |
| 239 | FILE* src = fopen(srcPath.c_str(), "rb"); |
| 240 | if (src == NULL) { |
| 241 | Logging::error("Failed to open source file {} during copy with errno {}.", srcPath, errno); |
| 242 | return RES_COPY_FAILED; |
| 243 | } |
| 244 | fseek(src, 0, SEEK_END); |
| 245 | u64 sz = ftell(src); |
| 246 | rewind(src); |
| 247 | |
| 248 | const bool toSaveDevice = dstPath.rfind("save:/", 0) == 0; |
| 249 | |
| 250 | // Create the destination at its final size instead of letting it grow one |
| 251 | // write at a time. Every extending write on the save filesystem has to find |
| 252 | // and chain a free block, so an append-grown tree of tens of thousands of |
| 253 | // files both fragments and gets slower as the save fills. |
| 254 | bool preallocated = false; |
| 255 | if (toSaveDevice && sz > 0) { |
| 256 | const Result cr = fsdevCreateFile(dstPath.c_str(), (size_t)sz, 0); |
| 257 | if (R_SUCCEEDED(cr)) { |
| 258 | preallocated = true; |
| 259 | } |
| 260 | else { |
| 261 | Logging::debug("Preallocating {} at {} bytes failed with result 0x{:08X}; growing it by writes instead.", dstPath, sz, (u32)cr); |
| 262 | } |
| 263 | } |
| 264 | |
| 265 | // "wb" truncates, which would throw the preallocation away: an already-sized |
| 266 | // file has to be opened for update instead. |
| 267 | FILE* dst = fopen(dstPath.c_str(), preallocated ? "r+b" : "wb"); |
| 268 | if (dst == NULL) { |
| 269 | Logging::error("Failed to open destination file {} during copy with errno {}.", dstPath, errno); |
| 270 | fclose(src); |
| 271 | return RES_COPY_FAILED; |
| 272 | } |
| 273 | |
| 274 | u8* buf = new u8[BUFFER_SIZE]; |
| 275 | u64 offset = 0; |
| 276 | u32 crc = 0; |
| 277 | Result res = 0; |
| 278 | |
| 279 | size_t slashpos = srcPath.rfind("/"); |
| 280 | sink.startFile(srcPath.substr(slashpos + 1, srcPath.length() - slashpos - 1), sz); |
| 281 | |
| 282 | // The save journal only holds `commitWriteLimit` bytes of uncommitted writes: |
| 283 | // a single file bigger than that must be committed partway through, or the |
| 284 | // commit at the end would overflow the journal and fail (#443, #297). |
| 285 | u64 journalPending = 0; |
| 286 | |
| 287 | while (offset < sz) { |
| 288 | if (sink.cancelled()) { |
| 289 | break; |
| 290 | } |
| 291 | |
| 292 | size_t count = fread((char*)buf, 1, BUFFER_SIZE, src); |
| 293 | if (count == 0) { |
| 294 | Logging::error("fread returned 0 for file {} at offset {}/{} with errno {}. Aborting copy.", srcPath, offset, sz, errno); |
nothing calls this directly
no test coverage detected