diff options
| author | Paul Eggert | 2025-01-22 11:06:06 -0800 |
|---|---|---|
| committer | Paul Eggert | 2025-07-13 21:09:38 -0700 |
| commit | ffd65be2277b9a30e77a00ad69c9ba21459f72c5 (patch) | |
| tree | 2a3cc5dc796280ec9cc86e1a374744b0b27169c0 /src | |
| parent | ae30a61c74215c3e02fb3c0d603894457a4cfef5 (diff) | |
| download | emacs-ffd65be2277b9a30e77a00ad69c9ba21459f72c5.tar.gz emacs-ffd65be2277b9a30e77a00ad69c9ba21459f72c5.zip | |
copy-file no longer trusts st_size
In copy-file, do not trust st_size, since it might change as we run,
or we might be in a /proc system where it is unreliable anyway.
Also, fix some other unlikely copy-file bugs while we’re here.
* src/fileio.c (Fcopy_file): Use O_TRUNC when opening a
destination that already exists. This saves us the trouble
of having to call ftruncate with a possibly-bogus st_size;
the old motivation for using ftruncate is no longer compelling.
Do not assume ptrdiff_t is as wide as ssize_t; although this is
true on all known platforms, it’s easy to not assume it.
Don’t trust st_size. Prefer SSIZE_MAX to TYPE_MAXIMUM (ssize_t).
Always read+write, regardless of whether copy_file_range failed.
Diffstat (limited to 'src')
| -rw-r--r-- | src/fileio.c | 64 |
1 files changed, 22 insertions, 42 deletions
diff --git a/src/fileio.c b/src/fileio.c index 4c3bc5787cf..82ee22d2187 100644 --- a/src/fileio.c +++ b/src/fileio.c | |||
| @@ -2369,15 +2369,13 @@ permissions. */) | |||
| 2369 | barf_or_query_if_file_exists (newname, true, "copy to it", | 2369 | barf_or_query_if_file_exists (newname, true, "copy to it", |
| 2370 | FIXNUMP (ok_if_already_exists), false); | 2370 | FIXNUMP (ok_if_already_exists), false); |
| 2371 | already_exists = true; | 2371 | already_exists = true; |
| 2372 | ofd = emacs_open (SSDATA (encoded_newname), O_WRONLY, 0); | 2372 | ofd = emacs_open (SSDATA (encoded_newname), O_WRONLY | O_TRUNC, 0); |
| 2373 | } | 2373 | } |
| 2374 | if (ofd < 0) | 2374 | if (ofd < 0) |
| 2375 | report_file_error ("Opening output file", newname); | 2375 | report_file_error ("Opening output file", newname); |
| 2376 | 2376 | ||
| 2377 | record_unwind_protect_int (close_file_unwind, ofd); | 2377 | record_unwind_protect_int (close_file_unwind, ofd); |
| 2378 | 2378 | ||
| 2379 | off_t oldsize = 0, newsize; | ||
| 2380 | |||
| 2381 | if (already_exists) | 2379 | if (already_exists) |
| 2382 | { | 2380 | { |
| 2383 | struct stat out_st; | 2381 | struct stat out_st; |
| @@ -2386,36 +2384,26 @@ permissions. */) | |||
| 2386 | if (st.st_dev == out_st.st_dev && st.st_ino == out_st.st_ino) | 2384 | if (st.st_dev == out_st.st_dev && st.st_ino == out_st.st_ino) |
| 2387 | report_file_errno ("Input and output files are the same", | 2385 | report_file_errno ("Input and output files are the same", |
| 2388 | list2 (file, newname), 0); | 2386 | list2 (file, newname), 0); |
| 2389 | if (S_ISREG (out_st.st_mode)) | ||
| 2390 | oldsize = out_st.st_size; | ||
| 2391 | } | 2387 | } |
| 2392 | 2388 | ||
| 2393 | maybe_quit (); | 2389 | maybe_quit (); |
| 2394 | 2390 | ||
| 2395 | if (emacs_fd_to_int (ifd) != -1 | 2391 | if (emacs_fd_to_int (ifd) == -1 |
| 2396 | && clone_file (ofd, emacs_fd_to_int (ifd))) | 2392 | || !clone_file (ofd, emacs_fd_to_int (ifd))) |
| 2397 | newsize = st.st_size; | ||
| 2398 | else | ||
| 2399 | { | 2393 | { |
| 2400 | off_t insize = st.st_size; | 2394 | off_t newsize = 0; |
| 2401 | ssize_t copied; | ||
| 2402 | 2395 | ||
| 2403 | #ifndef MSDOS | 2396 | #ifndef MSDOS |
| 2404 | newsize = 0; | ||
| 2405 | |||
| 2406 | if (emacs_fd_to_int (ifd) != -1) | 2397 | if (emacs_fd_to_int (ifd) != -1) |
| 2407 | { | 2398 | { |
| 2408 | for (; newsize < insize; newsize += copied) | 2399 | for (ssize_t copied; ; newsize += copied) |
| 2409 | { | 2400 | { |
| 2410 | /* Copy at most COPY_MAX bytes at a time; this is min | 2401 | /* Copy at most COPY_MAX bytes at a time; this is min |
| 2411 | (PTRDIFF_MAX, SIZE_MAX) truncated to a value that is | 2402 | (SSIZE_MAX, SIZE_MAX) truncated to a value that is |
| 2412 | surely aligned well. */ | 2403 | surely aligned well. */ |
| 2413 | ssize_t ssize_max = TYPE_MAXIMUM (ssize_t); | 2404 | ssize_t copy_max = min (SSIZE_MAX, SIZE_MAX) >> 30 << 30; |
| 2414 | ptrdiff_t copy_max = min (ssize_max, SIZE_MAX) >> 30 << 30; | ||
| 2415 | off_t intail = insize - newsize; | ||
| 2416 | ptrdiff_t len = min (intail, copy_max); | ||
| 2417 | copied = copy_file_range (emacs_fd_to_int (ifd), NULL, | 2405 | copied = copy_file_range (emacs_fd_to_int (ifd), NULL, |
| 2418 | ofd, NULL, len, 0); | 2406 | ofd, NULL, copy_max, 0); |
| 2419 | if (copied <= 0) | 2407 | if (copied <= 0) |
| 2420 | break; | 2408 | break; |
| 2421 | maybe_quit (); | 2409 | maybe_quit (); |
| @@ -2423,32 +2411,24 @@ permissions. */) | |||
| 2423 | } | 2411 | } |
| 2424 | #endif /* MSDOS */ | 2412 | #endif /* MSDOS */ |
| 2425 | 2413 | ||
| 2426 | /* Fall back on read+write if copy_file_range failed, or if the | 2414 | /* Follow up with read+write regardless of any copy_file_range failure. |
| 2427 | input is empty and so could be a /proc file, or if ifd is an | 2415 | Many copy_file_range implementations fail for no good reason, |
| 2428 | invention of android.c. read+write will either succeed, or | 2416 | or "succeed" even when they did nothing (e.g., in /proc files). |
| 2429 | report an error more precisely than copy_file_range | 2417 | Also, if read+write fails it will report an error more |
| 2430 | would. */ | 2418 | precisely than copy_file_range would. */ |
| 2431 | if (newsize != insize || insize == 0) | 2419 | char buf[MAX_ALLOCA]; |
| 2432 | { | ||
| 2433 | char buf[MAX_ALLOCA]; | ||
| 2434 | 2420 | ||
| 2435 | for (; (copied = emacs_fd_read (ifd, buf, sizeof buf)); | 2421 | for (ptrdiff_t copied; |
| 2436 | newsize += copied) | 2422 | (copied = emacs_fd_read (ifd, buf, sizeof buf)); |
| 2437 | { | 2423 | newsize += copied) |
| 2438 | if (copied < 0) | 2424 | { |
| 2439 | report_file_error ("Read error", file); | 2425 | if (copied < 0) |
| 2440 | if (emacs_write_quit (ofd, buf, copied) != copied) | 2426 | report_file_error ("Read error", file); |
| 2441 | report_file_error ("Write error", newname); | 2427 | if (emacs_write_quit (ofd, buf, copied) != copied) |
| 2442 | } | 2428 | report_file_error ("Write error", newname); |
| 2443 | } | 2429 | } |
| 2444 | } | 2430 | } |
| 2445 | 2431 | ||
| 2446 | /* Truncate any existing output file after writing the data. This | ||
| 2447 | is more likely to work than truncation before writing, if the | ||
| 2448 | file system is out of space or the user is over disk quota. */ | ||
| 2449 | if (newsize < oldsize && ftruncate (ofd, newsize) != 0) | ||
| 2450 | report_file_error ("Truncating output file", newname); | ||
| 2451 | |||
| 2452 | #ifndef MSDOS | 2432 | #ifndef MSDOS |
| 2453 | /* Preserve the original file permissions, and if requested, also its | 2433 | /* Preserve the original file permissions, and if requested, also its |
| 2454 | owner and group. */ | 2434 | owner and group. */ |