]>
Commit | Line | Data |
---|---|---|
ccc6cda3 JA |
1 | /* histfile.c - functions to manipulate the history file. */ |
2 | ||
03e4274f | 3 | /* Copyright (C) 1989-2019 Free Software Foundation, Inc. |
ccc6cda3 | 4 | |
2e4498b3 | 5 | This file contains the GNU History Library (History), a set of |
ccc6cda3 JA |
6 | routines for managing the text of previously typed lines. |
7 | ||
2e4498b3 | 8 | History is free software: you can redistribute it and/or modify |
ccc6cda3 | 9 | it under the terms of the GNU General Public License as published by |
2e4498b3 CR |
10 | the Free Software Foundation, either version 3 of the License, or |
11 | (at your option) any later version. | |
12 | ||
13 | History is distributed in the hope that it will be useful, | |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | GNU General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU General Public License | |
19 | along with History. If not, see <http://www.gnu.org/licenses/>. | |
20 | */ | |
ccc6cda3 JA |
21 | |
22 | /* The goal is to make the implementation transparent, so that you | |
23 | don't have to know what data types are used, just what functions | |
24 | you can call. I think I have done that. */ | |
5565fb1a | 25 | |
ccc6cda3 JA |
26 | #define READLINE_LIBRARY |
27 | ||
5565fb1a | 28 | #if defined (__TANDEM) |
fc35c477 CR |
29 | # define _XOPEN_SOURCE_EXTENDED 1 |
30 | # include <unistd.h> | |
5565fb1a CR |
31 | # include <floss.h> |
32 | #endif | |
33 | ||
ccc6cda3 JA |
34 | #if defined (HAVE_CONFIG_H) |
35 | # include <config.h> | |
36 | #endif | |
37 | ||
38 | #include <stdio.h> | |
39 | ||
8a0829e9 CR |
40 | #if defined (HAVE_LIMITS_H) |
41 | # include <limits.h> | |
42 | #endif | |
43 | ||
ccc6cda3 | 44 | #include <sys/types.h> |
d3a24ed2 | 45 | #if ! defined (_MINIX) && defined (HAVE_SYS_FILE_H) |
cce855bc JA |
46 | # include <sys/file.h> |
47 | #endif | |
bb70624e | 48 | #include "posixstat.h" |
ccc6cda3 JA |
49 | #include <fcntl.h> |
50 | ||
51 | #if defined (HAVE_STDLIB_H) | |
52 | # include <stdlib.h> | |
53 | #else | |
54 | # include "ansi_stdlib.h" | |
55 | #endif /* HAVE_STDLIB_H */ | |
56 | ||
57 | #if defined (HAVE_UNISTD_H) | |
58 | # include <unistd.h> | |
59 | #endif | |
60 | ||
d3ad40de CR |
61 | #include <ctype.h> |
62 | ||
63 | #if defined (__EMX__) | |
7117c2d2 JA |
64 | # undef HAVE_MMAP |
65 | #endif | |
ccc6cda3 | 66 | |
d3a24ed2 | 67 | #ifdef HISTORY_USE_MMAP |
7117c2d2 JA |
68 | # include <sys/mman.h> |
69 | ||
70 | # ifdef MAP_FILE | |
71 | # define MAP_RFLAGS (MAP_FILE|MAP_PRIVATE) | |
72 | # define MAP_WFLAGS (MAP_FILE|MAP_SHARED) | |
73 | # else | |
74 | # define MAP_RFLAGS MAP_PRIVATE | |
75 | # define MAP_WFLAGS MAP_SHARED | |
76 | # endif | |
77 | ||
78 | # ifndef MAP_FAILED | |
79 | # define MAP_FAILED ((void *)-1) | |
80 | # endif | |
81 | ||
d3a24ed2 | 82 | #endif /* HISTORY_USE_MMAP */ |
bb70624e | 83 | |
03e4274f CR |
84 | #if defined(_WIN32) |
85 | # define WIN32_LEAN_AND_MEAN | |
86 | # include <windows.h> | |
87 | #endif | |
88 | ||
bb70624e JA |
89 | /* If we're compiling for __EMX__ (OS/2) or __CYGWIN__ (cygwin32 environment |
90 | on win 95/98/nt), we want to open files with O_BINARY mode so that there | |
91 | is no \n -> \r\n conversion performed. On other systems, we don't want to | |
92 | mess around with O_BINARY at all, so we ensure that it's defined to 0. */ | |
93 | #if defined (__EMX__) || defined (__CYGWIN__) | |
d166f048 JA |
94 | # ifndef O_BINARY |
95 | # define O_BINARY 0 | |
96 | # endif | |
bb70624e | 97 | #else /* !__EMX__ && !__CYGWIN__ */ |
d166f048 JA |
98 | # undef O_BINARY |
99 | # define O_BINARY 0 | |
bb70624e | 100 | #endif /* !__EMX__ && !__CYGWIN__ */ |
d166f048 | 101 | |
ccc6cda3 JA |
102 | #include <errno.h> |
103 | #if !defined (errno) | |
104 | extern int errno; | |
105 | #endif /* !errno */ | |
106 | ||
107 | #include "history.h" | |
108 | #include "histlib.h" | |
109 | ||
bb70624e JA |
110 | #include "rlshell.h" |
111 | #include "xmalloc.h" | |
ccc6cda3 | 112 | |
8a0829e9 CR |
113 | #if !defined (PATH_MAX) |
114 | # define PATH_MAX 1024 /* default */ | |
115 | #endif | |
116 | ||
6f82653c CR |
117 | /* history file version; currently unused */ |
118 | int history_file_version = 1; | |
119 | ||
d3a24ed2 CR |
120 | /* If non-zero, we write timestamps to the history file in history_do_write() */ |
121 | int history_write_timestamps = 0; | |
122 | ||
6f82653c CR |
123 | /* If non-zero, we assume that a history file that starts with a timestamp |
124 | uses timestamp-delimited entries and can include multi-line history | |
125 | entries. Used by read_history_range */ | |
126 | int history_multiline_entries = 0; | |
127 | ||
03d922b1 CR |
128 | /* Immediately after a call to read_history() or read_history_range(), this |
129 | will return the number of lines just read from the history file in that | |
130 | call. */ | |
131 | int history_lines_read_from_file = 0; | |
132 | ||
133 | /* Immediately after a call to write_history() or history_do_write(), this | |
134 | will return the number of lines just written to the history file in that | |
135 | call. This also works with history_truncate_file. */ | |
136 | int history_lines_written_to_file = 0; | |
137 | ||
d3a24ed2 CR |
138 | /* Does S look like the beginning of a history timestamp entry? Placeholder |
139 | for more extensive tests. */ | |
c4c90ef8 | 140 | #define HIST_TIMESTAMP_START(s) (*(s) == history_comment_char && isdigit ((unsigned char)(s)[1]) ) |
d3a24ed2 | 141 | |
38881450 CR |
142 | static char *history_backupfile (const char *); |
143 | static char *history_tempfile (const char *); | |
144 | static int histfile_backup (const char *, const char *); | |
145 | static int histfile_restore (const char *, const char *); | |
146 | static int history_rename (const char *, const char *); | |
de608191 | 147 | |
ccc6cda3 JA |
148 | /* Return the string that should be used in the place of this |
149 | filename. This only matters when you don't specify the | |
150 | filename to read_history (), or write_history (). */ | |
151 | static char * | |
10729c7b | 152 | history_filename (const char *filename) |
ccc6cda3 | 153 | { |
28ef6c31 JA |
154 | char *return_val; |
155 | const char *home; | |
2e725f73 | 156 | size_t home_len; |
ccc6cda3 JA |
157 | |
158 | return_val = filename ? savestring (filename) : (char *)NULL; | |
159 | ||
160 | if (return_val) | |
161 | return (return_val); | |
162 | ||
28ef6c31 | 163 | home = sh_get_env_value ("HOME"); |
8f50a023 CR |
164 | #if defined (_WIN32) |
165 | if (home == 0) | |
166 | home = sh_get_env_value ("APPDATA"); | |
167 | #endif | |
ccc6cda3 JA |
168 | |
169 | if (home == 0) | |
276cb932 | 170 | return (NULL); |
ccc6cda3 JA |
171 | else |
172 | home_len = strlen (home); | |
173 | ||
f73dda09 | 174 | return_val = (char *)xmalloc (2 + home_len + 8); /* strlen(".history") == 8 */ |
ccc6cda3 JA |
175 | strcpy (return_val, home); |
176 | return_val[home_len] = '/'; | |
bb70624e JA |
177 | #if defined (__MSDOS__) |
178 | strcpy (return_val + home_len + 1, "_history"); | |
179 | #else | |
ccc6cda3 | 180 | strcpy (return_val + home_len + 1, ".history"); |
bb70624e | 181 | #endif |
ccc6cda3 JA |
182 | |
183 | return (return_val); | |
184 | } | |
185 | ||
51f7ea36 | 186 | static char * |
10729c7b | 187 | history_backupfile (const char *filename) |
51f7ea36 | 188 | { |
f8b90b7b CR |
189 | const char *fn; |
190 | char *ret, linkbuf[PATH_MAX+1]; | |
51f7ea36 | 191 | size_t len; |
f8b90b7b | 192 | ssize_t n; |
51f7ea36 | 193 | |
de608191 CR |
194 | fn = filename; |
195 | #if defined (HAVE_READLINK) | |
196 | /* Follow symlink to avoid backing up symlink itself; call will fail if | |
197 | not a symlink */ | |
198 | if ((n = readlink (filename, linkbuf, sizeof (linkbuf) - 1)) > 0) | |
f8b90b7b | 199 | { |
de608191 CR |
200 | linkbuf[n] = '\0'; |
201 | fn = linkbuf; | |
f8b90b7b CR |
202 | } |
203 | #endif | |
204 | ||
205 | len = strlen (fn); | |
51f7ea36 | 206 | ret = xmalloc (len + 2); |
f8b90b7b | 207 | strcpy (ret, fn); |
51f7ea36 CR |
208 | ret[len] = '-'; |
209 | ret[len+1] = '\0'; | |
210 | return ret; | |
211 | } | |
212 | ||
1573ba78 | 213 | static char * |
10729c7b | 214 | history_tempfile (const char *filename) |
1573ba78 CR |
215 | { |
216 | const char *fn; | |
217 | char *ret, linkbuf[PATH_MAX+1]; | |
218 | size_t len; | |
219 | ssize_t n; | |
1573ba78 CR |
220 | int pid; |
221 | ||
222 | fn = filename; | |
223 | #if defined (HAVE_READLINK) | |
224 | /* Follow symlink so tempfile created in the same directory as any symlinked | |
225 | history file; call will fail if not a symlink */ | |
226 | if ((n = readlink (filename, linkbuf, sizeof (linkbuf) - 1)) > 0) | |
227 | { | |
228 | linkbuf[n] = '\0'; | |
229 | fn = linkbuf; | |
230 | } | |
231 | #endif | |
232 | ||
233 | len = strlen (fn); | |
234 | ret = xmalloc (len + 11); | |
235 | strcpy (ret, fn); | |
236 | ||
237 | pid = (int)getpid (); | |
238 | ||
239 | /* filename-PID.tmp */ | |
240 | ret[len] = '-'; | |
241 | ret[len+1] = (pid / 10000 % 10) + '0'; | |
242 | ret[len+2] = (pid / 1000 % 10) + '0'; | |
243 | ret[len+3] = (pid / 100 % 10) + '0'; | |
244 | ret[len+4] = (pid / 10 % 10) + '0'; | |
245 | ret[len+5] = (pid % 10) + '0'; | |
246 | strcpy (ret + len + 6, ".tmp"); | |
247 | ||
248 | return ret; | |
249 | } | |
250 | ||
ccc6cda3 JA |
251 | /* Add the contents of FILENAME to the history list, a line at a time. |
252 | If FILENAME is NULL, then read from ~/.history. Returns 0 if | |
253 | successful, or errno if not. */ | |
254 | int | |
10729c7b | 255 | read_history (const char *filename) |
ccc6cda3 JA |
256 | { |
257 | return (read_history_range (filename, 0, -1)); | |
258 | } | |
259 | ||
260 | /* Read a range of lines from FILENAME, adding them to the history list. | |
261 | Start reading at the FROM'th line and end at the TO'th. If FROM | |
262 | is zero, start at the beginning. If TO is less than FROM, read | |
263 | until the end of the file. If FILENAME is NULL, then read from | |
264 | ~/.history. Returns 0 if successful, or errno if not. */ | |
265 | int | |
10729c7b | 266 | read_history_range (const char *filename, int from, int to) |
ccc6cda3 | 267 | { |
d3a24ed2 CR |
268 | register char *line_start, *line_end, *p; |
269 | char *input, *buffer, *bufend, *last_ts; | |
6f82653c | 270 | int file, current_line, chars_read, has_timestamps, reset_comment_char; |
ccc6cda3 | 271 | struct stat finfo; |
cce855bc | 272 | size_t file_size; |
d3a24ed2 CR |
273 | #if defined (EFBIG) |
274 | int overflow_errno = EFBIG; | |
275 | #elif defined (EOVERFLOW) | |
276 | int overflow_errno = EOVERFLOW; | |
277 | #else | |
278 | int overflow_errno = EIO; | |
279 | #endif | |
ccc6cda3 | 280 | |
03d922b1 CR |
281 | history_lines_read_from_file = 0; |
282 | ||
d3a24ed2 | 283 | buffer = last_ts = (char *)NULL; |
ccc6cda3 | 284 | input = history_filename (filename); |
64419627 | 285 | file = input ? open (input, O_RDONLY|O_BINARY, 0666) : -1; |
ccc6cda3 JA |
286 | |
287 | if ((file < 0) || (fstat (file, &finfo) == -1)) | |
288 | goto error_and_exit; | |
289 | ||
8b6b8f60 CR |
290 | if (S_ISREG (finfo.st_mode) == 0) |
291 | { | |
292 | #ifdef EFTYPE | |
293 | errno = EFTYPE; | |
294 | #else | |
295 | errno = EINVAL; | |
296 | #endif | |
297 | goto error_and_exit; | |
298 | } | |
299 | ||
cce855bc JA |
300 | file_size = (size_t)finfo.st_size; |
301 | ||
302 | /* check for overflow on very large files */ | |
303 | if (file_size != finfo.st_size || file_size + 1 < file_size) | |
304 | { | |
d3a24ed2 | 305 | errno = overflow_errno; |
cce855bc JA |
306 | goto error_and_exit; |
307 | } | |
ccc6cda3 | 308 | |
f698849a | 309 | if (file_size == 0) |
2a391577 | 310 | { |
0b9a4b3a | 311 | xfree (input); |
439b8c2c | 312 | close (file); |
2a391577 CR |
313 | return 0; /* don't waste time if we don't have to */ |
314 | } | |
f698849a | 315 | |
d3a24ed2 | 316 | #ifdef HISTORY_USE_MMAP |
7117c2d2 JA |
317 | /* We map read/write and private so we can change newlines to NULs without |
318 | affecting the underlying object. */ | |
319 | buffer = (char *)mmap (0, file_size, PROT_READ|PROT_WRITE, MAP_RFLAGS, file, 0); | |
320 | if ((void *)buffer == MAP_FAILED) | |
d3a24ed2 CR |
321 | { |
322 | errno = overflow_errno; | |
323 | goto error_and_exit; | |
324 | } | |
7117c2d2 JA |
325 | chars_read = file_size; |
326 | #else | |
327 | buffer = (char *)malloc (file_size + 1); | |
328 | if (buffer == 0) | |
d3a24ed2 CR |
329 | { |
330 | errno = overflow_errno; | |
331 | goto error_and_exit; | |
332 | } | |
bb70624e JA |
333 | |
334 | chars_read = read (file, buffer, file_size); | |
7117c2d2 | 335 | #endif |
bb70624e | 336 | if (chars_read < 0) |
ccc6cda3 JA |
337 | { |
338 | error_and_exit: | |
d3a24ed2 CR |
339 | if (errno != 0) |
340 | chars_read = errno; | |
341 | else | |
342 | chars_read = EIO; | |
ccc6cda3 JA |
343 | if (file >= 0) |
344 | close (file); | |
345 | ||
346 | FREE (input); | |
d3a24ed2 | 347 | #ifndef HISTORY_USE_MMAP |
ccc6cda3 | 348 | FREE (buffer); |
7117c2d2 | 349 | #endif |
ccc6cda3 | 350 | |
7117c2d2 | 351 | return (chars_read); |
ccc6cda3 JA |
352 | } |
353 | ||
354 | close (file); | |
355 | ||
356 | /* Set TO to larger than end of file if negative. */ | |
357 | if (to < 0) | |
bb70624e | 358 | to = chars_read; |
ccc6cda3 JA |
359 | |
360 | /* Start at beginning of file, work to end. */ | |
7117c2d2 | 361 | bufend = buffer + chars_read; |
f698849a | 362 | *bufend = '\0'; /* null-terminate buffer for timestamp checks */ |
7117c2d2 | 363 | current_line = 0; |
ccc6cda3 | 364 | |
6f82653c CR |
365 | /* Heuristic: the history comment character rarely changes, so assume we |
366 | have timestamps if the buffer starts with `#[:digit:]' and temporarily | |
367 | set history_comment_char so timestamp parsing works right */ | |
368 | reset_comment_char = 0; | |
369 | if (history_comment_char == '\0' && buffer[0] == '#' && isdigit ((unsigned char)buffer[1])) | |
370 | { | |
371 | history_comment_char = '#'; | |
372 | reset_comment_char = 1; | |
373 | } | |
374 | ||
375 | has_timestamps = HIST_TIMESTAMP_START (buffer); | |
376c9fe5 | 376 | history_multiline_entries += has_timestamps && history_write_timestamps; |
6f82653c | 377 | |
ccc6cda3 | 378 | /* Skip lines until we are at FROM. */ |
376c9fe5 CR |
379 | if (has_timestamps) |
380 | last_ts = buffer; | |
7117c2d2 JA |
381 | for (line_start = line_end = buffer; line_end < bufend && current_line < from; line_end++) |
382 | if (*line_end == '\n') | |
383 | { | |
d3a24ed2 CR |
384 | p = line_end + 1; |
385 | /* If we see something we think is a timestamp, continue with this | |
386 | line. We should check more extensively here... */ | |
387 | if (HIST_TIMESTAMP_START(p) == 0) | |
388 | current_line++; | |
376c9fe5 CR |
389 | else |
390 | last_ts = p; | |
d3a24ed2 | 391 | line_start = p; |
376c9fe5 CR |
392 | /* If we are at the last line (current_line == from) but we have |
393 | timestamps (has_timestamps), then line_start points to the | |
394 | text of the last command, and we need to skip to its end. */ | |
395 | if (current_line >= from && has_timestamps) | |
396 | { | |
397 | for (line_end = p; line_end < bufend && *line_end != '\n'; line_end++) | |
398 | ; | |
399 | line_start = (*line_end == '\n') ? line_end + 1 : line_end; | |
400 | } | |
7117c2d2 | 401 | } |
ccc6cda3 JA |
402 | |
403 | /* If there are lines left to gobble, then gobble them now. */ | |
7117c2d2 JA |
404 | for (line_end = line_start; line_end < bufend; line_end++) |
405 | if (*line_end == '\n') | |
ccc6cda3 | 406 | { |
d3ad40de CR |
407 | /* Change to allow Windows-like \r\n end of line delimiter. */ |
408 | if (line_end > line_start && line_end[-1] == '\r') | |
409 | line_end[-1] = '\0'; | |
410 | else | |
411 | *line_end = '\0'; | |
ccc6cda3 | 412 | |
7117c2d2 | 413 | if (*line_start) |
d3a24ed2 CR |
414 | { |
415 | if (HIST_TIMESTAMP_START(line_start) == 0) | |
416 | { | |
12beeabf | 417 | if (last_ts == NULL && history_length > 0 && history_multiline_entries) |
6f82653c CR |
418 | _hs_append_history_line (history_length - 1, line_start); |
419 | else | |
420 | add_history (line_start); | |
d3a24ed2 CR |
421 | if (last_ts) |
422 | { | |
423 | add_history_time (last_ts); | |
424 | last_ts = NULL; | |
425 | } | |
426 | } | |
427 | else | |
428 | { | |
429 | last_ts = line_start; | |
430 | current_line--; | |
431 | } | |
432 | } | |
ccc6cda3 JA |
433 | |
434 | current_line++; | |
435 | ||
436 | if (current_line >= to) | |
437 | break; | |
438 | ||
439 | line_start = line_end + 1; | |
440 | } | |
441 | ||
03d922b1 | 442 | history_lines_read_from_file = current_line; |
6f82653c CR |
443 | if (reset_comment_char) |
444 | history_comment_char = '\0'; | |
03d922b1 | 445 | |
ccc6cda3 | 446 | FREE (input); |
d3a24ed2 | 447 | #ifndef HISTORY_USE_MMAP |
ccc6cda3 | 448 | FREE (buffer); |
7117c2d2 JA |
449 | #else |
450 | munmap (buffer, file_size); | |
451 | #endif | |
ccc6cda3 JA |
452 | |
453 | return (0); | |
454 | } | |
455 | ||
03e4274f CR |
456 | /* We need a special version for WIN32 because Windows rename() refuses to |
457 | overwrite an existing file. */ | |
458 | static int | |
459 | history_rename (const char *old, const char *new) | |
460 | { | |
461 | #if defined (_WIN32) | |
462 | return (MoveFileEx (old, new, MOVEFILE_REPLACE_EXISTING) == 0 ? -1 : 0); | |
463 | #else | |
464 | return (rename (old, new)); | |
465 | #endif | |
466 | } | |
467 | ||
1573ba78 CR |
468 | /* Save FILENAME to BACK, handling case where FILENAME is a symlink |
469 | (e.g., ~/.bash_history -> .histfiles/.bash_history.$HOSTNAME) */ | |
f8b90b7b | 470 | static int |
10729c7b | 471 | histfile_backup (const char *filename, const char *back) |
f8b90b7b | 472 | { |
de608191 CR |
473 | #if defined (HAVE_READLINK) |
474 | char linkbuf[PATH_MAX+1]; | |
475 | ssize_t n; | |
476 | ||
477 | /* Follow to target of symlink to avoid renaming symlink itself */ | |
478 | if ((n = readlink (filename, linkbuf, sizeof (linkbuf) - 1)) > 0) | |
479 | { | |
480 | linkbuf[n] = '\0'; | |
03e4274f | 481 | return (history_rename (linkbuf, back)); |
de608191 CR |
482 | } |
483 | #endif | |
03e4274f | 484 | return (history_rename (filename, back)); |
de608191 CR |
485 | } |
486 | ||
1573ba78 CR |
487 | /* Restore ORIG from BACKUP handling case where ORIG is a symlink |
488 | (e.g., ~/.bash_history -> .histfiles/.bash_history.$HOSTNAME) */ | |
de608191 | 489 | static int |
10729c7b | 490 | histfile_restore (const char *backup, const char *orig) |
de608191 CR |
491 | { |
492 | #if defined (HAVE_READLINK) | |
493 | char linkbuf[PATH_MAX+1]; | |
494 | ssize_t n; | |
495 | ||
496 | /* Follow to target of symlink to avoid renaming symlink itself */ | |
497 | if ((n = readlink (orig, linkbuf, sizeof (linkbuf) - 1)) > 0) | |
498 | { | |
499 | linkbuf[n] = '\0'; | |
03e4274f | 500 | return (history_rename (backup, linkbuf)); |
de608191 CR |
501 | } |
502 | #endif | |
03e4274f | 503 | return (history_rename (backup, orig)); |
f8b90b7b CR |
504 | } |
505 | ||
cf58e12c CR |
506 | /* Should we call chown, based on whether finfo and nfinfo describe different |
507 | files with different owners? */ | |
508 | ||
509 | #define SHOULD_CHOWN(finfo, nfinfo) \ | |
510 | (finfo.st_uid != nfinfo.st_uid || finfo.st_gid != nfinfo.st_gid) | |
511 | ||
ccc6cda3 | 512 | /* Truncate the history file FNAME, leaving only LINES trailing lines. |
1573ba78 CR |
513 | If FNAME is NULL, then use ~/.history. Writes a new file and renames |
514 | it to the original name. Returns 0 on success, errno on failure. */ | |
ccc6cda3 | 515 | int |
10729c7b | 516 | history_truncate_file (const char *fname, int lines) |
ccc6cda3 | 517 | { |
1573ba78 | 518 | char *buffer, *filename, *tempname, *bp, *bp1; /* bp1 == bp+1 */ |
1b05a005 | 519 | int file, chars_read, rv, orig_lines, exists, r; |
cf58e12c | 520 | struct stat finfo, nfinfo; |
cce855bc | 521 | size_t file_size; |
ccc6cda3 | 522 | |
03d922b1 CR |
523 | history_lines_written_to_file = 0; |
524 | ||
d166f048 | 525 | buffer = (char *)NULL; |
ccc6cda3 | 526 | filename = history_filename (fname); |
1573ba78 | 527 | tempname = 0; |
64419627 | 528 | file = filename ? open (filename, O_RDONLY|O_BINARY, 0666) : -1; |
03d922b1 | 529 | rv = exists = 0; |
ccc6cda3 | 530 | |
28ef6c31 | 531 | /* Don't try to truncate non-regular files. */ |
ccc6cda3 | 532 | if (file == -1 || fstat (file, &finfo) == -1) |
28ef6c31 JA |
533 | { |
534 | rv = errno; | |
535 | if (file != -1) | |
536 | close (file); | |
537 | goto truncate_exit; | |
538 | } | |
03d922b1 | 539 | exists = 1; |
ccc6cda3 | 540 | |
cf58e12c CR |
541 | nfinfo.st_uid = finfo.st_uid; |
542 | nfinfo.st_gid = finfo.st_gid; | |
543 | ||
28ef6c31 JA |
544 | if (S_ISREG (finfo.st_mode) == 0) |
545 | { | |
546 | close (file); | |
547 | #ifdef EFTYPE | |
548 | rv = EFTYPE; | |
549 | #else | |
550 | rv = EINVAL; | |
551 | #endif | |
552 | goto truncate_exit; | |
553 | } | |
bb70624e | 554 | |
cce855bc JA |
555 | file_size = (size_t)finfo.st_size; |
556 | ||
557 | /* check for overflow on very large files */ | |
558 | if (file_size != finfo.st_size || file_size + 1 < file_size) | |
559 | { | |
560 | close (file); | |
561 | #if defined (EFBIG) | |
28ef6c31 JA |
562 | rv = errno = EFBIG; |
563 | #elif defined (EOVERFLOW) | |
564 | rv = errno = EOVERFLOW; | |
565 | #else | |
566 | rv = errno = EINVAL; | |
cce855bc JA |
567 | #endif |
568 | goto truncate_exit; | |
569 | } | |
570 | ||
7117c2d2 JA |
571 | buffer = (char *)malloc (file_size + 1); |
572 | if (buffer == 0) | |
573 | { | |
fbbc416f | 574 | rv = errno; |
7117c2d2 JA |
575 | close (file); |
576 | goto truncate_exit; | |
577 | } | |
578 | ||
cce855bc | 579 | chars_read = read (file, buffer, file_size); |
ccc6cda3 JA |
580 | close (file); |
581 | ||
582 | if (chars_read <= 0) | |
28ef6c31 JA |
583 | { |
584 | rv = (chars_read < 0) ? errno : 0; | |
585 | goto truncate_exit; | |
586 | } | |
ccc6cda3 | 587 | |
03d922b1 | 588 | orig_lines = lines; |
ccc6cda3 | 589 | /* Count backwards from the end of buffer until we have passed |
d3a24ed2 CR |
590 | LINES lines. bp1 is set funny initially. But since bp[1] can't |
591 | be a comment character (since it's off the end) and *bp can't be | |
592 | both a newline and the history comment character, it should be OK. */ | |
593 | for (bp1 = bp = buffer + chars_read - 1; lines && bp > buffer; bp--) | |
ccc6cda3 | 594 | { |
d3a24ed2 | 595 | if (*bp == '\n' && HIST_TIMESTAMP_START(bp1) == 0) |
ccc6cda3 | 596 | lines--; |
d3a24ed2 | 597 | bp1 = bp; |
ccc6cda3 JA |
598 | } |
599 | ||
600 | /* If this is the first line, then the file contains exactly the | |
601 | number of lines we want to truncate to, so we don't need to do | |
602 | anything. It's the first line if we don't find a newline between | |
603 | the current value of i and 0. Otherwise, write from the start of | |
604 | this line until the end of the buffer. */ | |
7117c2d2 | 605 | for ( ; bp > buffer; bp--) |
d3a24ed2 CR |
606 | { |
607 | if (*bp == '\n' && HIST_TIMESTAMP_START(bp1) == 0) | |
608 | { | |
609 | bp++; | |
610 | break; | |
611 | } | |
612 | bp1 = bp; | |
613 | } | |
ccc6cda3 JA |
614 | |
615 | /* Write only if there are more lines in the file than we want to | |
616 | truncate to. */ | |
25a0eacf CR |
617 | if (bp <= buffer) |
618 | { | |
619 | rv = 0; | |
03d922b1 CR |
620 | /* No-op if LINES == 0 at this point */ |
621 | history_lines_written_to_file = orig_lines - lines; | |
25a0eacf CR |
622 | goto truncate_exit; |
623 | } | |
624 | ||
1573ba78 | 625 | tempname = history_tempfile (filename); |
25a0eacf | 626 | |
1573ba78 | 627 | if ((file = open (tempname, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0600)) != -1) |
ccc6cda3 | 628 | { |
7571d3f4 CR |
629 | if (write (file, bp, chars_read - (bp - buffer)) < 0) |
630 | rv = errno; | |
b72432fd | 631 | |
cf58e12c CR |
632 | if (fstat (file, &nfinfo) < 0 && rv == 0) |
633 | rv = errno; | |
634 | ||
7571d3f4 CR |
635 | if (close (file) < 0 && rv == 0) |
636 | rv = errno; | |
ccc6cda3 | 637 | } |
25a0eacf CR |
638 | else |
639 | rv = errno; | |
ccc6cda3 JA |
640 | |
641 | truncate_exit: | |
ccc6cda3 JA |
642 | FREE (buffer); |
643 | ||
03d922b1 CR |
644 | history_lines_written_to_file = orig_lines - lines; |
645 | ||
1573ba78 CR |
646 | if (rv == 0 && filename && tempname) |
647 | rv = histfile_restore (tempname, filename); | |
648 | ||
649 | if (rv != 0) | |
8a0829e9 | 650 | { |
10db6565 | 651 | rv = errno; |
1573ba78 CR |
652 | if (tempname) |
653 | unlink (tempname); | |
8a0829e9 CR |
654 | history_lines_written_to_file = 0; |
655 | } | |
25a0eacf | 656 | |
553a7d66 | 657 | #if defined (HAVE_CHOWN) |
03d922b1 CR |
658 | /* Make sure the new filename is owned by the same user as the old. If one |
659 | user is running this, it's a no-op. If the shell is running after sudo | |
660 | with a shared history file, we don't want to leave the history file | |
661 | owned by root. */ | |
cf58e12c | 662 | if (rv == 0 && exists && SHOULD_CHOWN (finfo, nfinfo)) |
1b05a005 | 663 | r = chown (filename, finfo.st_uid, finfo.st_gid); |
553a7d66 | 664 | #endif |
03d922b1 | 665 | |
9ec5ed66 | 666 | xfree (filename); |
1573ba78 | 667 | FREE (tempname); |
25a0eacf | 668 | |
28ef6c31 | 669 | return rv; |
ccc6cda3 JA |
670 | } |
671 | ||
03d922b1 | 672 | /* Workhorse function for writing history. Writes the last NELEMENT entries |
ccc6cda3 JA |
673 | from the history list to FILENAME. OVERWRITE is non-zero if you |
674 | wish to replace FILENAME with the entries. */ | |
675 | static int | |
10729c7b | 676 | history_do_write (const char *filename, int nelements, int overwrite) |
ccc6cda3 JA |
677 | { |
678 | register int i; | |
1573ba78 | 679 | char *output, *tempname, *histname; |
03d922b1 | 680 | int file, mode, rv, exists; |
2e725f73 | 681 | struct stat finfo; |
d3a24ed2 | 682 | #ifdef HISTORY_USE_MMAP |
7117c2d2 | 683 | size_t cursize; |
ccc6cda3 | 684 | |
03d922b1 CR |
685 | history_lines_written_to_file = 0; |
686 | ||
7117c2d2 JA |
687 | mode = overwrite ? O_RDWR|O_CREAT|O_TRUNC|O_BINARY : O_RDWR|O_APPEND|O_BINARY; |
688 | #else | |
d166f048 | 689 | mode = overwrite ? O_WRONLY|O_CREAT|O_TRUNC|O_BINARY : O_WRONLY|O_APPEND|O_BINARY; |
7117c2d2 | 690 | #endif |
1573ba78 | 691 | histname = history_filename (filename); |
1573ba78 | 692 | exists = histname ? (stat (histname, &finfo) == 0) : 0; |
51f7ea36 | 693 | |
07357ec2 CR |
694 | tempname = (overwrite && exists && S_ISREG (finfo.st_mode)) ? history_tempfile (histname) : 0; |
695 | output = tempname ? tempname : histname; | |
696 | ||
64419627 | 697 | file = output ? open (output, mode, 0600) : -1; |
28ef6c31 | 698 | rv = 0; |
ccc6cda3 | 699 | |
64419627 | 700 | if (file == -1) |
ccc6cda3 | 701 | { |
51f7ea36 | 702 | rv = errno; |
1573ba78 CR |
703 | FREE (histname); |
704 | FREE (tempname); | |
51f7ea36 | 705 | return (rv); |
ccc6cda3 JA |
706 | } |
707 | ||
d3a24ed2 | 708 | #ifdef HISTORY_USE_MMAP |
7117c2d2 JA |
709 | cursize = overwrite ? 0 : lseek (file, 0, SEEK_END); |
710 | #endif | |
711 | ||
ccc6cda3 JA |
712 | if (nelements > history_length) |
713 | nelements = history_length; | |
714 | ||
715 | /* Build a buffer of all the lines to write, and write them in one syscall. | |
716 | Suggested by Peter Ho (peter@robosts.oxford.ac.uk). */ | |
717 | { | |
718 | HIST_ENTRY **the_history; /* local */ | |
2e725f73 CR |
719 | size_t j; |
720 | size_t buffer_size; | |
ccc6cda3 JA |
721 | char *buffer; |
722 | ||
723 | the_history = history_list (); | |
724 | /* Calculate the total number of bytes to write. */ | |
725 | for (buffer_size = 0, i = history_length - nelements; i < history_length; i++) | |
d3a24ed2 CR |
726 | { |
727 | if (history_write_timestamps && the_history[i]->timestamp && the_history[i]->timestamp[0]) | |
728 | buffer_size += strlen (the_history[i]->timestamp) + 1; | |
729 | buffer_size += strlen (the_history[i]->line) + 1; | |
730 | } | |
ccc6cda3 JA |
731 | |
732 | /* Allocate the buffer, and fill it. */ | |
d3a24ed2 | 733 | #ifdef HISTORY_USE_MMAP |
7117c2d2 JA |
734 | if (ftruncate (file, buffer_size+cursize) == -1) |
735 | goto mmap_error; | |
736 | buffer = (char *)mmap (0, buffer_size, PROT_READ|PROT_WRITE, MAP_WFLAGS, file, cursize); | |
737 | if ((void *)buffer == MAP_FAILED) | |
738 | { | |
739 | mmap_error: | |
740 | rv = errno; | |
7117c2d2 | 741 | close (file); |
1573ba78 CR |
742 | if (tempname) |
743 | unlink (tempname); | |
744 | FREE (histname); | |
745 | FREE (tempname); | |
7117c2d2 JA |
746 | return rv; |
747 | } | |
748 | #else | |
749 | buffer = (char *)malloc (buffer_size); | |
750 | if (buffer == 0) | |
751 | { | |
752 | rv = errno; | |
7117c2d2 | 753 | close (file); |
1573ba78 CR |
754 | if (tempname) |
755 | unlink (tempname); | |
756 | FREE (histname); | |
757 | FREE (tempname); | |
7117c2d2 JA |
758 | return rv; |
759 | } | |
760 | #endif | |
ccc6cda3 JA |
761 | |
762 | for (j = 0, i = history_length - nelements; i < history_length; i++) | |
763 | { | |
d3a24ed2 CR |
764 | if (history_write_timestamps && the_history[i]->timestamp && the_history[i]->timestamp[0]) |
765 | { | |
766 | strcpy (buffer + j, the_history[i]->timestamp); | |
767 | j += strlen (the_history[i]->timestamp); | |
768 | buffer[j++] = '\n'; | |
769 | } | |
ccc6cda3 JA |
770 | strcpy (buffer + j, the_history[i]->line); |
771 | j += strlen (the_history[i]->line); | |
772 | buffer[j++] = '\n'; | |
773 | } | |
774 | ||
d3a24ed2 | 775 | #ifdef HISTORY_USE_MMAP |
a31bf37d | 776 | if (msync (buffer, buffer_size, MS_ASYNC) != 0 || munmap (buffer, buffer_size) != 0) |
7117c2d2 JA |
777 | rv = errno; |
778 | #else | |
28ef6c31 JA |
779 | if (write (file, buffer, buffer_size) < 0) |
780 | rv = errno; | |
9ec5ed66 | 781 | xfree (buffer); |
7117c2d2 | 782 | #endif |
ccc6cda3 JA |
783 | } |
784 | ||
03d922b1 CR |
785 | history_lines_written_to_file = nelements; |
786 | ||
7571d3f4 CR |
787 | if (close (file) < 0 && rv == 0) |
788 | rv = errno; | |
ccc6cda3 | 789 | |
1573ba78 CR |
790 | if (rv == 0 && histname && tempname) |
791 | rv = histfile_restore (tempname, histname); | |
792 | ||
793 | if (rv != 0) | |
8a0829e9 | 794 | { |
10db6565 | 795 | rv = errno; |
1573ba78 CR |
796 | if (tempname) |
797 | unlink (tempname); | |
8a0829e9 CR |
798 | history_lines_written_to_file = 0; |
799 | } | |
51f7ea36 | 800 | |
553a7d66 | 801 | #if defined (HAVE_CHOWN) |
03d922b1 CR |
802 | /* Make sure the new filename is owned by the same user as the old. If one |
803 | user is running this, it's a no-op. If the shell is running after sudo | |
804 | with a shared history file, we don't want to leave the history file | |
805 | owned by root. */ | |
806 | if (rv == 0 && exists) | |
1b05a005 | 807 | mode = chown (histname, finfo.st_uid, finfo.st_gid); |
553a7d66 | 808 | #endif |
03d922b1 | 809 | |
1573ba78 CR |
810 | FREE (histname); |
811 | FREE (tempname); | |
ccc6cda3 | 812 | |
28ef6c31 | 813 | return (rv); |
ccc6cda3 JA |
814 | } |
815 | ||
816 | /* Append NELEMENT entries to FILENAME. The entries appended are from | |
817 | the end of the list minus NELEMENTs up to the end of the list. */ | |
818 | int | |
10729c7b | 819 | append_history (int nelements, const char *filename) |
ccc6cda3 JA |
820 | { |
821 | return (history_do_write (filename, nelements, HISTORY_APPEND)); | |
822 | } | |
823 | ||
824 | /* Overwrite FILENAME with the current history. If FILENAME is NULL, | |
825 | then write the history list to ~/.history. Values returned | |
826 | are as in read_history ().*/ | |
827 | int | |
10729c7b | 828 | write_history (const char *filename) |
ccc6cda3 JA |
829 | { |
830 | return (history_do_write (filename, history_length, HISTORY_OVERWRITE)); | |
831 | } |