aboutsummaryrefslogtreecommitdiff
path: root/libbb/lineedit.c
diff options
context:
space:
mode:
authorDenis Vlasenko <vda.linux@googlemail.com>2009-03-22 19:00:05 +0000
committerDenis Vlasenko <vda.linux@googlemail.com>2009-03-22 19:00:05 +0000
commit57abf9e947fa3f7d69f7adb97023b299916ee63c (patch)
treebcc36a5d96b56859763bd52a52377ed1c081326a /libbb/lineedit.c
parent3fd104630020168ead90123e45d848d4d2d0a555 (diff)
downloadbusybox-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.c142
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 */
994static void load_history(const char *fromfile) 1003static 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 */
1031static void save_history(const char *tofile) 1066static 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
1050static void remember_in_history(const char *str) 1127static 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