libbb: revent previous version of "concurrent history updating"
authorDenis Vlasenko <vda.linux@googlemail.com>
Mon, 23 Mar 2009 06:33:37 +0000 (06:33 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Mon, 23 Mar 2009 06:33:37 +0000 (06:33 -0000)
and replace it with one which does not "snoop" history written
by others. (1) it is what bug 185 needs, and (2) it is less bloaty:

function                                             old     new   delta
load_history                                           -     252    +252
read_line_input                                     3155    3287    +132
next_token                                           914     918      +4
qrealloc                                              36      33      -3
getoptscmd                                           713     708      -5
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 2/2 up/down: 388/-8)            Total: 380 bytes

include/libbb.h
libbb/lineedit.c

index 7bf9469cb34f2d174b587da29d8125d5f41352f5..015374b4a6142f80c35fa4d89c12a4c4e292c673 100644 (file)
@@ -1199,7 +1199,6 @@ typedef struct line_input_t {
        int cur_history;
 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
        unsigned cnt_history_in_file;
-       off_t last_history_end;
        const char *hist_file;
 #endif
        char *history[MAX_HISTORY + 1];
@@ -1215,6 +1214,7 @@ enum {
        FOR_SHELL = DO_HISTORY | SAVE_HISTORY | TAB_COMPLETION | USERNAME_COMPLETION,
 };
 line_input_t *new_line_input_t(int flags) FAST_FUNC;
+/* so far static: void free_line_input_t(line_input_t *n) FAST_FUNC; */
 /* Returns:
  * -1 on read errors or EOF, or on bare Ctrl-D,
  * 0  on ctrl-C (the line entered is still returned in 'command'),
index af1b6276458f572d1d36021fe0e369e8e3de00e1..7bcdb954c506936b63fd6ec6bc1b6a2403cdc941 100644 (file)
@@ -954,6 +954,14 @@ static void input_tab(smallint *lastWasTab)
 #endif  /* FEATURE_COMMAND_TAB_COMPLETION */
 
 
+line_input_t* FAST_FUNC new_line_input_t(int flags)
+{
+       line_input_t *n = xzalloc(sizeof(*n));
+       n->flags = flags;
+       return n;
+}
+
+
 #if MAX_HISTORY > 0
 
 static void save_command_ps_at_cur_history(void)
@@ -991,16 +999,23 @@ static int get_next_history(void)
 
 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
 /* We try to ensure that concurrent additions to the history
- * do not overwrite each other, and that additions to the history
- * by one user are noticed by others.
+ * do not overwrite each other.
  * Otherwise shell users get unhappy.
  *
  * History file is trimmed lazily, when it grows several times longer
  * than configured MAX_HISTORY lines.
  */
 
+static void free_line_input_t(line_input_t *n)
+{
+       int i = n->cnt_history;
+       while (i > 0)
+               free(n->history[--i]);
+       free(n);
+}
+
 /* state->flags is already checked to be nonzero */
-static void load_history(void)
+static void load_history(line_input_t *st_parm)
 {
        char *temp_h[MAX_HISTORY];
        char *line;
@@ -1009,18 +1024,18 @@ static void load_history(void)
 
        /* NB: do not trash old history if file can't be opened */
 
-       fp = fopen_for_read(state->hist_file);
+       fp = fopen_for_read(st_parm->hist_file);
        if (fp) {
                /* clean up old history */
-               for (idx = state->cnt_history; idx > 0;) {
+               for (idx = st_parm->cnt_history; idx > 0;) {
                        idx--;
-                       free(state->history[idx]);
-                       state->history[idx] = NULL;
+                       free(st_parm->history[idx]);
+                       st_parm->history[idx] = NULL;
                }
 
                /* fill temp_h[], retaining only last MAX_HISTORY lines */
                memset(temp_h, 0, sizeof(temp_h));
-               state->cnt_history_in_file = idx = 0;
+               st_parm->cnt_history_in_file = idx = 0;
                while ((line = xmalloc_fgetline(fp)) != NULL) {
                        if (line[0] == '\0') {
                                free(line);
@@ -1028,16 +1043,15 @@ static void load_history(void)
                        }
                        free(temp_h[idx]);
                        temp_h[idx] = line;
-                       state->cnt_history_in_file++;
+                       st_parm->cnt_history_in_file++;
                        idx++;
                        if (idx == MAX_HISTORY)
                                idx = 0;
                }
-               state->last_history_end = lseek(fileno(fp), 0, SEEK_CUR);
                fclose(fp);
 
                /* find first non-NULL temp_h[], if any */
-               if (state->cnt_history_in_file) {
+               if (st_parm->cnt_history_in_file) {
                        while (temp_h[idx] == NULL) {
                                idx++;
                                if (idx == MAX_HISTORY)
@@ -1045,7 +1059,7 @@ static void load_history(void)
                        }
                }
 
-               /* copy temp_h[] to state->history[] */
+               /* copy temp_h[] to st_parm->history[] */
                for (i = 0; i < MAX_HISTORY;) {
                        line = temp_h[idx];
                        if (!line)
@@ -1056,71 +1070,60 @@ static void load_history(void)
                        line_len = strlen(line);
                        if (line_len >= MAX_LINELEN)
                                line[MAX_LINELEN-1] = '\0';
-                       state->history[i++] = line;
+                       st_parm->history[i++] = line;
                }
-               state->cnt_history = i;
+               st_parm->cnt_history = i;
        }
 }
 
 /* state->flags is already checked to be nonzero */
 static void save_history(char *str)
 {
-       off_t end;
        int fd;
-       int len;
+       int len, len2;
 
-       len = strlen(str);
- again:
        fd = open(state->hist_file, O_WRONLY | O_CREAT | O_APPEND, 0666);
        if (fd < 0)
                return;
-
-       end = lseek(fd, 0, SEEK_END);
-
-       if (str) {
-               str[len] = '\n';
-               full_write(fd, str, len + 1);
-               str[len] = '\0';
-               str = NULL;
-               state->cnt_history_in_file++;
-       }
+       xlseek(fd, 0, SEEK_END); /* paranoia */
+       len = strlen(str);
+       str[len] = '\n'; /* we (try to) do atomic write */
+       len2 = full_write(fd, str, len + 1);
+       str[len] = '\0';
        close(fd);
-
-       /* if it was not a 1st write */
-       if (state->last_history_end >= 0) {
-               /* did someone else write anything there? */
-               if (state->last_history_end != end) {
-                       load_history(); /* note: updates cnt_history_in_file */
-                       state->last_history_end = -1;
-                       goto again;
-               }
-       }
-       state->last_history_end = end + len + 1;
+       if (len2 != len + 1)
+               return; /* "wtf?" */
 
        /* did we write so much that history file needs trimming? */
+       state->cnt_history_in_file++;
        if (state->cnt_history_in_file > MAX_HISTORY * 4) {
                FILE *fp;
                char *new_name;
+               line_input_t *st_temp;
+               int i;
+
+               /* we may have concurrently written entries from others.
+                * load them */
+               st_temp = new_line_input_t(state->flags);
+               st_temp->hist_file = state->hist_file;
+               load_history(st_temp);
 
-               new_name = xasprintf("%s.new", state->hist_file);
+               /* write out temp file and replace hist_file atomically */
+               new_name = xasprintf("%s.%u.new", state->hist_file, (int) getpid());
                fp = fopen_for_write(new_name);
                if (fp) {
-                       int i;
-
-                       for (i = 0; i < state->cnt_history; i++) {
-                               fprintf(fp, "%s\n", state->history[i]);
-                       }
-                       state->cnt_history_in_file = i; /* == cnt_history */
-                       state->last_history_end = lseek(fileno(fp), 0, SEEK_CUR);
+                       for (i = 0; i < st_temp->cnt_history; i++)
+                               fprintf(fp, "%s\n", st_temp->history[i]);
                        fclose(fp);
-                       /* replace hist_file atomically */
-                       rename(new_name, state->hist_file);
+                       if (rename(new_name, state->hist_file) == 0)
+                               state->cnt_history_in_file = st_temp->cnt_history;
                }
                free(new_name);
+               free_line_input_t(st_temp);
        }
 }
 #else
-#define load_history( ((void)0)
+#define load_history(a) ((void)0)
 #define save_history(a) ((void)0)
 #endif /* FEATURE_COMMAND_SAVEHISTORY */
 
@@ -1490,7 +1493,8 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li
        state = st ? st : (line_input_t*) &const_int_0;
 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
        if ((state->flags & SAVE_HISTORY) && state->hist_file)
-               load_history();
+               if (state->cnt_history == 0)
+                       load_history(state);
 #endif
        if (state->flags & DO_HISTORY)
                state->cur_history = state->cnt_history;
@@ -1948,14 +1952,6 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li
        return len; /* can't return command_len, DEINIT_S() destroys it */
 }
 
-line_input_t* FAST_FUNC new_line_input_t(int flags)
-{
-       line_input_t *n = xzalloc(sizeof(*n));
-       n->flags = flags;
-       n->last_history_end = -1;
-       return n;
-}
-
 #else
 
 #undef read_line_input