• R/O
  • SSH
  • HTTPS

yash: Commit


Commit MetaInfo

Revision3830 (tree)
Time2018-03-10 22:57:46
Authormagicant

Log Message

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.

Change Summary

Incremental Difference

--- yash/trunk/NEWS (revision 3829)
+++ yash/trunk/NEWS (revision 3830)
@@ -17,6 +17,10 @@
1717 are now printed with less quotes.
1818 * The "." built-in no longer leaves temporary positional parameters
1919 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.
2024
2125 ----------------------------------------------------------------------
2226 Yash 2.46
--- yash/trunk/redir.c (revision 3829)
+++ yash/trunk/redir.c (revision 3830)
@@ -1,6 +1,6 @@
11 /* Yash: yet another shell */
22 /* redir.c: manages file descriptors and provides functions for redirections */
3-/* (C) 2007-2017 magicant */
3+/* (C) 2007-2018 magicant */
44
55 /* This program is free software: you can redistribute it and/or modify
66 * it under the terms of the GNU General Public License as published by
@@ -359,7 +359,7 @@
359359 flags = O_RDONLY;
360360 goto openwithflags;
361361 case RT_OUTPUT:
362- if (!shopt_clobber && !is_irregular_file(filename)) {
362+ if (!shopt_clobber) {
363363 flags = O_WRONLY | O_CREAT | O_EXCL;
364364 } else {
365365 /* falls thru! */
@@ -482,6 +482,8 @@
482482
483483 /* Opens the redirected file.
484484 * `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.
485487 * If socket redirection is enabled and `path' begins with "/dev/tcp/" or
486488 * "/dev/udp/", a socket is opened.
487489 * Returns a new file descriptor if successful. Otherwise, `errno' is set and
@@ -488,8 +490,35 @@
488490 * -1 is returned. */
489491 int open_file(const char *path, int oflag)
490492 {
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.
493522 #if YASH_ENABLE_SOCKET
494523 if (fd < 0) {
495524 const char *hostandport = matchstrprefix(path, "/dev/tcp/");
@@ -502,6 +531,7 @@
502531 fd = open_socket(hostandport, SOCK_DGRAM);
503532 }
504533 #endif /* YASH_ENABLE_SOCKET */
534+
505535 return fd;
506536 }
507537
Show on old repository browser