diff options
-rw-r--r-- | src/nvim/buffer_defs.h | 5 | ||||
-rw-r--r-- | src/nvim/memline.c | 25 | ||||
-rw-r--r-- | src/nvim/misc2.c | 33 | ||||
-rw-r--r-- | src/nvim/undo.c | 656 | ||||
-rw-r--r-- | src/nvim/undo_defs.h | 7 |
5 files changed, 417 insertions, 309 deletions
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 35fa3978b6..9c8cdd41ae 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -6,6 +6,8 @@ // for FILE #include <stdio.h> +typedef struct file_buffer buf_T; // Forward declaration + // for garray_T #include "nvim/garray.h" // for pos_T, lpos_T and linenr_T @@ -16,7 +18,7 @@ #include "nvim/iconv.h" // for jump list and tag stack sizes in a buffer and mark types #include "nvim/mark_defs.h" -// for u_header_T +// for u_header_T; needs buf_T. #include "nvim/undo_defs.h" // for hashtab_T #include "nvim/hashtab.h" @@ -80,7 +82,6 @@ typedef struct window_S win_T; typedef struct wininfo_S wininfo_T; typedef struct frame_S frame_T; typedef int scid_T; /* script ID */ -typedef struct file_buffer buf_T; /* forward declaration */ // for struct memline (it needs memfile_T) #include "nvim/memline_defs.h" diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 8b2ebfe554..93fbbd512f 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -91,8 +91,6 @@ typedef struct pointer_entry PTR_EN; /* block/line-count pair */ #define PTR_ID (('p' << 8) + 't') /* pointer block id */ #define BLOCK0_ID0 'b' /* block 0 id 0 */ #define BLOCK0_ID1 '0' /* block 0 id 1 */ -#define BLOCK0_ID1_C0 'c' /* block 0 id 1 'cm' 0 */ -#define BLOCK0_ID1_C1 'C' /* block 0 id 1 'cm' 1 */ /* * pointer to a block, used in a pointer block @@ -176,8 +174,7 @@ struct data_block { * variables, because the rest of the swap file is not portable. */ struct block0 { - char_u b0_id[2]; /* id for block 0: BLOCK0_ID0 and BLOCK0_ID1, - * BLOCK0_ID1_C0, BLOCK0_ID1_C1 */ + char_u b0_id[2]; ///< ID for block 0: BLOCK0_ID0 and BLOCK0_ID1. char_u b0_version[10]; /* Vim version string */ char_u b0_page_size[4]; /* number of bytes per page */ char_u b0_mtime[4]; /* last modification time of file */ @@ -619,22 +616,16 @@ void ml_timestamp(buf_T *buf) ml_upd_block0(buf, UB_FNAME); } -/* - * Return FAIL when the ID of "b0p" is wrong. - */ -static int ml_check_b0_id(ZERO_BL *b0p) +/// Checks whether the IDs in b0 are valid. +static bool ml_check_b0_id(ZERO_BL *b0p) + FUNC_ATTR_NONNULL_ALL { - if (b0p->b0_id[0] != BLOCK0_ID0 - || (b0p->b0_id[1] != BLOCK0_ID1 - && b0p->b0_id[1] != BLOCK0_ID1_C0 - && b0p->b0_id[1] != BLOCK0_ID1_C1) - ) - return FAIL; - return OK; + return b0p->b0_id[0] == BLOCK0_ID0 && b0p->b0_id[1] == BLOCK0_ID1; } -/// Return true if all strings in b0 are correct (nul-terminated). -static bool ml_check_b0_strings(ZERO_BL *b0p) FUNC_ATTR_NONNULL_ALL +/// Checks whether all strings in b0 are valid (i.e. nul-terminated). +static bool ml_check_b0_strings(ZERO_BL *b0p) + FUNC_ATTR_NONNULL_ALL { return (memchr(b0p->b0_version, NUL, 10) && memchr(b0p->b0_uname, NUL, B0_UNAME_SIZE) diff --git a/src/nvim/misc2.c b/src/nvim/misc2.c index fafce66c5f..79b42cabf0 100644 --- a/src/nvim/misc2.c +++ b/src/nvim/misc2.c @@ -9,6 +9,7 @@ /* * misc2.c: Various functions. */ +#include <assert.h> #include <errno.h> #include <inttypes.h> #include <string.h> @@ -29,6 +30,7 @@ #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" +#include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memfile.h" @@ -459,22 +461,33 @@ char *read_string(FILE *fd, size_t cnt) return (char *)str; } -/// Write a number to file "fd", MSB first, in "len" bytes. -/// @return OK/FAIL. -int put_bytes(FILE *fd, uintmax_t number, unsigned int len) +/// Writes a number to file "fd", most significant bit first, in "len" bytes. +/// @returns false in case of an error. +bool put_bytes(FILE *fd, uintmax_t number, size_t len) { - for (unsigned int i = len - 1; i < len; --i) - if (putc((int)(number >> (i * 8)), fd) == EOF) - return FAIL; - return OK; + assert(len > 0); + for (size_t i = len - 1; i < len; i--) { + if (putc((int)(number >> (i * 8)), fd) == EOF) { + return false; + } + } + return true; } -/// Write time_t to file "fd" in 8 bytes. +/// Writes time_t to file "fd" in 8 bytes. void put_time(FILE *fd, time_t time_) { + uint8_t buf[8]; + time_to_bytes(time_, buf); + fwrite(buf, sizeof(uint8_t), ARRAY_SIZE(buf), fd); +} + +/// Writes time_t to "buf[8]". +void time_to_bytes(time_t time_, uint8_t buf[8]) +{ // time_t can be up to 8 bytes in size, more than uintmax_t in 32 bits // systems, thus we can't use put_bytes() here. - for (unsigned int i = 7; i < 8; --i) { - putc((int)((uint64_t)time_ >> (i * 8)), fd); + for (size_t i = 7, bufi = 0; bufi < 8; i--, bufi++) { + buf[bufi] = (uint8_t)((uint64_t)time_ >> (i * 8)); } } diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 67195235fe..4a721a0884 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -713,143 +713,159 @@ static void u_free_uhp(u_header_T *uhp) free(uhp); } -static int serialize_header(FILE *fp, buf_T *buf, char_u *hash) +/// Writes the header. +/// @returns false in case of an error. +static bool serialize_header(bufinfo_T *bi, char_u *hash) + FUNC_ATTR_NONNULL_ALL { - /* Start writing, first the magic marker and undo info version. */ - if (fwrite(UF_START_MAGIC, UF_START_MAGIC_LEN, 1, fp) != 1) - return FAIL; + buf_T *buf = bi->bi_buf; + FILE *fp = bi->bi_fp; - put_bytes(fp, UF_VERSION, 2); - - /* Write a hash of the buffer text, so that we can verify it is still the - * same when reading the buffer text. */ - if (fwrite(hash, UNDO_HASH_SIZE, 1, fp) != 1) - return FAIL; - - /* buffer-specific data */ - put_bytes(fp, (uintmax_t)buf->b_ml.ml_line_count, 4); - size_t len = buf->b_u_line_ptr ? STRLEN(buf->b_u_line_ptr) : 0; - put_bytes(fp, len, 4); - if (len > 0 && fwrite(buf->b_u_line_ptr, len, 1, fp) != 1) - return FAIL; - put_bytes(fp, (uintmax_t)buf->b_u_line_lnum, 4); - put_bytes(fp, (uintmax_t)buf->b_u_line_colnr, 4); - - /* Undo structures header data */ - put_header_ptr(fp, buf->b_u_oldhead); - put_header_ptr(fp, buf->b_u_newhead); - put_header_ptr(fp, buf->b_u_curhead); - - put_bytes(fp, (uintmax_t)buf->b_u_numhead, 4); - put_bytes(fp, (uintmax_t)buf->b_u_seq_last, 4); - put_bytes(fp, (uintmax_t)buf->b_u_seq_cur, 4); - put_time(fp, buf->b_u_time_cur); + // Start writing, first the magic marker and undo info version. + if (fwrite(UF_START_MAGIC, UF_START_MAGIC_LEN, 1, fp) != 1) { + return false; + } - /* Optional fields. */ - putc(4, fp); - putc(UF_LAST_SAVE_NR, fp); - put_bytes(fp, (uintmax_t)buf->b_u_save_nr_last, 4); + undo_write_bytes(bi, UF_VERSION, 2); - putc(0, fp); /* end marker */ + // Write a hash of the buffer text, so that we can verify it is + // still the same when reading the buffer text. + if (!undo_write(bi, hash, UNDO_HASH_SIZE)) { + return false; + } - return OK; + // Write buffer-specific data. + undo_write_bytes(bi, (uintmax_t)buf->b_ml.ml_line_count, 4); + size_t len = buf->b_u_line_ptr ? STRLEN(buf->b_u_line_ptr) : 0; + undo_write_bytes(bi, len, 4); + if (len > 0 && !undo_write(bi, buf->b_u_line_ptr, len)) { + return false; + } + undo_write_bytes(bi, (uintmax_t)buf->b_u_line_lnum, 4); + undo_write_bytes(bi, (uintmax_t)buf->b_u_line_colnr, 4); + + // Write undo structures header data. + put_header_ptr(bi, buf->b_u_oldhead); + put_header_ptr(bi, buf->b_u_newhead); + put_header_ptr(bi, buf->b_u_curhead); + + undo_write_bytes(bi, (uintmax_t)buf->b_u_numhead, 4); + undo_write_bytes(bi, (uintmax_t)buf->b_u_seq_last, 4); + undo_write_bytes(bi, (uintmax_t)buf->b_u_seq_cur, 4); + uint8_t time_buf[8]; + time_to_bytes(buf->b_u_time_cur, time_buf); + undo_write(bi, time_buf, sizeof(time_buf)); + + // Write optional fields. + undo_write_bytes(bi, 4, 1); + undo_write_bytes(bi, UF_LAST_SAVE_NR, 1); + undo_write_bytes(bi, (uintmax_t)buf->b_u_save_nr_last, 4); + + // Write end marker. + undo_write_bytes(bi, 0, 1); + + return true; } -static int serialize_uhp(FILE *fp, buf_T *buf, u_header_T *uhp) +static bool serialize_uhp(bufinfo_T *bi, u_header_T *uhp) { - if (put_bytes(fp, UF_HEADER_MAGIC, 2) == FAIL) - return FAIL; - - put_header_ptr(fp, uhp->uh_next.ptr); - put_header_ptr(fp, uhp->uh_prev.ptr); - put_header_ptr(fp, uhp->uh_alt_next.ptr); - put_header_ptr(fp, uhp->uh_alt_prev.ptr); - put_bytes(fp, (uintmax_t)uhp->uh_seq, 4); - serialize_pos(uhp->uh_cursor, fp); - put_bytes(fp, (uintmax_t)uhp->uh_cursor_vcol, 4); - put_bytes(fp, (uintmax_t)uhp->uh_flags, 2); - /* Assume NMARKS will stay the same. */ - for (size_t i = 0; i < NMARKS; ++i) - serialize_pos(uhp->uh_namedm[i], fp); - serialize_visualinfo(&uhp->uh_visual, fp); - put_time(fp, uhp->uh_time); - - /* Optional fields. */ - putc(4, fp); - putc(UHP_SAVE_NR, fp); - put_bytes(fp, (uintmax_t)uhp->uh_save_nr, 4); - - putc(0, fp); /* end marker */ - - /* Write all the entries. */ + if (!undo_write_bytes(bi, (uintmax_t)UF_HEADER_MAGIC, 2)) { + return false; + } + + put_header_ptr(bi, uhp->uh_next.ptr); + put_header_ptr(bi, uhp->uh_prev.ptr); + put_header_ptr(bi, uhp->uh_alt_next.ptr); + put_header_ptr(bi, uhp->uh_alt_prev.ptr); + undo_write_bytes(bi, (uintmax_t)uhp->uh_seq, 4); + serialize_pos(bi, uhp->uh_cursor); + undo_write_bytes(bi, (uintmax_t)uhp->uh_cursor_vcol, 4); + undo_write_bytes(bi, (uintmax_t)uhp->uh_flags, 2); + // Assume NMARKS will stay the same. + for (size_t i = 0; i < (size_t)NMARKS; i++) { + serialize_pos(bi, uhp->uh_namedm[i]); + } + serialize_visualinfo(bi, &uhp->uh_visual); + uint8_t time_buf[8]; + time_to_bytes(uhp->uh_time, time_buf); + undo_write(bi, time_buf, sizeof(time_buf)); + + // Write optional fields. + undo_write_bytes(bi, 4, 1); + undo_write_bytes(bi, UHP_SAVE_NR, 1); + undo_write_bytes(bi, (uintmax_t)uhp->uh_save_nr, 4); + + // Write end marker. + undo_write_bytes(bi, 0, 1); + + // Write all the entries. for (u_entry_T *uep = uhp->uh_entry; uep; uep = uep->ue_next) { - put_bytes(fp, UF_ENTRY_MAGIC, 2); - if (serialize_uep(fp, buf, uep) == FAIL) - return FAIL; + undo_write_bytes(bi, (uintmax_t)UF_ENTRY_MAGIC, 2); + if (!serialize_uep(bi, uep)) { + return false; + } } - put_bytes(fp, UF_ENTRY_END_MAGIC, 2); - return OK; + undo_write_bytes(bi, (uintmax_t)UF_ENTRY_END_MAGIC, 2); + return true; } -static u_header_T *unserialize_uhp(FILE *fp, char_u *file_name) +static u_header_T *unserialize_uhp(bufinfo_T *bi, char_u *file_name) { - u_header_T *uhp; - int i; - u_entry_T *uep, *last_uep; - int c; - int error; - - uhp = xmalloc(sizeof(u_header_T)); + u_header_T *uhp = xmalloc(sizeof(u_header_T)); memset(uhp, 0, sizeof(u_header_T)); #ifdef U_DEBUG uhp->uh_magic = UH_MAGIC; #endif - uhp->uh_next.seq = get4c(fp); - uhp->uh_prev.seq = get4c(fp); - uhp->uh_alt_next.seq = get4c(fp); - uhp->uh_alt_prev.seq = get4c(fp); - uhp->uh_seq = get4c(fp); + uhp->uh_next.seq = undo_read_4c(bi); + uhp->uh_prev.seq = undo_read_4c(bi); + uhp->uh_alt_next.seq = undo_read_4c(bi); + uhp->uh_alt_prev.seq = undo_read_4c(bi); + uhp->uh_seq = undo_read_4c(bi); if (uhp->uh_seq <= 0) { corruption_error("uh_seq", file_name); free(uhp); return NULL; } - unserialize_pos(&uhp->uh_cursor, fp); - uhp->uh_cursor_vcol = get4c(fp); - uhp->uh_flags = get2c(fp); - for (i = 0; i < NMARKS; ++i) - unserialize_pos(&uhp->uh_namedm[i], fp); - unserialize_visualinfo(&uhp->uh_visual, fp); - uhp->uh_time = get8ctime(fp); + unserialize_pos(bi, &uhp->uh_cursor); + uhp->uh_cursor_vcol = undo_read_4c(bi); + uhp->uh_flags = undo_read_2c(bi); + for (size_t i = 0; i < (size_t)NMARKS; i++) { + unserialize_pos(bi, &uhp->uh_namedm[i]); + } + unserialize_visualinfo(bi, &uhp->uh_visual); + uhp->uh_time = undo_read_time(bi); - /* Optional fields. */ + // Unserialize optional fields. for (;; ) { - int len = getc(fp); - int what; + int len = undo_read_byte(bi); - if (len == 0) + if (len == 0) { break; - what = getc(fp); + } + int what = undo_read_byte(bi); switch (what) { case UHP_SAVE_NR: - uhp->uh_save_nr = get4c(fp); + uhp->uh_save_nr = undo_read_4c(bi); break; default: - /* field not supported, skip */ - while (--len >= 0) - (void)getc(fp); + // Field not supported, skip it. + while (--len >= 0) { + (void)undo_read_byte(bi); + } } } - /* Unserialize the uep list. */ - last_uep = NULL; - while ((c = get2c(fp)) == UF_ENTRY_MAGIC) { - error = FALSE; - uep = unserialize_uep(fp, &error, file_name); - if (last_uep == NULL) + // Unserialize the uep list. + u_entry_T *last_uep = NULL; + int c; + while ((c = undo_read_2c(bi)) == UF_ENTRY_MAGIC) { + bool error = false; + u_entry_T *uep = unserialize_uep(bi, &error, file_name); + if (last_uep == NULL) { uhp->uh_entry = uep; - else + } else { last_uep->ue_next = uep; + } last_uep = uep; if (uep == NULL || error) { u_free_uhp(uhp); @@ -865,59 +881,60 @@ static u_header_T *unserialize_uhp(FILE *fp, char_u *file_name) return uhp; } -/* - * Serialize "uep" to "fp". - */ -static int serialize_uep(FILE *fp, buf_T *buf, u_entry_T *uep) +/// Serializes "uep". +/// +/// @returns false in case of an error. +static bool serialize_uep(bufinfo_T *bi, u_entry_T *uep) { - put_bytes(fp, (uintmax_t)uep->ue_top, 4); - put_bytes(fp, (uintmax_t)uep->ue_bot, 4); - put_bytes(fp, (uintmax_t)uep->ue_lcount, 4); - put_bytes(fp, (uintmax_t)uep->ue_size, 4); - for (size_t i = 0; i < (size_t)uep->ue_size; ++i) { + undo_write_bytes(bi, (uintmax_t)uep->ue_top, 4); + undo_write_bytes(bi, (uintmax_t)uep->ue_bot, 4); + undo_write_bytes(bi, (uintmax_t)uep->ue_lcount, 4); + undo_write_bytes(bi, (uintmax_t)uep->ue_size, 4); + + for (size_t i = 0; i < (size_t)uep->ue_size; i++) { size_t len = STRLEN(uep->ue_array[i]); - if (put_bytes(fp, len, 4) == FAIL) - return FAIL; - if (len > 0 && fwrite(uep->ue_array[i], len, 1, fp) != 1) - return FAIL; + if (!undo_write_bytes(bi, len, 4)) { + return false; + } + if (len > 0 && !undo_write(bi, uep->ue_array[i], len)) { + return false; + } } - return OK; + return true; } -static u_entry_T *unserialize_uep(FILE *fp, int *error, char_u *file_name) +static u_entry_T *unserialize_uep(bufinfo_T * bi, bool *error, char_u *file_name) { - int i; - u_entry_T *uep; - char_u **array; - char_u *line; - int line_len; - - uep = xmalloc(sizeof(u_entry_T)); + u_entry_T *uep = xmalloc(sizeof(u_entry_T)); memset(uep, 0, sizeof(u_entry_T)); #ifdef U_DEBUG uep->ue_magic = UE_MAGIC; #endif - uep->ue_top = get4c(fp); - uep->ue_bot = get4c(fp); - uep->ue_lcount = get4c(fp); - uep->ue_size = get4c(fp); + uep->ue_top = undo_read_4c(bi); + uep->ue_bot = undo_read_4c(bi); + uep->ue_lcount = undo_read_4c(bi); + uep->ue_size = undo_read_4c(bi); + + char_u **array; if (uep->ue_size > 0) { array = xmalloc(sizeof(char_u *) * (size_t)uep->ue_size); memset(array, 0, sizeof(char_u *) * (size_t)uep->ue_size); - } else + } else { array = NULL; + } uep->ue_array = array; - for (i = 0; i < uep->ue_size; ++i) { - line_len = get4c(fp); - if (line_len >= 0) - line = READ_STRING(fp, line_len); - else { + for (size_t i = 0; i < (size_t)uep->ue_size; i++) { + int line_len = undo_read_4c(bi); + char_u *line; + if (line_len >= 0) { + line = undo_read_string(bi, (size_t)line_len); + } else { line = NULL; corruption_error("line length", file_name); } if (line == NULL) { - *error = TRUE; + *error = true; return uep; } array[i] = line; @@ -925,61 +942,47 @@ static u_entry_T *unserialize_uep(FILE *fp, int *error, char_u *file_name) return uep; } -/* - * Serialize "pos" to "fp". - */ -static void serialize_pos(pos_T pos, FILE *fp) +/// Serializes "pos". +static void serialize_pos(bufinfo_T *bi, pos_T pos) { - put_bytes(fp, (uintmax_t)pos.lnum, 4); - put_bytes(fp, (uintmax_t)pos.col, 4); - put_bytes(fp, (uintmax_t)pos.coladd, 4); + undo_write_bytes(bi, (uintmax_t)pos.lnum, 4); + undo_write_bytes(bi, (uintmax_t)pos.col, 4); + undo_write_bytes(bi, (uintmax_t)pos.coladd, 4); } -/* - * Unserialize the pos_T at the current position in fp. - */ -static void unserialize_pos(pos_T *pos, FILE *fp) +/// Unserializes the pos_T at the current position. +static void unserialize_pos(bufinfo_T *bi, pos_T *pos) { - pos->lnum = get4c(fp); - if (pos->lnum < 0) + pos->lnum = undo_read_4c(bi); + if (pos->lnum < 0) { pos->lnum = 0; - pos->col = get4c(fp); - if (pos->col < 0) + } + pos->col = undo_read_4c(bi); + if (pos->col < 0) { pos->col = 0; - pos->coladd = get4c(fp); - if (pos->coladd < 0) + } + pos->coladd = undo_read_4c(bi); + if (pos->coladd < 0) { pos->coladd = 0; + } } -/* - * Serialize "info" to "fp". - */ -static void serialize_visualinfo(visualinfo_T *info, FILE *fp) +/// Serializes "info". +static void serialize_visualinfo(bufinfo_T *bi, visualinfo_T *info) { - serialize_pos(info->vi_start, fp); - serialize_pos(info->vi_end, fp); - put_bytes(fp, (uintmax_t)info->vi_mode, 4); - put_bytes(fp, (uintmax_t)info->vi_curswant, 4); + serialize_pos(bi, info->vi_start); + serialize_pos(bi, info->vi_end); + undo_write_bytes(bi, (uintmax_t)info->vi_mode, 4); + undo_write_bytes(bi, (uintmax_t)info->vi_curswant, 4); } -/* - * Unserialize the visualinfo_T at the current position in fp. - */ -static void unserialize_visualinfo(visualinfo_T *info, FILE *fp) +/// Unserializes the visualinfo_T at the current position. +static void unserialize_visualinfo(bufinfo_T *bi, visualinfo_T *info) { - unserialize_pos(&info->vi_start, fp); - unserialize_pos(&info->vi_end, fp); - info->vi_mode = get4c(fp); - info->vi_curswant = get4c(fp); -} - -/* - * Write the pointer to an undo header. Instead of writing the pointer itself - * we use the sequence number of the header. This is converted back to - * pointers when reading. */ -static void put_header_ptr(FILE *fp, u_header_T *uhp) -{ - put_bytes(fp, (uintmax_t)(uhp != NULL ? uhp->uh_seq : 0), 4); + unserialize_pos(bi, &info->vi_start); + unserialize_pos(bi, &info->vi_end); + info->vi_mode = undo_read_4c(bi); + info->vi_curswant = undo_read_4c(bi); } /* @@ -1003,6 +1006,7 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) FILE *fp = NULL; int perm; bool write_ok = false; + bufinfo_T bi; if (name == NULL) { file_name = u_get_undo_file_name(buf->b_ffname, FALSE); @@ -1134,8 +1138,11 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) /* * Write the header. */ - if (serialize_header(fp, buf, hash) == FAIL) + bi.bi_buf = buf; + bi.bi_fp = fp; + if (!serialize_header(&bi, hash)) { goto write_error; + } /* * Iteratively serialize UHPs and their UEPs from the top down. @@ -1149,8 +1156,9 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) #ifdef U_DEBUG ++headers_written; #endif - if (serialize_uhp(fp, buf, uhp) == FAIL) + if (!serialize_uhp(&bi, uhp)) { goto write_error; + } } /* Now walk through the tree - algorithm from undo_time(). */ @@ -1168,8 +1176,9 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) uhp = uhp->uh_next.ptr; } - if (put_bytes(fp, UF_HEADER_END_MAGIC, 2) == OK) + if (undo_write_bytes(&bi, (uintmax_t)UF_HEADER_END_MAGIC, 2)) { write_ok = true; + } #ifdef U_DEBUG if (headers_written != buf->b_u_numhead) { EMSGN("Written %" PRId64 " headers, ...", headers_written); @@ -1198,47 +1207,27 @@ theend: free(file_name); } -/* - * Load the undo tree from an undo file. - * If "name" is not NULL use it as the undo file name. This also means being - * a bit more verbose. - * Otherwise use curbuf->b_ffname to generate the undo file name. - * "hash[UNDO_HASH_SIZE]" must be the hash value of the buffer text. - */ +/// Loads the undo tree from an undo file. +/// If "name" is not NULL use it as the undo file name. This also means being +/// a bit more verbose. +/// Otherwise use curbuf->b_ffname to generate the undo file name. +/// "hash[UNDO_HASH_SIZE]" must be the hash value of the buffer text. void u_read_undo(char_u *name, char_u *hash, char_u *orig_name) + FUNC_ATTR_NONNULL_ARG(2) { - char_u *file_name; - FILE *fp; - long version, str_len; - char_u *line_ptr = NULL; - linenr_T line_lnum; - colnr_T line_colnr; - linenr_T line_count; - int num_head = 0; - long old_header_seq, new_header_seq, cur_header_seq; - long seq_last, seq_cur; - long last_save_nr = 0; - short old_idx = -1, new_idx = -1, cur_idx = -1; - long num_read_uhps = 0; - time_t seq_time; - int i, j; - int c; - u_header_T *uhp; - u_header_T **uhp_table = NULL; - char_u read_hash[UNDO_HASH_SIZE]; - char_u magic_buf[UF_START_MAGIC_LEN]; -#ifdef U_DEBUG - int *uhp_table_used; -#endif + u_header_T **uhp_table = NULL; + char_u *line_ptr = NULL; + char_u *file_name; if (name == NULL) { file_name = u_get_undo_file_name(curbuf->b_ffname, TRUE); - if (file_name == NULL) + if (file_name == NULL) { return; + } #ifdef UNIX - /* For safety we only read an undo file if the owner is equal to the - * owner of the text file or equal to the current user. */ + // For safety we only read an undo file if the owner is equal to the + // owner of the text file or equal to the current user. FileInfo file_info_orig; FileInfo file_info_undo; if (os_fileinfo((char *)orig_name, &file_info_orig) @@ -1254,8 +1243,9 @@ void u_read_undo(char_u *name, char_u *hash, char_u *orig_name) return; } #endif - } else + } else { file_name = name; + } if (p_verbose > 0) { verbose_enter(); @@ -1263,103 +1253,120 @@ void u_read_undo(char_u *name, char_u *hash, char_u *orig_name) verbose_leave(); } - fp = mch_fopen((char *)file_name, "r"); + FILE *fp = mch_fopen((char *)file_name, "r"); if (fp == NULL) { - if (name != NULL || p_verbose > 0) + if (name != NULL || p_verbose > 0) { EMSG2(_("E822: Cannot open undo file for reading: %s"), file_name); + } goto error; } - /* - * Read the undo file header. - */ + bufinfo_T bi; + bi.bi_buf = curbuf; + bi.bi_fp = fp; + + // Read the undo file header. + char_u magic_buf[UF_START_MAGIC_LEN]; if (fread(magic_buf, UF_START_MAGIC_LEN, 1, fp) != 1 || memcmp(magic_buf, UF_START_MAGIC, UF_START_MAGIC_LEN) != 0) { EMSG2(_("E823: Not an undo file: %s"), file_name); goto error; } - version = get2c(fp); + int version = get2c(fp); if (version != UF_VERSION) { EMSG2(_("E824: Incompatible undo file: %s"), file_name); goto error; } - if (fread(read_hash, UNDO_HASH_SIZE, 1, fp) != 1) { + char_u read_hash[UNDO_HASH_SIZE]; + if (!undo_read(&bi, read_hash, UNDO_HASH_SIZE)) { corruption_error("hash", file_name); goto error; } - line_count = (linenr_T)get4c(fp); + linenr_T line_count = (linenr_T)undo_read_4c(&bi); if (memcmp(hash, read_hash, UNDO_HASH_SIZE) != 0 || line_count != curbuf->b_ml.ml_line_count) { if (p_verbose > 0 || name != NULL) { - if (name == NULL) + if (name == NULL) { verbose_enter(); + } give_warning((char_u *) _("File contents changed, cannot use undo info"), true); - if (name == NULL) + if (name == NULL) { verbose_leave(); + } } goto error; } - /* Read undo data for "U" command. */ - str_len = get4c(fp); - if (str_len < 0) + // Read undo data for "U" command. + int str_len = undo_read_4c(&bi); + if (str_len < 0) { goto error; - if (str_len > 0) - line_ptr = READ_STRING(fp, str_len); - line_lnum = (linenr_T)get4c(fp); - line_colnr = (colnr_T)get4c(fp); + } + + if (str_len > 0) { + line_ptr = undo_read_string(&bi, (size_t)str_len); + } + linenr_T line_lnum = (linenr_T)undo_read_4c(&bi); + colnr_T line_colnr = (colnr_T)undo_read_4c(&bi); if (line_lnum < 0 || line_colnr < 0) { corruption_error("line lnum/col", file_name); goto error; } - /* Begin general undo data */ - old_header_seq = get4c(fp); - new_header_seq = get4c(fp); - cur_header_seq = get4c(fp); - num_head = get4c(fp); - seq_last = get4c(fp); - seq_cur = get4c(fp); - seq_time = get8ctime(fp); + // Begin general undo data + int old_header_seq = undo_read_4c(&bi); + int new_header_seq = undo_read_4c(&bi); + int cur_header_seq = undo_read_4c(&bi); + int num_head = undo_read_4c(&bi); + int seq_last = undo_read_4c(&bi); + int seq_cur = undo_read_4c(&bi); + time_t seq_time = undo_read_time(&bi); - /* Optional header fields. */ + // Optional header fields. + long last_save_nr = 0; for (;; ) { - int len = getc(fp); - int what; + int len = undo_read_byte(&bi); - if (len == 0 || len == EOF) + if (len == 0 || len == EOF) { break; - what = getc(fp); + } + int what = undo_read_byte(&bi); switch (what) { - case UF_LAST_SAVE_NR: - last_save_nr = get4c(fp); - break; - default: - /* field not supported, skip */ - while (--len >= 0) - (void)getc(fp); + case UF_LAST_SAVE_NR: + last_save_nr = undo_read_4c(&bi); + break; + + default: + // field not supported, skip + while (--len >= 0) { + (void)undo_read_byte(&bi); + } } } - /* uhp_table will store the freshly created undo headers we allocate - * until we insert them into curbuf. The table remains sorted by the - * sequence numbers of the headers. - * When there are no headers uhp_table is NULL. */ + // uhp_table will store the freshly created undo headers we allocate + // until we insert them into curbuf. The table remains sorted by the + // sequence numbers of the headers. + // When there are no headers uhp_table is NULL. if (num_head > 0) { uhp_table = xmalloc((size_t)num_head * sizeof(u_header_T *)); } - while ((c = get2c(fp)) == UF_HEADER_MAGIC) { + long num_read_uhps = 0; + + int c; + while ((c = undo_read_2c(&bi)) == UF_HEADER_MAGIC) { if (num_read_uhps >= num_head) { corruption_error("num_head too small", file_name); goto error; } - uhp = unserialize_uhp(fp, file_name); - if (uhp == NULL) + u_header_T *uhp = unserialize_uhp(&bi, file_name); + if (uhp == NULL) { goto error; + } uhp_table[num_read_uhps++] = uhp; } @@ -1374,54 +1381,61 @@ void u_read_undo(char_u *name, char_u *hash, char_u *orig_name) #ifdef U_DEBUG size_t amount = num_head * sizeof(int) + 1; - uhp_table_used = xmalloc(amount); + int *uhp_table_used = xmalloc(amount); memset(uhp_table_used, 0, amount); # define SET_FLAG(j) ++ uhp_table_used[j] #else # define SET_FLAG(j) #endif - /* We have put all of the headers into a table. Now we iterate through the - * table and swizzle each sequence number we have stored in uh_*_seq into - * a pointer corresponding to the header with that sequence number. */ - for (i = 0; i < num_head; i++) { - uhp = uhp_table[i]; - if (uhp == NULL) + // We have put all of the headers into a table. Now we iterate through the + // table and swizzle each sequence number we have stored in uh_*_seq into + // a pointer corresponding to the header with that sequence number. + short old_idx = -1, new_idx = -1, cur_idx = -1; + for (int i = 0; i < num_head; i++) { + u_header_T *uhp = uhp_table[i]; + if (uhp == NULL) { continue; - for (j = 0; j < num_head; j++) + } + for (int j = 0; j < num_head; j++) { if (uhp_table[j] != NULL && i != j && uhp_table[i]->uh_seq == uhp_table[j]->uh_seq) { corruption_error("duplicate uh_seq", file_name); goto error; } - for (j = 0; j < num_head; j++) + } + for (int j = 0; j < num_head; j++) { if (uhp_table[j] != NULL && uhp_table[j]->uh_seq == uhp->uh_next.seq) { uhp->uh_next.ptr = uhp_table[j]; SET_FLAG(j); break; } - for (j = 0; j < num_head; j++) + } + for (int j = 0; j < num_head; j++) { if (uhp_table[j] != NULL && uhp_table[j]->uh_seq == uhp->uh_prev.seq) { uhp->uh_prev.ptr = uhp_table[j]; SET_FLAG(j); break; } - for (j = 0; j < num_head; j++) + } + for (int j = 0; j < num_head; j++) { if (uhp_table[j] != NULL && uhp_table[j]->uh_seq == uhp->uh_alt_next.seq) { uhp->uh_alt_next.ptr = uhp_table[j]; SET_FLAG(j); break; } - for (j = 0; j < num_head; j++) + } + for (int j = 0; j < num_head; j++) { if (uhp_table[j] != NULL && uhp_table[j]->uh_seq == uhp->uh_alt_prev.seq) { uhp->uh_alt_prev.ptr = uhp_table[j]; SET_FLAG(j); break; } + } if (old_header_seq > 0 && old_idx < 0 && uhp->uh_seq == old_header_seq) { assert(i <= SHRT_MAX); old_idx = (short)i; @@ -1439,8 +1453,8 @@ void u_read_undo(char_u *name, char_u *hash, char_u *orig_name) } } - /* Now that we have read the undo info successfully, free the current undo - * info and use the info from the file. */ + // Now that we have read the undo info successfully, free the current undo + // info and use the info from the file. u_blockfree(curbuf); curbuf->b_u_oldhead = old_idx < 0 ? NULL : uhp_table[old_idx]; curbuf->b_u_newhead = new_idx < 0 ? NULL : uhp_table[new_idx]; @@ -1459,35 +1473,117 @@ void u_read_undo(char_u *name, char_u *hash, char_u *orig_name) free(uhp_table); #ifdef U_DEBUG - for (i = 0; i < num_head; ++i) - if (uhp_table_used[i] == 0) + for (int i = 0; i < num_head; i++) { + if (uhp_table_used[i] == 0) { EMSGN("uhp_table entry %" PRId64 " not used, leaking memory", i); + } + } free(uhp_table_used); u_check(TRUE); #endif - if (name != NULL) + if (name != NULL) { smsg((char_u *)_("Finished reading undo file %s"), file_name); + } goto theend; error: free(line_ptr); if (uhp_table != NULL) { - for (i = 0; i < num_read_uhps; i++) - if (uhp_table[i] != NULL) + for (long i = 0; i < num_read_uhps; i++) + if (uhp_table[i] != NULL) { u_free_uhp(uhp_table[i]); + } free(uhp_table); } theend: - if (fp != NULL) + if (fp != NULL) { fclose(fp); - if (file_name != name) + } + if (file_name != name) { free(file_name); - return; + } +} + +/// Writes a sequence of bytes to the undo file. +/// +/// @returns false in case of an error. +static bool undo_write(bufinfo_T *bi, uint8_t *ptr, size_t len) + FUNC_ATTR_NONNULL_ARG(1) +{ + return fwrite(ptr, len, 1, bi->bi_fp) == 1; +} + +/// Writes a number, most significant bit first, in "len" bytes. +/// +/// Must match with undo_read_?c() functions. +/// +/// @returns false in case of an error. +static bool undo_write_bytes(bufinfo_T *bi, uintmax_t nr, size_t len) +{ + assert(len > 0); + uint8_t buf[8]; + for (size_t i = len - 1, bufi = 0; bufi < len; i--, bufi++) { + buf[bufi] = (uint8_t)(nr >> (i * 8)); + } + return undo_write(bi, buf, len); +} + +/// Writes the pointer to an undo header. +/// +/// Instead of writing the pointer itself, we use the sequence +/// number of the header. This is converted back to pointers +/// when reading. +static void put_header_ptr(bufinfo_T *bi, u_header_T *uhp) +{ + assert(uhp == NULL || uhp->uh_seq >= 0); + undo_write_bytes(bi, (uint64_t)(uhp != NULL ? uhp->uh_seq : 0), 4); +} + +static int undo_read_4c(bufinfo_T *bi) +{ + return get4c(bi->bi_fp); } +static int undo_read_2c(bufinfo_T *bi) +{ + return get2c(bi->bi_fp); +} +static int undo_read_byte(bufinfo_T *bi) +{ + return getc(bi->bi_fp); +} + +static time_t undo_read_time(bufinfo_T *bi) +{ + return get8ctime(bi->bi_fp); +} + +/// Reads "buffer[size]" from the undo file. +/// +/// @returns false in case of an error. +static bool undo_read(bufinfo_T *bi, uint8_t *buffer, size_t size) + FUNC_ATTR_NONNULL_ARG(1) +{ + return fread(buffer, size, 1, bi->bi_fp) == 1; +} + +/// Reads a string of length "len" from "bi->bi_fd" and appends a zero to it. +/// +/// @param len can be zero to allocate an empty line. +/// +/// @returns a pointer to allocated memory or NULL in case of an error. +static uint8_t *undo_read_string(bufinfo_T *bi, size_t len) +{ + uint8_t *ptr = xmallocz(len); + if (len > 0 && !undo_read(bi, ptr, len)) { + free(ptr); + return NULL; + } + return ptr; +} /* * If 'cpoptions' contains 'u': Undo the previous undo or redo (vi compatible). diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h index 2579f13b93..610adb4367 100644 --- a/src/nvim/undo_defs.h +++ b/src/nvim/undo_defs.h @@ -4,6 +4,7 @@ #include <time.h> // for time_t #include "nvim/pos.h" +#include "nvim/buffer_defs.h" /* Structure to store info about the Visual area. */ typedef struct { @@ -67,4 +68,10 @@ struct u_header { #define UH_CHANGED 0x01 /* b_changed flag before undo/after redo */ #define UH_EMPTYBUF 0x02 /* buffer was empty */ +/// Structure passed around between undofile functions. +typedef struct { + buf_T *bi_buf; + FILE *bi_fp; +} bufinfo_T; + #endif // NVIM_UNDO_DEFS_H |