[git commit] ifupdown: rewrite state file atomically

Denys Vlasenko vda.linux at googlemail.com
Sun Oct 9 21:04:16 UTC 2016


commit: https://git.busybox.net/busybox/commit/?id=3720a61daf2e03e42e34902c2636ce3e3d6b8485
branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master

By user's request.
Decided to not use fcntl(F_SETLKW) in lieu of problems with locking
on networked filesystems. The existence of /var/run/ifstate.new
is treated as a write lock. rename() provides atomicity.

function                                             old     new   delta
ifupdown_main                                       1019    1122    +103

Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
 networking/ifupdown.c | 43 ++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 38 insertions(+), 5 deletions(-)

diff --git a/networking/ifupdown.c b/networking/ifupdown.c
index b0bc0d7..1d0fc53 100644
--- a/networking/ifupdown.c
+++ b/networking/ifupdown.c
@@ -56,6 +56,7 @@
 #endif
 
 #define UDHCPC_CMD_OPTIONS CONFIG_IFUPDOWN_UDHCPC_CMD_OPTIONS
+#define IFSTATE_FILE_PATH  CONFIG_IFUPDOWN_IFSTATE_PATH
 
 #define debug_noise(args...) /*fprintf(stderr, args)*/
 
@@ -1200,7 +1201,7 @@ static llist_t *find_iface_state(llist_t *state_list, const char *iface)
 static llist_t *read_iface_state(void)
 {
 	llist_t *state_list = NULL;
-	FILE *state_fp = fopen_for_read(CONFIG_IFUPDOWN_IFSTATE_PATH);
+	FILE *state_fp = fopen_for_read(IFSTATE_FILE_PATH);
 
 	if (state_fp) {
 		char *start, *end_ptr;
@@ -1215,6 +1216,38 @@ static llist_t *read_iface_state(void)
 	return state_list;
 }
 
+/* read the previous state from the state file */
+static FILE *open_new_state_file(void)
+{
+	int fd, flags, cnt;
+
+	cnt = 0;
+	flags = (O_WRONLY | O_CREAT | O_EXCL);
+	for (;;) {
+		fd = open(IFSTATE_FILE_PATH".new", flags, 0666);
+		if (fd >= 0)
+			break;
+		if (errno != EEXIST
+		 || flags == (O_WRONLY | O_CREAT | O_TRUNC)
+		) {
+			bb_perror_msg_and_die("can't open '%s'",
+					IFSTATE_FILE_PATH".new");
+		}
+		/* Someone else created the .new file */
+		if (cnt > 30 * 1000) {
+			/* Waited for 30*30/2 = 450 milliseconds, still EEXIST.
+			 * Assuming a stale file, rewriting it.
+			 */
+			flags = (O_WRONLY | O_CREAT | O_TRUNC);
+			continue;
+		}
+		usleep(cnt);
+		cnt += 1000;
+	}
+
+	return xfdopen_for_write(fd);
+}
+
 
 int ifupdown_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int ifupdown_main(int argc UNUSED_PARAM, char **argv)
@@ -1348,7 +1381,7 @@ int ifupdown_main(int argc UNUSED_PARAM, char **argv)
 			any_failures = 1;
 		} else if (!NO_ACT) {
 			/* update the state file */
-			FILE *state_fp;
+			FILE *new_state_fp = open_new_state_file();
 			llist_t *state;
 			llist_t *state_list = read_iface_state();
 			llist_t *iface_state = find_iface_state(state_list, iface);
@@ -1368,15 +1401,15 @@ int ifupdown_main(int argc UNUSED_PARAM, char **argv)
 			}
 
 			/* Actually write the new state */
-			state_fp = xfopen_for_write(CONFIG_IFUPDOWN_IFSTATE_PATH);
 			state = state_list;
 			while (state) {
 				if (state->data) {
-					fprintf(state_fp, "%s\n", state->data);
+					fprintf(new_state_fp, "%s\n", state->data);
 				}
 				state = state->link;
 			}
-			fclose(state_fp);
+			fclose(new_state_fp);
+			xrename(IFSTATE_FILE_PATH".new", IFSTATE_FILE_PATH);
 			llist_free(state_list, free);
 		}
  next:


More information about the busybox-cvs mailing list