Open no-clobber redirection atomically
The previous implementation of the ">" redirection with the no-clobber
option was unreliable. After the is_irregular_file call, if another
process replaced the irregular file with a regular file, the shell was
clobbering the regular file.
To open the irregular file safely, the shell needs to open without the
O_CREAT or O_TRUNC flag and then check if it is really irregular.
@@ -17,6 +17,10 @@ | ||
17 | 17 | are now printed with less quotes. |
18 | 18 | * The "." built-in no longer leaves temporary positional parameters |
19 | 19 | after a file-not-found error. |
20 | + * The ">" redirection with the noclobber option is now more | |
21 | + reliable than before. Previously, there was little possibility of | |
22 | + overwriting an existing regular file in case another process | |
23 | + simultaneously replaces the file. | |
20 | 24 | |
21 | 25 | ---------------------------------------------------------------------- |
22 | 26 | Yash 2.46 |
@@ -1,6 +1,6 @@ | ||
1 | 1 | /* Yash: yet another shell */ |
2 | 2 | /* redir.c: manages file descriptors and provides functions for redirections */ |
3 | -/* (C) 2007-2017 magicant */ | |
3 | +/* (C) 2007-2018 magicant */ | |
4 | 4 | |
5 | 5 | /* This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
@@ -359,7 +359,7 @@ | ||
359 | 359 | flags = O_RDONLY; |
360 | 360 | goto openwithflags; |
361 | 361 | case RT_OUTPUT: |
362 | - if (!shopt_clobber && !is_irregular_file(filename)) { | |
362 | + if (!shopt_clobber) { | |
363 | 363 | flags = O_WRONLY | O_CREAT | O_EXCL; |
364 | 364 | } else { |
365 | 365 | /* falls thru! */ |
@@ -482,6 +482,8 @@ | ||
482 | 482 | |
483 | 483 | /* Opens the redirected file. |
484 | 484 | * `path' and `oflag' are the first and second arguments to the `open' function. |
485 | + * If `oflag' contains the O_EXCL flag, this function may retry without the flag | |
486 | + * to allow opening an existing non-regular file. | |
485 | 487 | * If socket redirection is enabled and `path' begins with "/dev/tcp/" or |
486 | 488 | * "/dev/udp/", a socket is opened. |
487 | 489 | * Returns a new file descriptor if successful. Otherwise, `errno' is set and |
@@ -488,8 +490,35 @@ | ||
488 | 490 | * -1 is returned. */ |
489 | 491 | int open_file(const char *path, int oflag) |
490 | 492 | { |
491 | - int fd = open(path, oflag, | |
492 | - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); | |
493 | + const mode_t mode = | |
494 | + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; | |
495 | + | |
496 | +start:; | |
497 | + int fd = open(path, oflag, mode); | |
498 | + | |
499 | + // Support the no-clobber mode. | |
500 | + if ((oflag & O_EXCL) && fd < 0 && errno == EEXIST) { | |
501 | + fd = open(path, oflag & ~(O_CREAT | O_EXCL | O_TRUNC), mode); | |
502 | + if (fd < 0) { | |
503 | + if (errno == ENOENT) { | |
504 | + // A file existed on the first open but not on the second. | |
505 | + // Somebody must have removed it between the two opens. | |
506 | + // Start over as we might be able to create one this time. | |
507 | + goto start; | |
508 | + } | |
509 | + } else { | |
510 | + struct stat st; | |
511 | + if (fstat(fd, &st) >= 0 && S_ISREG(st.st_mode)) { | |
512 | + // We opened the FD without the O_CREAT flag, so this regular | |
513 | + // file was created by somebody else. Failure. | |
514 | + xclose(fd); | |
515 | + fd = -1; | |
516 | + errno = EEXIST; | |
517 | + } | |
518 | + } | |
519 | + } | |
520 | + | |
521 | + // Support socket redirection. | |
493 | 522 | #if YASH_ENABLE_SOCKET |
494 | 523 | if (fd < 0) { |
495 | 524 | const char *hostandport = matchstrprefix(path, "/dev/tcp/"); |
@@ -502,6 +531,7 @@ | ||
502 | 531 | fd = open_socket(hostandport, SOCK_DGRAM); |
503 | 532 | } |
504 | 533 | #endif /* YASH_ENABLE_SOCKET */ |
534 | + | |
505 | 535 | return fd; |
506 | 536 | } |
507 | 537 |