diff options
author | Denis Vlasenko <vda.linux@googlemail.com> | 2009-03-22 19:00:05 +0000 |
---|---|---|
committer | Denis Vlasenko <vda.linux@googlemail.com> | 2009-03-22 19:00:05 +0000 |
commit | 57abf9e947fa3f7d69f7adb97023b299916ee63c (patch) | |
tree | bcc36a5d96b56859763bd52a52377ed1c081326a /libbb/lineedit.c | |
parent | 3fd104630020168ead90123e45d848d4d2d0a555 (diff) | |
download | busybox-w32-57abf9e947fa3f7d69f7adb97023b299916ee63c.tar.gz busybox-w32-57abf9e947fa3f7d69f7adb97023b299916ee63c.tar.bz2 busybox-w32-57abf9e947fa3f7d69f7adb97023b299916ee63c.zip |
libbb: make history saving/loading concurrent-safe
* all history writers always append (not overwrite) history files
* they reload history if they detect that file length has changed since last
write
* they trim history file only when it grows 4 times longer than MAXLINES
* they do this atomically by creating new file and renaming it to old
Unfortunately, this comes at a price:
function old new delta
load_history - 346 +346
read_line_input 3155 3358 +203
new_line_input_t 17 31 +14
...irrelevant small jitter...
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 5/5 up/down: 573/-13) Total: 560 bytes
Diffstat (limited to 'libbb/lineedit.c')
-rw-r--r-- | libbb/lineedit.c | 142 |
1 files changed, 110 insertions, 32 deletions
diff --git a/libbb/lineedit.c b/libbb/lineedit.c index 3953cc904..af1b62764 100644 --- a/libbb/lineedit.c +++ b/libbb/lineedit.c | |||
@@ -990,64 +990,141 @@ static int get_next_history(void) | |||
990 | } | 990 | } |
991 | 991 | ||
992 | #if ENABLE_FEATURE_EDITING_SAVEHISTORY | 992 | #if ENABLE_FEATURE_EDITING_SAVEHISTORY |
993 | /* We try to ensure that concurrent additions to the history | ||
994 | * do not overwrite each other, and that additions to the history | ||
995 | * by one user are noticed by others. | ||
996 | * Otherwise shell users get unhappy. | ||
997 | * | ||
998 | * History file is trimmed lazily, when it grows several times longer | ||
999 | * than configured MAX_HISTORY lines. | ||
1000 | */ | ||
1001 | |||
993 | /* state->flags is already checked to be nonzero */ | 1002 | /* state->flags is already checked to be nonzero */ |
994 | static void load_history(const char *fromfile) | 1003 | static void load_history(void) |
995 | { | 1004 | { |
1005 | char *temp_h[MAX_HISTORY]; | ||
1006 | char *line; | ||
996 | FILE *fp; | 1007 | FILE *fp; |
997 | int hi; | 1008 | unsigned idx, i, line_len; |
998 | 1009 | ||
999 | /* NB: do not trash old history if file can't be opened */ | 1010 | /* NB: do not trash old history if file can't be opened */ |
1000 | 1011 | ||
1001 | fp = fopen_for_read(fromfile); | 1012 | fp = fopen_for_read(state->hist_file); |
1002 | if (fp) { | 1013 | if (fp) { |
1003 | /* clean up old history */ | 1014 | /* clean up old history */ |
1004 | for (hi = state->cnt_history; hi > 0;) { | 1015 | for (idx = state->cnt_history; idx > 0;) { |
1005 | hi--; | 1016 | idx--; |
1006 | free(state->history[hi]); | 1017 | free(state->history[idx]); |
1007 | state->history[hi] = NULL; | 1018 | state->history[idx] = NULL; |
1008 | } | 1019 | } |
1009 | 1020 | ||
1010 | for (hi = 0; hi < MAX_HISTORY;) { | 1021 | /* fill temp_h[], retaining only last MAX_HISTORY lines */ |
1011 | char *hl = xmalloc_fgetline(fp); | 1022 | memset(temp_h, 0, sizeof(temp_h)); |
1012 | int l; | 1023 | state->cnt_history_in_file = idx = 0; |
1013 | 1024 | while ((line = xmalloc_fgetline(fp)) != NULL) { | |
1014 | if (!hl) | 1025 | if (line[0] == '\0') { |
1015 | break; | 1026 | free(line); |
1016 | l = strlen(hl); | ||
1017 | if (l >= MAX_LINELEN) | ||
1018 | hl[MAX_LINELEN-1] = '\0'; | ||
1019 | if (l == 0) { | ||
1020 | free(hl); | ||
1021 | continue; | 1027 | continue; |
1022 | } | 1028 | } |
1023 | state->history[hi++] = hl; | 1029 | free(temp_h[idx]); |
1030 | temp_h[idx] = line; | ||
1031 | state->cnt_history_in_file++; | ||
1032 | idx++; | ||
1033 | if (idx == MAX_HISTORY) | ||
1034 | idx = 0; | ||
1024 | } | 1035 | } |
1036 | state->last_history_end = lseek(fileno(fp), 0, SEEK_CUR); | ||
1025 | fclose(fp); | 1037 | fclose(fp); |
1026 | state->cnt_history = hi; | 1038 | |
1039 | /* find first non-NULL temp_h[], if any */ | ||
1040 | if (state->cnt_history_in_file) { | ||
1041 | while (temp_h[idx] == NULL) { | ||
1042 | idx++; | ||
1043 | if (idx == MAX_HISTORY) | ||
1044 | idx = 0; | ||
1045 | } | ||
1046 | } | ||
1047 | |||
1048 | /* copy temp_h[] to state->history[] */ | ||
1049 | for (i = 0; i < MAX_HISTORY;) { | ||
1050 | line = temp_h[idx]; | ||
1051 | if (!line) | ||
1052 | break; | ||
1053 | idx++; | ||
1054 | if (idx == MAX_HISTORY) | ||
1055 | idx = 0; | ||
1056 | line_len = strlen(line); | ||
1057 | if (line_len >= MAX_LINELEN) | ||
1058 | line[MAX_LINELEN-1] = '\0'; | ||
1059 | state->history[i++] = line; | ||
1060 | } | ||
1061 | state->cnt_history = i; | ||
1027 | } | 1062 | } |
1028 | } | 1063 | } |
1029 | 1064 | ||
1030 | /* state->flags is already checked to be nonzero */ | 1065 | /* state->flags is already checked to be nonzero */ |
1031 | static void save_history(const char *tofile) | 1066 | static void save_history(char *str) |
1032 | { | 1067 | { |
1033 | FILE *fp; | 1068 | off_t end; |
1069 | int fd; | ||
1070 | int len; | ||
1034 | 1071 | ||
1035 | fp = fopen_for_write(tofile); | 1072 | len = strlen(str); |
1036 | if (fp) { | 1073 | again: |
1037 | int i; | 1074 | fd = open(state->hist_file, O_WRONLY | O_CREAT | O_APPEND, 0666); |
1075 | if (fd < 0) | ||
1076 | return; | ||
1038 | 1077 | ||
1039 | for (i = 0; i < state->cnt_history; i++) { | 1078 | end = lseek(fd, 0, SEEK_END); |
1040 | fprintf(fp, "%s\n", state->history[i]); | 1079 | |
1080 | if (str) { | ||
1081 | str[len] = '\n'; | ||
1082 | full_write(fd, str, len + 1); | ||
1083 | str[len] = '\0'; | ||
1084 | str = NULL; | ||
1085 | state->cnt_history_in_file++; | ||
1086 | } | ||
1087 | close(fd); | ||
1088 | |||
1089 | /* if it was not a 1st write */ | ||
1090 | if (state->last_history_end >= 0) { | ||
1091 | /* did someone else write anything there? */ | ||
1092 | if (state->last_history_end != end) { | ||
1093 | load_history(); /* note: updates cnt_history_in_file */ | ||
1094 | state->last_history_end = -1; | ||
1095 | goto again; | ||
1041 | } | 1096 | } |
1042 | fclose(fp); | 1097 | } |
1098 | state->last_history_end = end + len + 1; | ||
1099 | |||
1100 | /* did we write so much that history file needs trimming? */ | ||
1101 | if (state->cnt_history_in_file > MAX_HISTORY * 4) { | ||
1102 | FILE *fp; | ||
1103 | char *new_name; | ||
1104 | |||
1105 | new_name = xasprintf("%s.new", state->hist_file); | ||
1106 | fp = fopen_for_write(new_name); | ||
1107 | if (fp) { | ||
1108 | int i; | ||
1109 | |||
1110 | for (i = 0; i < state->cnt_history; i++) { | ||
1111 | fprintf(fp, "%s\n", state->history[i]); | ||
1112 | } | ||
1113 | state->cnt_history_in_file = i; /* == cnt_history */ | ||
1114 | state->last_history_end = lseek(fileno(fp), 0, SEEK_CUR); | ||
1115 | fclose(fp); | ||
1116 | /* replace hist_file atomically */ | ||
1117 | rename(new_name, state->hist_file); | ||
1118 | } | ||
1119 | free(new_name); | ||
1043 | } | 1120 | } |
1044 | } | 1121 | } |
1045 | #else | 1122 | #else |
1046 | #define load_history(a) ((void)0) | 1123 | #define load_history() ((void)0) |
1047 | #define save_history(a) ((void)0) | 1124 | #define save_history(a) ((void)0) |
1048 | #endif /* FEATURE_COMMAND_SAVEHISTORY */ | 1125 | #endif /* FEATURE_COMMAND_SAVEHISTORY */ |
1049 | 1126 | ||
1050 | static void remember_in_history(const char *str) | 1127 | static void remember_in_history(char *str) |
1051 | { | 1128 | { |
1052 | int i; | 1129 | int i; |
1053 | 1130 | ||
@@ -1078,7 +1155,7 @@ static void remember_in_history(const char *str) | |||
1078 | state->cnt_history = i; | 1155 | state->cnt_history = i; |
1079 | #if ENABLE_FEATURE_EDITING_SAVEHISTORY | 1156 | #if ENABLE_FEATURE_EDITING_SAVEHISTORY |
1080 | if ((state->flags & SAVE_HISTORY) && state->hist_file) | 1157 | if ((state->flags & SAVE_HISTORY) && state->hist_file) |
1081 | save_history(state->hist_file); | 1158 | save_history(str); |
1082 | #endif | 1159 | #endif |
1083 | USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines++;) | 1160 | USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines++;) |
1084 | } | 1161 | } |
@@ -1413,7 +1490,7 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li | |||
1413 | state = st ? st : (line_input_t*) &const_int_0; | 1490 | state = st ? st : (line_input_t*) &const_int_0; |
1414 | #if ENABLE_FEATURE_EDITING_SAVEHISTORY | 1491 | #if ENABLE_FEATURE_EDITING_SAVEHISTORY |
1415 | if ((state->flags & SAVE_HISTORY) && state->hist_file) | 1492 | if ((state->flags & SAVE_HISTORY) && state->hist_file) |
1416 | load_history(state->hist_file); | 1493 | load_history(); |
1417 | #endif | 1494 | #endif |
1418 | if (state->flags & DO_HISTORY) | 1495 | if (state->flags & DO_HISTORY) |
1419 | state->cur_history = state->cnt_history; | 1496 | state->cur_history = state->cnt_history; |
@@ -1875,6 +1952,7 @@ line_input_t* FAST_FUNC new_line_input_t(int flags) | |||
1875 | { | 1952 | { |
1876 | line_input_t *n = xzalloc(sizeof(*n)); | 1953 | line_input_t *n = xzalloc(sizeof(*n)); |
1877 | n->flags = flags; | 1954 | n->flags = flags; |
1955 | n->last_history_end = -1; | ||
1878 | return n; | 1956 | return n; |
1879 | } | 1957 | } |
1880 | 1958 | ||