File: //home/ubuntu/neovim/src/nvim/shada.c
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <uv.h>
#include "auto/config.h"
#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/cmdhist.h"
#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/decode.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/fileio.h"
#include "nvim/garray.h"
#include "nvim/garray_defs.h"
#include "nvim/gettext_defs.h"
#include "nvim/globals.h"
#include "nvim/hashtab.h"
#include "nvim/hashtab_defs.h"
#include "nvim/macros_defs.h"
#include "nvim/map_defs.h"
#include "nvim/mark.h"
#include "nvim/mark_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/msgpack_rpc/packer.h"
#include "nvim/msgpack_rpc/unpacker.h"
#include "nvim/normal_defs.h"
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/option_vars.h"
#include "nvim/os/fileio.h"
#include "nvim/os/fileio_defs.h"
#include "nvim/os/fs.h"
#include "nvim/os/fs_defs.h"
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
#include "nvim/os/time.h"
#include "nvim/os/time_defs.h"
#include "nvim/path.h"
#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
#include "nvim/search.h"
#include "nvim/shada.h"
#include "nvim/strings.h"
#include "nvim/types_defs.h"
#include "nvim/version.h"
#include "nvim/vim_defs.h"
#ifdef HAVE_BE64TOH
# define _BSD_SOURCE 1 // NOLINT(bugprone-reserved-identifier)
# define _DEFAULT_SOURCE 1 // NOLINT(bugprone-reserved-identifier)
# include ENDIAN_INCLUDE_FILE
#endif
#define SEARCH_KEY_MAGIC sm
#define SEARCH_KEY_SMARTCASE sc
#define SEARCH_KEY_HAS_LINE_OFFSET sl
#define SEARCH_KEY_PLACE_CURSOR_AT_END se
#define SEARCH_KEY_IS_LAST_USED su
#define SEARCH_KEY_IS_SUBSTITUTE_PATTERN ss
#define SEARCH_KEY_HIGHLIGHTED sh
#define SEARCH_KEY_OFFSET so
#define SEARCH_KEY_PAT sp
#define SEARCH_KEY_BACKWARD sb
#define REG_KEY_TYPE rt
#define REG_KEY_WIDTH rw
#define REG_KEY_CONTENTS rc
#define REG_KEY_UNNAMED ru
#define KEY_LNUM l
#define KEY_COL c
#define KEY_FILE f
#define KEY_NAME_CHAR n
// Error messages formerly used by viminfo code:
// E136: viminfo: Too many errors, skipping rest of file
// E137: Viminfo file is not writable: %s
// E138: Can't write viminfo file %s!
// E195: Cannot open ShaDa file for reading
// E574: Unknown register type %d
// E575: Illegal starting char
// E576: Missing '>'
// E577: Illegal register name
// E886: Can't rename viminfo file to %s!
// E929: Too many viminfo temp files, like %s!
// Now only six of them are used:
// E137: ShaDa file is not writeable (for pre-open checks)
// E929: All %s.tmp.X files exist, cannot write ShaDa file!
// RCERR (E576) for critical read errors.
// RNERR (E136) for various errors when renaming.
// RERR (E575) for various errors inside read ShaDa file.
// SERR (E886) for various “system” errors (always contains output of
// strerror)
// WERR (E574) for various ignorable write errors
/// Common prefix for all errors inside ShaDa file
///
/// I.e. errors occurred while parsing, but not system errors occurred while
/// reading.
#define RERR "E575: "
/// Common prefix for critical read errors
///
/// I.e. errors that make shada_read_next_item return kSDReadStatusNotShaDa.
#define RCERR "E576: "
/// Common prefix for all “system” errors
#define SERR "E886: "
/// Common prefix for all “rename” errors
#define RNERR "E136: "
/// Common prefix for all ignorable “write” errors
#define WERR "E574: "
/// Callback function for add_search_pattern
typedef void (*SearchPatternGetter)(SearchPattern *);
/// Possible ShaDa entry types
///
/// @warning Enum values are part of the API and must not be altered.
///
/// All values that are not in enum are ignored.
typedef enum {
kSDItemUnknown = -1, ///< Unknown item.
kSDItemMissing = 0, ///< Missing value. Should never appear in a file.
kSDItemHeader = 1, ///< Header. Present for debugging purposes.
kSDItemSearchPattern = 2, ///< Last search pattern (*not* history item).
///< Comes from user searches (e.g. when typing
///< "/pat") or :substitute command calls.
kSDItemSubString = 3, ///< Last substitute replacement string.
kSDItemHistoryEntry = 4, ///< History item.
kSDItemRegister = 5, ///< Register.
kSDItemVariable = 6, ///< Global variable.
kSDItemGlobalMark = 7, ///< Global mark definition.
kSDItemJump = 8, ///< Item from jump list.
kSDItemBufferList = 9, ///< Buffer list.
kSDItemLocalMark = 10, ///< Buffer-local mark.
kSDItemChange = 11, ///< Item from buffer change list.
} ShadaEntryType;
#define SHADA_LAST_ENTRY ((uint64_t)kSDItemChange)
/// Possible results when reading ShaDa file
typedef enum {
kSDReadStatusSuccess, ///< Reading was successful.
kSDReadStatusFinished, ///< Nothing more to read.
kSDReadStatusReadError, ///< Failed to read from file.
kSDReadStatusNotShaDa, ///< Input is most likely not a ShaDa file.
kSDReadStatusMalformed, ///< Error in the currently read item.
} ShaDaReadResult;
/// Possible results of shada_write function.
typedef enum {
kSDWriteSuccessful, ///< Writing was successful.
kSDWriteReadNotShada, ///< Writing was successful, but when reading it
///< attempted to read file that did not look like
///< a ShaDa file.
kSDWriteFailed, ///< Writing was not successful (e.g. because there
///< was no space left on device).
kSDWriteIgnError, ///< Writing resulted in a error which can be ignored
///< (e.g. when trying to dump a function reference or
///< self-referencing container in a variable).
} ShaDaWriteResult;
/// Flags for shada_read_next_item
enum SRNIFlags {
kSDReadHeader = (1 << kSDItemHeader), ///< Determines whether header should
///< be read (it is usually ignored).
kSDReadUndisableableData = (
(1 << kSDItemSearchPattern)
| (1 << kSDItemSubString)
| (1 << kSDItemJump)), ///< Data reading which cannot be disabled by
///< &shada or other options except for disabling
///< reading ShaDa as a whole.
kSDReadRegisters = (1 << kSDItemRegister), ///< Determines whether registers
///< should be read (may only be
///< disabled when writing, but
///< not when reading).
kSDReadHistory = (1 << kSDItemHistoryEntry), ///< Determines whether history
///< should be read (can only be
///< disabled by &history).
kSDReadVariables = (1 << kSDItemVariable), ///< Determines whether variables
///< should be read (disabled by
///< removing ! from &shada).
kSDReadBufferList = (1 << kSDItemBufferList), ///< Determines whether buffer
///< list should be read
///< (disabled by removing
///< % entry from &shada).
kSDReadUnknown = (1 << (SHADA_LAST_ENTRY + 1)), ///< Determines whether
///< unknown items should be
///< read (usually disabled).
kSDReadGlobalMarks = (1 << kSDItemGlobalMark), ///< Determines whether global
///< marks should be read. Can
///< only be disabled by
///< having f0 in &shada when
///< writing.
kSDReadLocalMarks = (1 << kSDItemLocalMark), ///< Determines whether local
///< marks should be read. Can
///< only be disabled by
///< disabling &shada or putting
///< '0 there. Is also used for
///< v:oldfiles.
kSDReadChanges = (1 << kSDItemChange), ///< Determines whether change list
///< should be read. Can only be
///< disabled by disabling &shada or
///< putting '0 there.
};
// Note: SRNIFlags enum name was created only to make it possible to reference
// it. This name is not actually used anywhere outside of the documentation.
/// Structure defining a single ShaDa file entry
typedef struct {
ShadaEntryType type;
Timestamp timestamp;
union {
Dictionary header;
struct shada_filemark {
char name;
pos_T mark;
char *fname;
} filemark;
Dict(_shada_search_pat) search_pattern;
struct history_item {
uint8_t histtype;
char *string;
char sep;
} history_item;
struct reg { // yankreg_T
char name;
MotionType type;
char **contents;
bool is_unnamed;
size_t contents_size;
size_t width;
} reg;
struct global_var {
char *name;
typval_T value;
} global_var;
struct {
uint64_t type;
char *contents;
size_t size;
} unknown_item;
struct sub_string {
char *sub;
} sub_string;
struct buffer_list {
size_t size;
struct buffer_list_buffer {
pos_T pos;
char *fname;
AdditionalData *additional_data;
} *buffers;
} buffer_list;
} data;
AdditionalData *additional_data;
} ShadaEntry;
/// One entry in sized linked list
typedef struct hm_llist_entry {
ShadaEntry data; ///< Entry data.
bool can_free_entry; ///< True if data can be freed.
struct hm_llist_entry *next; ///< Pointer to next entry or NULL.
struct hm_llist_entry *prev; ///< Pointer to previous entry or NULL.
} HMLListEntry;
/// Sized linked list structure for history merger
typedef struct {
HMLListEntry *entries; ///< Pointer to the start of the allocated array of
///< entries.
HMLListEntry *first; ///< First entry in the list (is not necessary start
///< of the array) or NULL.
HMLListEntry *last; ///< Last entry in the list or NULL.
HMLListEntry *free_entry; ///< Last free entry removed by hmll_remove.
HMLListEntry *last_free_entry; ///< Last unused element in entries array.
size_t size; ///< Number of allocated entries.
size_t num_entries; ///< Number of entries already used.
PMap(cstr_t) contained_entries; ///< Map all history entry strings to
///< corresponding entry pointers.
} HMLList;
typedef struct {
HMLList hmll;
bool do_merge;
bool reading;
const void *iter;
ShadaEntry last_hist_entry;
uint8_t history_type;
} HistoryMergerState;
/// ShadaEntry structure that knows whether it should be freed
typedef struct {
ShadaEntry data; ///< ShadaEntry data.
bool can_free_entry; ///< True if entry can be freed.
} PossiblyFreedShadaEntry;
/// Structure that holds one file marks.
typedef struct {
PossiblyFreedShadaEntry marks[NLOCALMARKS]; ///< All file marks.
PossiblyFreedShadaEntry changes[JUMPLISTSIZE]; ///< All file changes.
size_t changes_size; ///< Number of changes occupied.
ShadaEntry *additional_marks; ///< All marks with unknown names.
size_t additional_marks_size; ///< Size of the additional_marks array.
Timestamp greatest_timestamp; ///< Greatest timestamp among marks.
} FileMarks;
/// State structure used by shada_write
///
/// Before actually writing most of the data is read to this structure.
typedef struct {
HistoryMergerState hms[HIST_COUNT]; ///< Structures for history merging.
PossiblyFreedShadaEntry global_marks[NMARKS]; ///< Named global marks.
PossiblyFreedShadaEntry numbered_marks[EXTRA_MARKS]; ///< Numbered marks.
PossiblyFreedShadaEntry registers[NUM_SAVED_REGISTERS]; ///< All registers.
PossiblyFreedShadaEntry jumps[JUMPLISTSIZE]; ///< All dumped jumps.
size_t jumps_size; ///< Number of jumps occupied.
PossiblyFreedShadaEntry search_pattern; ///< Last search pattern.
PossiblyFreedShadaEntry sub_search_pattern; ///< Last s/ search pattern.
PossiblyFreedShadaEntry replacement; ///< Last s// replacement string.
Set(cstr_t) dumped_variables; ///< Names of already dumped variables.
PMap(cstr_t) file_marks; ///< All file marks.
} WriteMergerState;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "shada.c.generated.h"
#endif
#define DEF_SDE(name, attr, ...) \
[kSDItem##name] = { \
.timestamp = 0, \
.type = kSDItem##name, \
.additional_data = NULL, \
.data = { \
.attr = { __VA_ARGS__ } \
} \
}
#define DEFAULT_POS { 1, 0, 0 }
static const pos_T default_pos = DEFAULT_POS;
static const ShadaEntry sd_default_values[] = {
[kSDItemMissing] = { .type = kSDItemMissing, .timestamp = 0 },
DEF_SDE(Header, header, .size = 0),
DEF_SDE(SearchPattern, search_pattern,
.magic = true,
.smartcase = false,
.has_line_offset = false,
.place_cursor_at_end = false,
.offset = 0,
.is_last_used = true,
.is_substitute_pattern = false,
.highlighted = false,
.search_backward = false,
.pat = STRING_INIT),
DEF_SDE(SubString, sub_string, .sub = NULL),
DEF_SDE(HistoryEntry, history_item,
.histtype = HIST_CMD,
.string = NULL,
.sep = NUL),
DEF_SDE(Register, reg,
.name = NUL,
.type = kMTCharWise,
.contents = NULL,
.contents_size = 0,
.is_unnamed = false,
.width = 0),
DEF_SDE(Variable, global_var,
.name = NULL,
.value = { .v_type = VAR_UNKNOWN, .vval = { .v_string = NULL } }),
DEF_SDE(GlobalMark, filemark,
.name = '"',
.mark = DEFAULT_POS,
.fname = NULL),
DEF_SDE(Jump, filemark,
.name = NUL,
.mark = DEFAULT_POS,
.fname = NULL),
DEF_SDE(BufferList, buffer_list,
.size = 0,
.buffers = NULL),
DEF_SDE(LocalMark, filemark,
.name = '"',
.mark = DEFAULT_POS,
.fname = NULL),
DEF_SDE(Change, filemark,
.name = NUL,
.mark = DEFAULT_POS,
.fname = NULL),
};
#undef DEFAULT_POS
#undef DEF_SDE
/// Initialize new linked list
///
/// @param[out] hmll List to initialize.
/// @param[in] size Maximum size of the list.
static inline void hmll_init(HMLList *const hmll, const size_t size)
FUNC_ATTR_NONNULL_ALL
{
*hmll = (HMLList) {
.entries = xcalloc(size, sizeof(hmll->entries[0])),
.first = NULL,
.last = NULL,
.free_entry = NULL,
.size = size,
.num_entries = 0,
.contained_entries = MAP_INIT,
};
hmll->last_free_entry = hmll->entries;
}
/// Iterate over HMLList in forward direction
///
/// @param hmll Pointer to the list.
/// @param cur_entry Name of the variable to iterate over.
/// @param code Code to execute on each iteration.
///
/// @return `for` cycle header (use `HMLL_FORALL(hmll, cur_entry) {body}`).
#define HMLL_FORALL(hmll, cur_entry, code) \
for (HMLListEntry *(cur_entry) = (hmll)->first; (cur_entry) != NULL; \
(cur_entry) = (cur_entry)->next) { \
code \
} \
/// Remove entry from the linked list
///
/// @param hmll List to remove from.
/// @param hmll_entry Entry to remove.
static inline void hmll_remove(HMLList *const hmll, HMLListEntry *const hmll_entry)
FUNC_ATTR_NONNULL_ALL
{
if (hmll_entry == hmll->last_free_entry - 1) {
hmll->last_free_entry--;
} else {
assert(hmll->free_entry == NULL);
hmll->free_entry = hmll_entry;
}
ptr_t val = pmap_del(cstr_t)(&hmll->contained_entries,
hmll_entry->data.data.history_item.string, NULL);
assert(val);
(void)val;
if (hmll_entry->next == NULL) {
hmll->last = hmll_entry->prev;
} else {
hmll_entry->next->prev = hmll_entry->prev;
}
if (hmll_entry->prev == NULL) {
hmll->first = hmll_entry->next;
} else {
hmll_entry->prev->next = hmll_entry->next;
}
hmll->num_entries--;
if (hmll_entry->can_free_entry) {
shada_free_shada_entry(&hmll_entry->data);
}
}
/// Insert entry to the linked list
///
/// @param[out] hmll List to insert to.
/// @param[in] hmll_entry Entry to insert after or NULL if it is needed
/// to insert at the first entry.
/// @param[in] data Data to insert.
/// @param[in] can_free_entry True if data can be freed.
static inline void hmll_insert(HMLList *const hmll, HMLListEntry *hmll_entry, const ShadaEntry data,
const bool can_free_entry)
FUNC_ATTR_NONNULL_ARG(1)
{
if (hmll->num_entries == hmll->size) {
if (hmll_entry == hmll->first) {
hmll_entry = NULL;
}
assert(hmll->first != NULL);
hmll_remove(hmll, hmll->first);
}
HMLListEntry *target_entry;
if (hmll->free_entry == NULL) {
assert((size_t)(hmll->last_free_entry - hmll->entries)
== hmll->num_entries);
target_entry = hmll->last_free_entry++;
} else {
assert((size_t)(hmll->last_free_entry - hmll->entries) - 1
== hmll->num_entries);
target_entry = hmll->free_entry;
hmll->free_entry = NULL;
}
target_entry->data = data;
target_entry->can_free_entry = can_free_entry;
bool new_item = false;
ptr_t *val = pmap_put_ref(cstr_t)(&hmll->contained_entries, data.data.history_item.string,
NULL, &new_item);
if (new_item) {
*val = target_entry;
}
hmll->num_entries++;
target_entry->prev = hmll_entry;
if (hmll_entry == NULL) {
target_entry->next = hmll->first;
hmll->first = target_entry;
} else {
target_entry->next = hmll_entry->next;
hmll_entry->next = target_entry;
}
if (target_entry->next == NULL) {
hmll->last = target_entry;
} else {
target_entry->next->prev = target_entry;
}
}
/// Free linked list
///
/// @param[in] hmll List to free.
static inline void hmll_dealloc(HMLList *const hmll)
FUNC_ATTR_NONNULL_ALL
{
map_destroy(cstr_t, &hmll->contained_entries);
xfree(hmll->entries);
}
/// Wrapper for read that can be used when lseek cannot be used
///
/// E.g. when trying to read from a pipe.
///
/// @param[in,out] sd_reader File read.
/// @param[in] offset Amount of bytes to skip.
///
/// @return kSDReadStatusReadError, kSDReadStatusNotShaDa or
/// kSDReadStatusSuccess.
static ShaDaReadResult sd_reader_skip(FileDescriptor *const sd_reader, const size_t offset)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
const ptrdiff_t skip_bytes = file_skip(sd_reader, offset);
if (skip_bytes < 0) {
semsg(_(SERR "System error while skipping in ShaDa file: %s"),
os_strerror((int)skip_bytes));
return kSDReadStatusReadError;
} else if (skip_bytes != (ptrdiff_t)offset) {
assert(skip_bytes < (ptrdiff_t)offset);
if (file_eof(sd_reader)) {
semsg(_(RCERR "Error while reading ShaDa file: "
"last entry specified that it occupies %" PRIu64 " bytes, "
"but file ended earlier"),
(uint64_t)offset);
} else {
semsg(_(SERR "System error while skipping in ShaDa file: %s"),
_("too few bytes read"));
}
return kSDReadStatusNotShaDa;
}
return kSDReadStatusSuccess;
}
/// Wrapper for closing file descriptors
static void close_file(FileDescriptor *cookie)
{
const int error = file_close(cookie, !!p_fs);
if (error != 0) {
semsg(_(SERR "System error while closing ShaDa file: %s"),
os_strerror(error));
}
}
/// Check whether writing to shada file was disabled ("-i NONE" or "--clean").
///
/// @return true if it was disabled, false otherwise.
static bool shada_disabled(void)
FUNC_ATTR_PURE
{
return strequal(p_shadafile, "NONE");
}
/// Read ShaDa file
///
/// @param[in] file File to read or NULL to use default name.
/// @param[in] flags Flags, see ShaDaReadFileFlags enum.
///
/// @return FAIL if reading failed for some reason and OK otherwise.
static int shada_read_file(const char *const file, const int flags)
FUNC_ATTR_WARN_UNUSED_RESULT
{
if (shada_disabled()) {
return FAIL;
}
char *const fname = shada_filename(file);
FileDescriptor sd_reader;
int of_ret = file_open(&sd_reader, fname, kFileReadOnly, 0);
if (p_verbose > 1) {
verbose_enter();
smsg(0, _("Reading ShaDa file \"%s\"%s%s%s%s"),
fname,
(flags & kShaDaWantInfo) ? _(" info") : "",
(flags & kShaDaWantMarks) ? _(" marks") : "",
(flags & kShaDaGetOldfiles) ? _(" oldfiles") : "",
of_ret != 0 ? _(" FAILED") : "");
verbose_leave();
}
if (of_ret != 0) {
if (of_ret != UV_ENOENT || (flags & kShaDaMissingError)) {
semsg(_(SERR "System error while opening ShaDa file %s for reading: %s"),
fname, os_strerror(of_ret));
}
xfree(fname);
return FAIL;
}
xfree(fname);
shada_read(&sd_reader, flags);
close_file(&sd_reader);
return OK;
}
/// Wrapper for hist_iter() function which produces ShadaEntry values
///
/// @param[in] iter Current iteration state.
/// @param[in] history_type Type of the history (HIST_*).
/// @param[in] zero If true, then item is removed from instance
/// memory upon reading.
/// @param[out] hist Location where iteration results should be saved.
///
/// @return Next iteration state.
static const void *shada_hist_iter(const void *const iter, const uint8_t history_type,
const bool zero, ShadaEntry *const hist)
FUNC_ATTR_NONNULL_ARG(4) FUNC_ATTR_WARN_UNUSED_RESULT
{
histentry_T hist_he;
const void *const ret = hist_iter(iter, history_type, zero, &hist_he);
if (hist_he.hisstr == NULL) {
*hist = (ShadaEntry) { .type = kSDItemMissing };
} else {
*hist = (ShadaEntry) {
.type = kSDItemHistoryEntry,
.timestamp = hist_he.timestamp,
.data = {
.history_item = {
.histtype = history_type,
.string = hist_he.hisstr,
.sep = (char)(history_type == HIST_SEARCH
? hist_he.hisstr[strlen(hist_he.hisstr) + 1]
: 0),
}
},
.additional_data = hist_he.additional_data,
};
}
return ret;
}
/// Insert history entry
///
/// Inserts history entry at the end of the ring buffer (may insert earlier
/// according to the timestamp). If entry was already in the ring buffer
/// existing entry will be removed unless it has greater timestamp.
///
/// Before the new entry entries from the current Neovim history will be
/// inserted unless `do_iter` argument is false.
///
/// @param[in,out] hms_p Ring buffer and associated structures.
/// @param[in] entry Inserted entry.
/// @param[in] do_iter Determines whether Neovim own history should
/// be used. Must be true only if inserting
/// entry from current Neovim history.
/// @param[in] can_free_entry True if entry can be freed.
static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry entry, const bool do_iter,
const bool can_free_entry)
FUNC_ATTR_NONNULL_ALL
{
if (do_iter) {
while (hms_p->last_hist_entry.type != kSDItemMissing
&& hms_p->last_hist_entry.timestamp < entry.timestamp) {
hms_insert(hms_p, hms_p->last_hist_entry, false, hms_p->reading);
if (hms_p->iter == NULL) {
hms_p->last_hist_entry.type = kSDItemMissing;
break;
}
hms_p->iter = shada_hist_iter(hms_p->iter, hms_p->history_type,
hms_p->reading, &hms_p->last_hist_entry);
}
}
HMLList *const hmll = &hms_p->hmll;
cstr_t *key_alloc = NULL;
ptr_t *val = pmap_ref(cstr_t)(&hms_p->hmll.contained_entries, entry.data.history_item.string,
&key_alloc);
if (val) {
HMLListEntry *const existing_entry = *val;
if (entry.timestamp > existing_entry->data.timestamp) {
hmll_remove(hmll, existing_entry);
} else if (!do_iter && entry.timestamp == existing_entry->data.timestamp) {
// Prefer entry from the current Neovim instance.
if (existing_entry->can_free_entry) {
shada_free_shada_entry(&existing_entry->data);
}
existing_entry->data = entry;
existing_entry->can_free_entry = can_free_entry;
// Previous key was freed above, as part of freeing the ShaDa entry.
*key_alloc = entry.data.history_item.string;
return;
} else {
return;
}
}
HMLListEntry *insert_after;
// Iterate over HMLList in backward direction
for (insert_after = hmll->last; insert_after != NULL; insert_after = insert_after->prev) {
if (insert_after->data.timestamp <= entry.timestamp) {
break;
}
}
hmll_insert(hmll, insert_after, entry, can_free_entry);
}
/// Initialize the history merger
///
/// @param[out] hms_p Structure to be initialized.
/// @param[in] history_type History type (one of HIST_\* values).
/// @param[in] num_elements Number of elements in the result.
/// @param[in] do_merge Prepare structure for merging elements.
/// @param[in] reading If true, then merger is reading history for use
/// in Neovim.
static inline void hms_init(HistoryMergerState *const hms_p, const uint8_t history_type,
const size_t num_elements, const bool do_merge, const bool reading)
FUNC_ATTR_NONNULL_ALL
{
hmll_init(&hms_p->hmll, num_elements);
hms_p->do_merge = do_merge;
hms_p->reading = reading;
hms_p->iter = shada_hist_iter(NULL, history_type, hms_p->reading,
&hms_p->last_hist_entry);
hms_p->history_type = history_type;
}
/// Merge in all remaining Neovim own history entries
///
/// @param[in,out] hms_p Merger structure into which history should be
/// inserted.
static inline void hms_insert_whole_neovim_history(HistoryMergerState *const hms_p)
FUNC_ATTR_NONNULL_ALL
{
while (hms_p->last_hist_entry.type != kSDItemMissing) {
hms_insert(hms_p, hms_p->last_hist_entry, false, hms_p->reading);
if (hms_p->iter == NULL) {
break;
}
hms_p->iter = shada_hist_iter(hms_p->iter, hms_p->history_type,
hms_p->reading, &hms_p->last_hist_entry);
}
}
/// Convert merger structure to Neovim internal structure for history
///
/// @param[in] hms_p Converted merger structure.
/// @param[out] hist_array Array with the results.
/// @param[out] new_hisidx New last history entry index.
/// @param[out] new_hisnum Amount of history items in merger structure.
static inline void hms_to_he_array(const HistoryMergerState *const hms_p,
histentry_T *const hist_array, int *const new_hisidx,
int *const new_hisnum)
FUNC_ATTR_NONNULL_ALL
{
histentry_T *hist = hist_array;
HMLL_FORALL(&hms_p->hmll, cur_entry, {
hist->timestamp = cur_entry->data.timestamp;
hist->hisnum = (int)(hist - hist_array) + 1;
hist->hisstr = cur_entry->data.data.history_item.string;
hist->additional_data = cur_entry->data.additional_data;
hist++;
})
*new_hisnum = (int)(hist - hist_array);
*new_hisidx = *new_hisnum - 1;
}
/// Free history merger structure
///
/// @param[in] hms_p Structure to be freed.
static inline void hms_dealloc(HistoryMergerState *const hms_p)
FUNC_ATTR_NONNULL_ALL
{
hmll_dealloc(&hms_p->hmll);
}
/// Iterate over all history entries in history merger, in order
///
/// @param[in] hms_p Merger structure to iterate over.
/// @param[out] cur_entry Name of the iterator variable.
/// @param code Code to execute on each iteration.
///
/// @return for cycle header. Use `HMS_ITER(hms_p, cur_entry) {body}`.
#define HMS_ITER(hms_p, cur_entry, code) \
HMLL_FORALL(&((hms_p)->hmll), cur_entry, code)
/// Iterate over global variables
///
/// @warning No modifications to global variable dictionary must be performed
/// while iteration is in progress.
///
/// @param[in] iter Iterator. Pass NULL to start iteration.
/// @param[out] name Variable name.
/// @param[out] rettv Variable value.
///
/// @return Pointer that needs to be passed to next `var_shada_iter` invocation
/// or NULL to indicate that iteration is over.
static const void *var_shada_iter(const void *const iter, const char **const name, typval_T *rettv,
var_flavour_T flavour)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(2, 3)
{
const hashitem_T *hi;
const hashitem_T *hifirst = globvarht.ht_array;
const size_t hinum = (size_t)globvarht.ht_mask + 1;
*name = NULL;
if (iter == NULL) {
hi = globvarht.ht_array;
while ((size_t)(hi - hifirst) < hinum
&& (HASHITEM_EMPTY(hi)
|| !(var_flavour(hi->hi_key) & flavour))) {
hi++;
}
if ((size_t)(hi - hifirst) == hinum) {
return NULL;
}
} else {
hi = (const hashitem_T *)iter;
}
*name = TV_DICT_HI2DI(hi)->di_key;
tv_copy(&TV_DICT_HI2DI(hi)->di_tv, rettv);
while ((size_t)(++hi - hifirst) < hinum) {
if (!HASHITEM_EMPTY(hi) && (var_flavour(hi->hi_key) & flavour)) {
return hi;
}
}
return NULL;
}
/// Find buffer for given buffer name (cached)
///
/// @param[in,out] fname_bufs Cache containing fname to buffer mapping.
/// @param[in] fname File name to find.
///
/// @return Pointer to the buffer or NULL.
static buf_T *find_buffer(PMap(cstr_t) *const fname_bufs, const char *const fname)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
cstr_t *key_alloc = NULL;
bool new_item = false;
buf_T **ref = (buf_T **)pmap_put_ref(cstr_t)(fname_bufs, fname, &key_alloc, &new_item);
if (new_item) {
*key_alloc = xstrdup(fname);
} else {
return *ref; // item already existed (can be a NULL value)
}
FOR_ALL_BUFFERS(buf) {
if (buf->b_ffname != NULL) {
if (path_fnamecmp(fname, buf->b_ffname) == 0) {
*ref = buf;
return buf;
}
}
}
*ref = NULL;
return NULL;
}
/// Compare two marks
static inline bool marks_equal(const pos_T a, const pos_T b)
{
return (a.lnum == b.lnum) && (a.col == b.col);
}
#define MERGE_JUMPS(jumps_size, jumps, jumps_type, timestamp_attr, mark_attr, \
entry, fname_cond, free_func, fin_func, \
idxadj_func, afterfree_func) \
do { \
const int jl_len = (int)(jumps_size); \
int i; \
for (i = jl_len; i > 0; i--) { \
const jumps_type jl_entry = (jumps)[i - 1]; \
if (jl_entry.timestamp_attr <= (entry).timestamp) { \
if (marks_equal(jl_entry.mark_attr, (entry).data.filemark.mark) \
&& (fname_cond)) { \
i = -1; \
} \
break; \
} \
} \
if (i > 0) { \
if (jl_len == JUMPLISTSIZE) { \
free_func((jumps)[0]); \
i--; \
if (i > 0) { \
memmove(&(jumps)[0], &(jumps)[1], sizeof((jumps)[1]) * (size_t)i); \
} \
} else if (i != jl_len) { \
memmove(&(jumps)[i + 1], &(jumps)[i], \
sizeof((jumps)[0]) * (size_t)(jl_len - i)); \
} \
} else if (i == 0) { \
if (jl_len == JUMPLISTSIZE) { \
i = -1; \
} else if (jl_len > 0) { \
memmove(&(jumps)[1], &(jumps)[0], sizeof((jumps)[0]) * (size_t)jl_len); \
} \
} \
if (i != -1) { \
(jumps)[i] = fin_func(entry); \
if (jl_len < JUMPLISTSIZE) { \
(jumps_size)++; \
} \
idxadj_func(i); \
} else { \
shada_free_shada_entry(&(entry)); \
afterfree_func(entry); \
} \
} while (0)
/// Read data from ShaDa file
///
/// @param[in] sd_reader Structure containing file reader definition.
/// @param[in] flags What to read, see ShaDaReadFileFlags enum.
static void shada_read(FileDescriptor *const sd_reader, const int flags)
FUNC_ATTR_NONNULL_ALL
{
list_T *oldfiles_list = get_vim_var_list(VV_OLDFILES);
const bool force = flags & kShaDaForceit;
const bool get_old_files = (flags & (kShaDaGetOldfiles | kShaDaForceit)
&& (force || tv_list_len(oldfiles_list) == 0));
const bool want_marks = flags & kShaDaWantMarks;
const unsigned srni_flags =
(unsigned)(
(flags & kShaDaWantInfo
? (kSDReadUndisableableData
| kSDReadRegisters
| kSDReadGlobalMarks
| (p_hi ? kSDReadHistory : 0)
| (find_shada_parameter('!') != NULL
? kSDReadVariables
: 0)
| (find_shada_parameter('%') != NULL
&& ARGCOUNT == 0
? kSDReadBufferList
: 0))
: 0)
| (want_marks && get_shada_parameter('\'') > 0
? kSDReadLocalMarks | kSDReadChanges
: 0)
| (get_old_files
? kSDReadLocalMarks
: 0));
if (srni_flags == 0) {
// Nothing to do.
return;
}
HistoryMergerState hms[HIST_COUNT];
if (srni_flags & kSDReadHistory) {
for (int i = 0; i < HIST_COUNT; i++) {
hms_init(&hms[i], (uint8_t)i, (size_t)p_hi, true, true);
}
}
ShadaEntry cur_entry;
Set(ptr_t) cl_bufs = SET_INIT;
PMap(cstr_t) fname_bufs = MAP_INIT;
Set(cstr_t) oldfiles_set = SET_INIT;
if (get_old_files && (oldfiles_list == NULL || force)) {
oldfiles_list = tv_list_alloc(kListLenUnknown);
set_vim_var_list(VV_OLDFILES, oldfiles_list);
}
ShaDaReadResult srni_ret;
while ((srni_ret = shada_read_next_item(sd_reader, &cur_entry, srni_flags, 0))
!= kSDReadStatusFinished) {
switch (srni_ret) {
case kSDReadStatusSuccess:
break;
case kSDReadStatusFinished:
// Should be handled by the while condition.
abort();
case kSDReadStatusNotShaDa:
case kSDReadStatusReadError:
goto shada_read_main_cycle_end;
case kSDReadStatusMalformed:
continue;
}
switch (cur_entry.type) {
case kSDItemMissing:
abort();
case kSDItemUnknown:
break;
case kSDItemHeader:
shada_free_shada_entry(&cur_entry);
break;
case kSDItemSearchPattern:
if (!force) {
SearchPattern pat;
if (cur_entry.data.search_pattern.is_substitute_pattern) {
get_substitute_pattern(&pat);
} else {
get_search_pattern(&pat);
}
if (pat.pat != NULL && pat.timestamp >= cur_entry.timestamp) {
shada_free_shada_entry(&cur_entry);
break;
}
}
SearchPattern spat = (SearchPattern) {
.magic = cur_entry.data.search_pattern.magic,
.no_scs = !cur_entry.data.search_pattern.smartcase,
.off = {
.dir = cur_entry.data.search_pattern.search_backward ? '?' : '/',
.line = cur_entry.data.search_pattern.has_line_offset,
.end = cur_entry.data.search_pattern.place_cursor_at_end,
.off = cur_entry.data.search_pattern.offset,
},
.pat = cur_entry.data.search_pattern.pat.data,
.patlen = cur_entry.data.search_pattern.pat.size,
.additional_data = cur_entry.additional_data,
.timestamp = cur_entry.timestamp,
};
if (cur_entry.data.search_pattern.is_substitute_pattern) {
set_substitute_pattern(spat);
} else {
set_search_pattern(spat);
}
if (cur_entry.data.search_pattern.is_last_used) {
set_last_used_pattern(cur_entry.data.search_pattern.is_substitute_pattern);
set_no_hlsearch(!cur_entry.data.search_pattern.highlighted);
}
// Do not free shada entry: its allocated memory was saved above.
break;
case kSDItemSubString:
if (!force) {
SubReplacementString sub;
sub_get_replacement(&sub);
if (sub.sub != NULL && sub.timestamp >= cur_entry.timestamp) {
shada_free_shada_entry(&cur_entry);
break;
}
}
sub_set_replacement((SubReplacementString) {
.sub = cur_entry.data.sub_string.sub,
.timestamp = cur_entry.timestamp,
.additional_data = cur_entry.additional_data,
});
// Without using regtilde and without / &cpo flag previous substitute
// string is close to useless: you can only use it with :& or :~ and
// that’s all because s//~ is not available until the first call to
// regtilde. Vim was not calling this for some reason.
regtilde(cur_entry.data.sub_string.sub, magic_isset(), false);
// Do not free shada entry: its allocated memory was saved above.
break;
case kSDItemHistoryEntry:
if (cur_entry.data.history_item.histtype >= HIST_COUNT) {
shada_free_shada_entry(&cur_entry);
break;
}
hms_insert(hms + cur_entry.data.history_item.histtype, cur_entry, true,
true);
// Do not free shada entry: its allocated memory was saved above.
break;
case kSDItemRegister:
if (cur_entry.data.reg.type != kMTCharWise
&& cur_entry.data.reg.type != kMTLineWise
&& cur_entry.data.reg.type != kMTBlockWise) {
shada_free_shada_entry(&cur_entry);
break;
}
if (!force) {
const yankreg_T *const reg = op_reg_get(cur_entry.data.reg.name);
if (reg == NULL || reg->timestamp >= cur_entry.timestamp) {
shada_free_shada_entry(&cur_entry);
break;
}
}
if (!op_reg_set(cur_entry.data.reg.name, (yankreg_T) {
.y_array = cur_entry.data.reg.contents,
.y_size = cur_entry.data.reg.contents_size,
.y_type = cur_entry.data.reg.type,
.y_width = (colnr_T)cur_entry.data.reg.width,
.timestamp = cur_entry.timestamp,
.additional_data = cur_entry.additional_data,
}, cur_entry.data.reg.is_unnamed)) {
shada_free_shada_entry(&cur_entry);
}
// Do not free shada entry: its allocated memory was saved above.
break;
case kSDItemVariable:
var_set_global(cur_entry.data.global_var.name,
cur_entry.data.global_var.value);
cur_entry.data.global_var.value.v_type = VAR_UNKNOWN;
shada_free_shada_entry(&cur_entry);
break;
case kSDItemJump:
case kSDItemGlobalMark: {
buf_T *buf = find_buffer(&fname_bufs, cur_entry.data.filemark.fname);
if (buf != NULL) {
XFREE_CLEAR(cur_entry.data.filemark.fname);
}
xfmark_T fm = (xfmark_T) {
.fname = buf == NULL ? cur_entry.data.filemark.fname : NULL,
.fmark = {
.mark = cur_entry.data.filemark.mark,
.fnum = (buf == NULL ? 0 : buf->b_fnum),
.timestamp = cur_entry.timestamp,
.view = INIT_FMARKV,
.additional_data = cur_entry.additional_data,
},
};
if (cur_entry.type == kSDItemGlobalMark) {
if (!mark_set_global(cur_entry.data.filemark.name, fm, !force)) {
shada_free_shada_entry(&cur_entry);
break;
}
} else {
#define SDE_TO_XFMARK(entry) fm
#define ADJUST_IDX(i) \
if (curwin->w_jumplistidx >= (i) \
&& curwin->w_jumplistidx + 1 <= curwin->w_jumplistlen) { \
curwin->w_jumplistidx++; \
}
#define DUMMY_AFTERFREE(entry)
MERGE_JUMPS(curwin->w_jumplistlen, curwin->w_jumplist, xfmark_T,
fmark.timestamp, fmark.mark, cur_entry,
(buf == NULL
? (jl_entry.fname != NULL
&& strcmp(fm.fname, jl_entry.fname) == 0)
: fm.fmark.fnum == jl_entry.fmark.fnum),
free_xfmark, SDE_TO_XFMARK, ADJUST_IDX, DUMMY_AFTERFREE);
#undef SDE_TO_XFMARK
#undef ADJUST_IDX
#undef DUMMY_AFTERFREE
}
// Do not free shada entry: its allocated memory was saved above.
break;
}
case kSDItemBufferList:
for (size_t i = 0; i < cur_entry.data.buffer_list.size; i++) {
char *const sfname =
path_try_shorten_fname(cur_entry.data.buffer_list.buffers[i].fname);
buf_T *const buf =
buflist_new(cur_entry.data.buffer_list.buffers[i].fname, sfname, 0, BLN_LISTED);
if (buf != NULL) {
fmarkv_T view = INIT_FMARKV;
RESET_FMARK(&buf->b_last_cursor,
cur_entry.data.buffer_list.buffers[i].pos, 0, view);
buflist_setfpos(buf, curwin, buf->b_last_cursor.mark.lnum,
buf->b_last_cursor.mark.col, false);
xfree(buf->additional_data);
buf->additional_data = cur_entry.data.buffer_list.buffers[i].additional_data;
cur_entry.data.buffer_list.buffers[i].additional_data = NULL;
}
}
shada_free_shada_entry(&cur_entry);
break;
case kSDItemChange:
case kSDItemLocalMark: {
if (get_old_files && !set_has(cstr_t, &oldfiles_set, cur_entry.data.filemark.fname)) {
char *fname = cur_entry.data.filemark.fname;
if (want_marks) {
// Do not bother with allocating memory for the string if already
// allocated string from cur_entry can be used. It cannot be used if
// want_marks is set because this way it may be used for a mark.
fname = xstrdup(fname);
}
set_put(cstr_t, &oldfiles_set, fname);
tv_list_append_allocated_string(oldfiles_list, fname);
if (!want_marks) {
// Avoid free because this string was already used.
cur_entry.data.filemark.fname = NULL;
}
}
if (!want_marks) {
shada_free_shada_entry(&cur_entry);
break;
}
buf_T *buf = find_buffer(&fname_bufs, cur_entry.data.filemark.fname);
if (buf == NULL) {
shada_free_shada_entry(&cur_entry);
break;
}
const fmark_T fm = (fmark_T) {
.mark = cur_entry.data.filemark.mark,
.fnum = 0,
.timestamp = cur_entry.timestamp,
.view = INIT_FMARKV,
.additional_data = cur_entry.additional_data,
};
if (cur_entry.type == kSDItemLocalMark) {
if (!mark_set_local(cur_entry.data.filemark.name, buf, fm, !force)) {
shada_free_shada_entry(&cur_entry);
break;
}
} else {
set_put(ptr_t, &cl_bufs, buf);
#define SDE_TO_FMARK(entry) fm
#define AFTERFREE(entry) (entry).data.filemark.fname = NULL
#define DUMMY_IDX_ADJ(i)
MERGE_JUMPS(buf->b_changelistlen, buf->b_changelist, fmark_T,
timestamp, mark, cur_entry, true,
free_fmark, SDE_TO_FMARK, DUMMY_IDX_ADJ, AFTERFREE);
#undef SDE_TO_FMARK
#undef AFTERFREE
#undef DUMMY_IDX_ADJ
}
// Do not free shada entry: except for fname, its allocated memory (i.e.
// additional_data attribute contents if non-NULL) was saved above.
xfree(cur_entry.data.filemark.fname);
break;
}
}
}
shada_read_main_cycle_end:
// Warning: shada_hist_iter returns ShadaEntry elements which use strings from
// original history list. This means that once such entry is removed
// from the history Neovim array will no longer be valid. To reduce
// amount of memory allocations ShaDa file reader allocates enough
// memory for the history string itself and separator character which
// may be assigned right away.
if (srni_flags & kSDReadHistory) {
for (int i = 0; i < HIST_COUNT; i++) {
hms_insert_whole_neovim_history(&hms[i]);
clr_history(i);
int *new_hisidx;
int *new_hisnum;
histentry_T *hist = hist_get_array((uint8_t)i, &new_hisidx, &new_hisnum);
if (hist != NULL) {
hms_to_he_array(&hms[i], hist, new_hisidx, new_hisnum);
}
hms_dealloc(&hms[i]);
}
}
if (cl_bufs.h.n_occupied) {
FOR_ALL_TAB_WINDOWS(tp, wp) {
(void)tp;
if (set_has(ptr_t, &cl_bufs, wp->w_buffer)) {
wp->w_changelistidx = wp->w_buffer->b_changelistlen;
}
}
}
set_destroy(ptr_t, &cl_bufs);
const char *key;
map_foreach_key(&fname_bufs, key, {
xfree((char *)key);
})
map_destroy(cstr_t, &fname_bufs);
set_destroy(cstr_t, &oldfiles_set);
}
/// Default shada file location: cached path
static char *default_shada_file = NULL;
/// Get the default ShaDa file
static const char *shada_get_default_file(void)
FUNC_ATTR_WARN_UNUSED_RESULT
{
if (default_shada_file == NULL) {
char *shada_dir = stdpaths_user_state_subpath("shada", 0, false);
default_shada_file = concat_fnames_realloc(shada_dir, "main.shada", true);
}
return default_shada_file;
}
/// Get the ShaDa file name to use
///
/// If "file" is given and not empty, use it (has already been expanded by
/// cmdline functions). Otherwise use "-i file_name", value from 'shada' or the
/// default, and expand environment variables.
///
/// @param[in] file Forced file name or NULL.
///
/// @return An allocated string containing shada file name.
static char *shada_filename(const char *file)
FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
{
if (file == NULL || *file == NUL) {
if (p_shadafile != NULL && *p_shadafile != NUL) {
file = p_shadafile;
} else {
if ((file = find_shada_parameter('n')) == NULL || *file == NUL) {
file = shada_get_default_file();
}
// XXX It used to be one level lower, so that whatever is in
// `p_shadafile` was expanded. I intentionally moved it here
// because various expansions must have already be done by the shell.
// If shell is not performing them then they should be done in main.c
// where arguments are parsed, *not here*.
expand_env((char *)file, &(NameBuff[0]), MAXPATHL);
file = &(NameBuff[0]);
}
}
return xstrdup(file);
}
#define KEY_NAME_(s) #s
#define PACK_KEY(s) mpack_str(STATIC_CSTR_AS_STRING(KEY_NAME_(s)), &sbuf);
#define KEY_NAME(s) KEY_NAME_(s)
#define SHADA_MPACK_FREE_SPACE (4 * MPACK_ITEM_SIZE)
static void shada_check_buffer(PackerBuffer *packer)
{
if (mpack_remaining(packer) < SHADA_MPACK_FREE_SPACE) {
packer->packer_flush(packer);
}
}
static uint32_t additional_data_len(AdditionalData *src)
{
return src ? src->nitems : 0;
}
static void dump_additional_data(AdditionalData *src, PackerBuffer *sbuf)
{
if (src != NULL) {
mpack_raw(src->data, src->nbytes, sbuf);
}
}
/// Write single ShaDa entry
///
/// @param[in] packer Packer used to write entry.
/// @param[in] entry Entry written.
/// @param[in] max_kbyte Maximum size of an item in KiB. Zero means no
/// restrictions.
///
/// @return kSDWriteSuccessful, kSDWriteFailed or kSDWriteIgnError.
static ShaDaWriteResult shada_pack_entry(PackerBuffer *const packer, ShadaEntry entry,
const size_t max_kbyte)
FUNC_ATTR_NONNULL_ALL
{
ShaDaWriteResult ret = kSDWriteFailed;
PackerBuffer sbuf = packer_string_buffer();
#define CHECK_DEFAULT(entry, attr) \
(sd_default_values[(entry).type].data.attr == (entry).data.attr)
#define ONE_IF_NOT_DEFAULT(entry, attr) \
((uint32_t)(!CHECK_DEFAULT(entry, attr)))
#define PACK_BOOL(entry, name, attr) \
do { \
if (!CHECK_DEFAULT(entry, search_pattern.attr)) { \
PACK_KEY(name); \
mpack_bool(&sbuf.ptr, !sd_default_values[(entry).type].data.search_pattern.attr); \
} \
} while (0)
shada_check_buffer(&sbuf);
switch (entry.type) {
case kSDItemMissing:
abort();
case kSDItemUnknown:
mpack_raw(entry.data.unknown_item.contents, entry.data.unknown_item.size, &sbuf);
break;
case kSDItemHistoryEntry: {
const bool is_hist_search =
entry.data.history_item.histtype == HIST_SEARCH;
uint32_t arr_size = (2 + (uint32_t)is_hist_search
+ additional_data_len(entry.additional_data));
mpack_array(&sbuf.ptr, arr_size);
mpack_uint(&sbuf.ptr, entry.data.history_item.histtype);
mpack_bin(cstr_as_string(entry.data.history_item.string), &sbuf);
if (is_hist_search) {
mpack_uint(&sbuf.ptr, (uint8_t)entry.data.history_item.sep);
}
dump_additional_data(entry.additional_data, &sbuf);
break;
}
case kSDItemVariable: {
bool is_blob = (entry.data.global_var.value.v_type == VAR_BLOB);
uint32_t arr_size = 2 + (is_blob ? 1 : 0) + additional_data_len(entry.additional_data);
mpack_array(&sbuf.ptr, arr_size);
const String varname = cstr_as_string(entry.data.global_var.name);
mpack_bin(varname, &sbuf);
char vardesc[256] = "variable g:";
memcpy(&vardesc[sizeof("variable g:") - 1], varname.data,
varname.size + 1);
if (encode_vim_to_msgpack(&sbuf, &entry.data.global_var.value, vardesc)
== FAIL) {
ret = kSDWriteIgnError;
semsg(_(WERR "Failed to write variable %s"),
entry.data.global_var.name);
goto shada_pack_entry_error;
}
if (is_blob) {
mpack_check_buffer(&sbuf);
mpack_integer(&sbuf.ptr, VAR_TYPE_BLOB);
}
dump_additional_data(entry.additional_data, &sbuf);
break;
}
case kSDItemSubString: {
uint32_t arr_size = 1 + additional_data_len(entry.additional_data);
mpack_array(&sbuf.ptr, arr_size);
mpack_bin(cstr_as_string(entry.data.sub_string.sub), &sbuf);
dump_additional_data(entry.additional_data, &sbuf);
break;
}
case kSDItemSearchPattern: {
uint32_t entry_map_size = (1 // Search pattern is always present
+ ONE_IF_NOT_DEFAULT(entry, search_pattern.magic)
+ ONE_IF_NOT_DEFAULT(entry, search_pattern.is_last_used)
+ ONE_IF_NOT_DEFAULT(entry, search_pattern.smartcase)
+ ONE_IF_NOT_DEFAULT(entry, search_pattern.has_line_offset)
+ ONE_IF_NOT_DEFAULT(entry, search_pattern.place_cursor_at_end)
+ ONE_IF_NOT_DEFAULT(entry,
search_pattern.is_substitute_pattern)
+ ONE_IF_NOT_DEFAULT(entry, search_pattern.highlighted)
+ ONE_IF_NOT_DEFAULT(entry, search_pattern.offset)
+ ONE_IF_NOT_DEFAULT(entry, search_pattern.search_backward)
+ additional_data_len(entry.additional_data));
mpack_map(&sbuf.ptr, entry_map_size);
PACK_KEY(SEARCH_KEY_PAT);
mpack_bin(entry.data.search_pattern.pat, &sbuf);
PACK_BOOL(entry, SEARCH_KEY_MAGIC, magic);
PACK_BOOL(entry, SEARCH_KEY_IS_LAST_USED, is_last_used);
PACK_BOOL(entry, SEARCH_KEY_SMARTCASE, smartcase);
PACK_BOOL(entry, SEARCH_KEY_HAS_LINE_OFFSET, has_line_offset);
PACK_BOOL(entry, SEARCH_KEY_PLACE_CURSOR_AT_END, place_cursor_at_end);
PACK_BOOL(entry, SEARCH_KEY_IS_SUBSTITUTE_PATTERN, is_substitute_pattern);
PACK_BOOL(entry, SEARCH_KEY_HIGHLIGHTED, highlighted);
PACK_BOOL(entry, SEARCH_KEY_BACKWARD, search_backward);
if (!CHECK_DEFAULT(entry, search_pattern.offset)) {
PACK_KEY(SEARCH_KEY_OFFSET);
mpack_integer(&sbuf.ptr, entry.data.search_pattern.offset);
}
#undef PACK_BOOL
dump_additional_data(entry.additional_data, &sbuf);
break;
}
case kSDItemChange:
case kSDItemGlobalMark:
case kSDItemLocalMark:
case kSDItemJump: {
size_t entry_map_size = (1 // File name
+ ONE_IF_NOT_DEFAULT(entry, filemark.mark.lnum)
+ ONE_IF_NOT_DEFAULT(entry, filemark.mark.col)
+ ONE_IF_NOT_DEFAULT(entry, filemark.name)
+ additional_data_len(entry.additional_data));
mpack_map(&sbuf.ptr, (uint32_t)entry_map_size);
PACK_KEY(KEY_FILE);
mpack_bin(cstr_as_string(entry.data.filemark.fname), &sbuf);
if (!CHECK_DEFAULT(entry, filemark.mark.lnum)) {
PACK_KEY(KEY_LNUM);
mpack_integer(&sbuf.ptr, entry.data.filemark.mark.lnum);
}
if (!CHECK_DEFAULT(entry, filemark.mark.col)) {
PACK_KEY(KEY_COL);
mpack_integer(&sbuf.ptr, entry.data.filemark.mark.col);
}
assert(entry.type == kSDItemJump || entry.type == kSDItemChange
? CHECK_DEFAULT(entry, filemark.name)
: true);
if (!CHECK_DEFAULT(entry, filemark.name)) {
PACK_KEY(KEY_NAME_CHAR);
mpack_uint(&sbuf.ptr, (uint8_t)entry.data.filemark.name);
}
dump_additional_data(entry.additional_data, &sbuf);
break;
}
case kSDItemRegister: {
uint32_t entry_map_size = (2 // Register contents and name
+ ONE_IF_NOT_DEFAULT(entry, reg.type)
+ ONE_IF_NOT_DEFAULT(entry, reg.width)
+ ONE_IF_NOT_DEFAULT(entry, reg.is_unnamed)
+ additional_data_len(entry.additional_data));
mpack_map(&sbuf.ptr, entry_map_size);
PACK_KEY(REG_KEY_CONTENTS);
mpack_array(&sbuf.ptr, (uint32_t)entry.data.reg.contents_size);
for (size_t i = 0; i < entry.data.reg.contents_size; i++) {
mpack_bin(cstr_as_string(entry.data.reg.contents[i]), &sbuf);
}
PACK_KEY(KEY_NAME_CHAR);
mpack_uint(&sbuf.ptr, (uint8_t)entry.data.reg.name);
if (!CHECK_DEFAULT(entry, reg.type)) {
PACK_KEY(REG_KEY_TYPE);
mpack_uint(&sbuf.ptr, (uint8_t)entry.data.reg.type);
}
if (!CHECK_DEFAULT(entry, reg.width)) {
PACK_KEY(REG_KEY_WIDTH);
mpack_uint64(&sbuf.ptr, (uint64_t)entry.data.reg.width);
}
if (!CHECK_DEFAULT(entry, reg.is_unnamed)) {
PACK_KEY(REG_KEY_UNNAMED);
mpack_bool(&sbuf.ptr, entry.data.reg.is_unnamed);
}
dump_additional_data(entry.additional_data, &sbuf);
break;
}
case kSDItemBufferList:
mpack_array(&sbuf.ptr, (uint32_t)entry.data.buffer_list.size);
for (size_t i = 0; i < entry.data.buffer_list.size; i++) {
size_t entry_map_size = (1 // Buffer name
+ (size_t)(entry.data.buffer_list.buffers[i].pos.lnum
!= default_pos.lnum)
+ (size_t)(entry.data.buffer_list.buffers[i].pos.col
!= default_pos.col)
+ additional_data_len(entry.data.buffer_list.buffers[i].
additional_data));
mpack_map(&sbuf.ptr, (uint32_t)entry_map_size);
PACK_KEY(KEY_FILE);
mpack_bin(cstr_as_string(entry.data.buffer_list.buffers[i].fname), &sbuf);
if (entry.data.buffer_list.buffers[i].pos.lnum != 1) {
PACK_KEY(KEY_LNUM);
mpack_uint64(&sbuf.ptr, (uint64_t)entry.data.buffer_list.buffers[i].pos.lnum);
}
if (entry.data.buffer_list.buffers[i].pos.col != 0) {
PACK_KEY(KEY_COL);
mpack_uint64(&sbuf.ptr, (uint64_t)entry.data.buffer_list.buffers[i].pos.col);
}
dump_additional_data(entry.data.buffer_list.buffers[i].additional_data, &sbuf);
}
break;
case kSDItemHeader:
mpack_map(&sbuf.ptr, (uint32_t)entry.data.header.size);
for (size_t i = 0; i < entry.data.header.size; i++) {
mpack_str(entry.data.header.items[i].key, &sbuf);
const Object obj = entry.data.header.items[i].value;
switch (obj.type) {
case kObjectTypeString:
mpack_bin(obj.data.string, &sbuf);
break;
case kObjectTypeInteger:
mpack_integer(&sbuf.ptr, obj.data.integer);
break;
default:
abort();
}
}
break;
}
#undef CHECK_DEFAULT
#undef ONE_IF_NOT_DEFAULT
String packed = packer_take_string(&sbuf);
if (!max_kbyte || packed.size <= max_kbyte * 1024) {
shada_check_buffer(packer);
if (entry.type == kSDItemUnknown) {
mpack_uint64(&packer->ptr, entry.data.unknown_item.type);
} else {
mpack_uint64(&packer->ptr, (uint64_t)entry.type);
}
mpack_uint64(&packer->ptr, (uint64_t)entry.timestamp);
if (packed.size > 0) {
mpack_uint64(&packer->ptr, (uint64_t)packed.size);
mpack_raw(packed.data, packed.size, packer);
}
if (packer->anyint != 0) { // error code
goto shada_pack_entry_error;
}
}
ret = kSDWriteSuccessful;
shada_pack_entry_error:
xfree(sbuf.startptr);
return ret;
}
/// Write single ShaDa entry and free it afterwards
///
/// Will not free if entry could not be freed.
///
/// @param[in] packer Packer used to write entry.
/// @param[in] entry Entry written.
/// @param[in] max_kbyte Maximum size of an item in KiB. Zero means no
/// restrictions.
static inline ShaDaWriteResult shada_pack_pfreed_entry(PackerBuffer *const packer,
PossiblyFreedShadaEntry entry,
const size_t max_kbyte)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE
{
ShaDaWriteResult ret = kSDWriteSuccessful;
ret = shada_pack_entry(packer, entry.data, max_kbyte);
if (entry.can_free_entry) {
shada_free_shada_entry(&entry.data);
}
return ret;
}
/// Compare two FileMarks structure to order them by greatest_timestamp
///
/// Order is reversed: structure with greatest greatest_timestamp comes first.
/// Function signature is compatible with qsort.
static int compare_file_marks(const void *a, const void *b)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
{
const FileMarks *const *const a_fms = a;
const FileMarks *const *const b_fms = b;
return ((*a_fms)->greatest_timestamp == (*b_fms)->greatest_timestamp
? 0
: ((*a_fms)->greatest_timestamp > (*b_fms)->greatest_timestamp ? -1 : 1));
}
/// Parse msgpack object that has given length
///
/// @param[in] sd_reader Structure containing file reader definition.
/// @param[in] length Object length.
/// @param[out] ret_unpacked Location where read result should be saved. If
/// NULL then unpacked data will be freed. Must be
/// NULL if `ret_buf` is NULL.
/// @param[out] ret_buf Buffer containing parsed string.
///
/// @return kSDReadStatusNotShaDa, kSDReadStatusReadError or
/// kSDReadStatusSuccess.
static ShaDaReadResult shada_check_status(uintmax_t initial_fpos, int status, size_t remaining)
FUNC_ATTR_WARN_UNUSED_RESULT
{
switch (status) {
case MPACK_OK:
if (remaining) {
semsg(_(RCERR "Failed to parse ShaDa file: extra bytes in msgpack string "
"at position %" PRIu64),
(uint64_t)initial_fpos);
return kSDReadStatusNotShaDa;
}
return kSDReadStatusSuccess;
case MPACK_EOF:
semsg(_(RCERR "Failed to parse ShaDa file: incomplete msgpack string "
"at position %" PRIu64),
(uint64_t)initial_fpos);
return kSDReadStatusNotShaDa;
default:
semsg(_(RCERR "Failed to parse ShaDa file due to a msgpack parser error "
"at position %" PRIu64),
(uint64_t)initial_fpos);
return kSDReadStatusNotShaDa;
}
}
/// Format shada entry for debugging purposes
///
/// @param[in] entry ShaDa entry to format.
///
/// @return string representing ShaDa entry in a static buffer.
static const char *shada_format_entry(const ShadaEntry entry)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_UNUSED FUNC_ATTR_NONNULL_RET
{
static char ret[1024];
ret[0] = 0;
vim_snprintf(S_LEN(ret), "%s", "[ ] ts=%" PRIu64 " ");
// ^ Space for `can_free_entry`
#define FORMAT_MARK_ENTRY(entry_name, name_fmt, name_fmt_arg) \
do { \
vim_snprintf_add(S_LEN(ret), \
entry_name " {" name_fmt " file=[%zu]\"%.512s\", " \
"pos={l=%" PRIdLINENR ",c=%" PRIdCOLNR ",a=%" PRIdCOLNR "}, " \
"}", \
name_fmt_arg, \
strlen(entry.data.filemark.fname), \
entry.data.filemark.fname, \
entry.data.filemark.mark.lnum, \
entry.data.filemark.mark.col, \
entry.data.filemark.mark.coladd); \
} while (0)
switch (entry.type) {
case kSDItemMissing:
vim_snprintf_add(S_LEN(ret), "Missing");
break;
case kSDItemHeader:
vim_snprintf_add(S_LEN(ret), "Header { TODO }");
break;
case kSDItemBufferList:
vim_snprintf_add(S_LEN(ret), "BufferList { TODO }");
break;
case kSDItemUnknown:
vim_snprintf_add(S_LEN(ret), "Unknown { TODO }");
break;
case kSDItemSearchPattern:
vim_snprintf_add(S_LEN(ret), "SearchPattern { TODO }");
break;
case kSDItemSubString:
vim_snprintf_add(S_LEN(ret), "SubString { TODO }");
break;
case kSDItemHistoryEntry:
vim_snprintf_add(S_LEN(ret), "HistoryEntry { TODO }");
break;
case kSDItemRegister:
vim_snprintf_add(S_LEN(ret), "Register { TODO }");
break;
case kSDItemVariable:
vim_snprintf_add(S_LEN(ret), "Variable { TODO }");
break;
case kSDItemGlobalMark:
FORMAT_MARK_ENTRY("GlobalMark", " name='%c',", entry.data.filemark.name);
break;
case kSDItemChange:
FORMAT_MARK_ENTRY("Change", "%s", "");
break;
case kSDItemLocalMark:
FORMAT_MARK_ENTRY("LocalMark", " name='%c',", entry.data.filemark.name);
break;
case kSDItemJump:
FORMAT_MARK_ENTRY("Jump", "%s", "");
break;
#undef FORMAT_MARK_ENTRY
}
return ret;
}
/// Format possibly freed shada entry for debugging purposes
///
/// @param[in] entry ShaDa entry to format.
///
/// @return string representing ShaDa entry in a static buffer.
static const char *shada_format_pfreed_entry(const PossiblyFreedShadaEntry pfs_entry)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_UNUSED FUNC_ATTR_NONNULL_RET
{
char *ret = (char *)shada_format_entry(pfs_entry.data);
ret[1] = (pfs_entry.can_free_entry ? 'T' : 'F');
return ret;
}
/// Read and merge in ShaDa file, used when writing
///
/// @param[in] sd_reader Structure containing file reader definition.
/// @param[in] srni_flags Flags determining what to read.
/// @param[in] max_kbyte Maximum size of one element.
/// @param[in,out] ret_wms Location where results are saved.
/// @param[out] packer MessagePack packer for entries which are not
/// merged.
static inline ShaDaWriteResult shada_read_when_writing(FileDescriptor *const sd_reader,
const unsigned srni_flags,
const size_t max_kbyte,
WriteMergerState *const wms,
PackerBuffer *const packer)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
ShaDaWriteResult ret = kSDWriteSuccessful;
ShadaEntry entry;
ShaDaReadResult srni_ret;
#define COMPARE_WITH_ENTRY(wms_entry_, entry) \
do { \
PossiblyFreedShadaEntry *const wms_entry = (wms_entry_); \
if (wms_entry->data.type != kSDItemMissing) { \
if (wms_entry->data.timestamp >= (entry).timestamp) { \
shada_free_shada_entry(&(entry)); \
break; \
} \
if (wms_entry->can_free_entry) { \
shada_free_shada_entry(&wms_entry->data); \
} \
} \
*wms_entry = pfs_entry; \
} while (0)
#define FREE_POSSIBLY_FREED_SHADA_ENTRY(entry) \
do { \
if ((entry).can_free_entry) { \
shada_free_shada_entry(&(entry).data); \
} \
} while (0)
#define SDE_TO_PFSDE(entry) \
((PossiblyFreedShadaEntry) { .can_free_entry = true, .data = (entry) })
while ((srni_ret = shada_read_next_item(sd_reader, &entry, srni_flags,
max_kbyte))
!= kSDReadStatusFinished) {
switch (srni_ret) {
case kSDReadStatusSuccess:
break;
case kSDReadStatusFinished:
// Should be handled by the while condition.
abort();
case kSDReadStatusNotShaDa:
ret = kSDWriteReadNotShada;
FALLTHROUGH;
case kSDReadStatusReadError:
return ret;
case kSDReadStatusMalformed:
continue;
}
const PossiblyFreedShadaEntry pfs_entry = {
.can_free_entry = true,
.data = entry,
};
switch (entry.type) {
case kSDItemMissing:
break;
case kSDItemHeader:
case kSDItemBufferList:
abort();
case kSDItemUnknown:
ret = shada_pack_entry(packer, entry, 0);
shada_free_shada_entry(&entry);
break;
case kSDItemSearchPattern:
COMPARE_WITH_ENTRY((entry.data.search_pattern.is_substitute_pattern
? &wms->sub_search_pattern
: &wms->search_pattern), entry);
break;
case kSDItemSubString:
COMPARE_WITH_ENTRY(&wms->replacement, entry);
break;
case kSDItemHistoryEntry:
if (entry.data.history_item.histtype >= HIST_COUNT) {
ret = shada_pack_entry(packer, entry, 0);
shada_free_shada_entry(&entry);
break;
}
if (wms->hms[entry.data.history_item.histtype].hmll.size != 0) {
hms_insert(&wms->hms[entry.data.history_item.histtype], entry, true,
true);
} else {
shada_free_shada_entry(&entry);
}
break;
case kSDItemRegister: {
const int idx = op_reg_index(entry.data.reg.name);
if (idx < 0) {
ret = shada_pack_entry(packer, entry, 0);
shada_free_shada_entry(&entry);
break;
}
COMPARE_WITH_ENTRY(&wms->registers[idx], entry);
break;
}
case kSDItemVariable:
if (!set_has(cstr_t, &wms->dumped_variables, entry.data.global_var.name)) {
ret = shada_pack_entry(packer, entry, 0);
}
shada_free_shada_entry(&entry);
break;
case kSDItemGlobalMark:
if (ascii_isdigit(entry.data.filemark.name)) {
bool processed_mark = false;
// Completely ignore numbered mark names, make a list sorted by
// timestamp.
for (size_t i = ARRAY_SIZE(wms->numbered_marks); i > 0; i--) {
ShadaEntry wms_entry = wms->numbered_marks[i - 1].data;
if (wms_entry.type != kSDItemGlobalMark) {
continue;
}
// Ignore duplicates.
if (wms_entry.timestamp == entry.timestamp
&& (wms_entry.additional_data == NULL
&& entry.additional_data == NULL)
&& marks_equal(wms_entry.data.filemark.mark,
entry.data.filemark.mark)
&& strcmp(wms_entry.data.filemark.fname,
entry.data.filemark.fname) == 0) {
shada_free_shada_entry(&entry);
processed_mark = true;
break;
}
if (wms_entry.timestamp >= entry.timestamp) {
processed_mark = true;
if (i < ARRAY_SIZE(wms->numbered_marks)) {
replace_numbered_mark(wms, i, pfs_entry);
} else {
shada_free_shada_entry(&entry);
}
break;
}
}
if (!processed_mark) {
replace_numbered_mark(wms, 0, pfs_entry);
}
} else {
const int idx = mark_global_index(entry.data.filemark.name);
if (idx < 0) {
ret = shada_pack_entry(packer, entry, 0);
shada_free_shada_entry(&entry);
break;
}
if (wms->global_marks[idx].data.type == kSDItemMissing) {
if (namedfm[idx].fmark.timestamp >= entry.timestamp) {
shada_free_shada_entry(&entry);
break;
}
}
COMPARE_WITH_ENTRY(&wms->global_marks[idx], entry);
}
break;
case kSDItemChange:
case kSDItemLocalMark: {
if (shada_removable(entry.data.filemark.fname)) {
shada_free_shada_entry(&entry);
break;
}
const char *const fname = entry.data.filemark.fname;
cstr_t *key = NULL;
ptr_t *val = pmap_put_ref(cstr_t)(&wms->file_marks, fname, &key, NULL);
if (*val == NULL) {
*val = xcalloc(1, sizeof(FileMarks));
}
FileMarks *const filemarks = *val;
if (entry.timestamp > filemarks->greatest_timestamp) {
filemarks->greatest_timestamp = entry.timestamp;
}
if (entry.type == kSDItemLocalMark) {
const int idx = mark_local_index(entry.data.filemark.name);
if (idx < 0) {
filemarks->additional_marks = xrealloc(filemarks->additional_marks,
(++filemarks->additional_marks_size
* sizeof(filemarks->additional_marks[0])));
filemarks->additional_marks[filemarks->additional_marks_size - 1] =
entry;
} else {
PossiblyFreedShadaEntry *const wms_entry = &filemarks->marks[idx];
bool set_wms = true;
if (wms_entry->data.type != kSDItemMissing) {
if (wms_entry->data.timestamp >= entry.timestamp) {
shada_free_shada_entry(&entry);
break;
}
if (wms_entry->can_free_entry) {
if (*key == wms_entry->data.data.filemark.fname) {
*key = entry.data.filemark.fname;
}
shada_free_shada_entry(&wms_entry->data);
}
} else {
FOR_ALL_BUFFERS(buf) {
if (buf->b_ffname != NULL
&& path_fnamecmp(entry.data.filemark.fname, buf->b_ffname) == 0) {
fmark_T fm;
mark_get(buf, curwin, &fm, kMarkBufLocal, (int)entry.data.filemark.name);
if (fm.timestamp >= entry.timestamp) {
set_wms = false;
shada_free_shada_entry(&entry);
break;
}
}
}
}
if (set_wms) {
*wms_entry = pfs_entry;
}
}
} else {
#define AFTERFREE_DUMMY(entry)
#define DUMMY_IDX_ADJ(i)
MERGE_JUMPS(filemarks->changes_size, filemarks->changes,
PossiblyFreedShadaEntry, data.timestamp,
data.data.filemark.mark, entry, true,
FREE_POSSIBLY_FREED_SHADA_ENTRY, SDE_TO_PFSDE,
DUMMY_IDX_ADJ, AFTERFREE_DUMMY);
}
break;
}
case kSDItemJump:
MERGE_JUMPS(wms->jumps_size, wms->jumps, PossiblyFreedShadaEntry,
data.timestamp, data.data.filemark.mark, entry,
strcmp(jl_entry.data.data.filemark.fname,
entry.data.filemark.fname) == 0,
FREE_POSSIBLY_FREED_SHADA_ENTRY, SDE_TO_PFSDE,
DUMMY_IDX_ADJ, AFTERFREE_DUMMY);
#undef FREE_POSSIBLY_FREED_SHADA_ENTRY
#undef SDE_TO_PFSDE
#undef DUMMY_IDX_ADJ
#undef AFTERFREE_DUMMY
break;
}
}
#undef COMPARE_WITH_ENTRY
return ret;
}
/// Check whether buffer should be ignored
///
/// @param[in] buf buf_T* to check.
/// @param[in] removable_bufs Cache of buffers ignored due to their location.
///
/// @return true or false.
static inline bool ignore_buf(const buf_T *const buf, Set(ptr_t) *const removable_bufs)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE
{
return (buf->b_ffname == NULL || !buf->b_p_bl || bt_quickfix(buf) \
|| bt_terminal(buf) || set_has(ptr_t, removable_bufs, (ptr_t)buf));
}
/// Get list of buffers to write to the shada file
///
/// @param[in] removable_bufs Buffers which are ignored
///
/// @return ShadaEntry List of buffers to save, kSDItemBufferList entry.
static inline ShadaEntry shada_get_buflist(Set(ptr_t) *const removable_bufs)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE
{
int max_bufs = get_shada_parameter('%');
size_t buf_count = 0;
FOR_ALL_BUFFERS(buf) {
if (!ignore_buf(buf, removable_bufs)
&& (max_bufs < 0 || buf_count < (size_t)max_bufs)) {
buf_count++;
}
}
ShadaEntry buflist_entry = (ShadaEntry) {
.type = kSDItemBufferList,
.timestamp = os_time(),
.data = {
.buffer_list = {
.size = buf_count,
.buffers = xmalloc(buf_count
* sizeof(*buflist_entry.data.buffer_list.buffers)),
},
},
};
size_t i = 0;
FOR_ALL_BUFFERS(buf) {
if (ignore_buf(buf, removable_bufs)) {
continue;
}
if (i >= buf_count) {
break;
}
buflist_entry.data.buffer_list.buffers[i] = (struct buffer_list_buffer) {
.pos = buf->b_last_cursor.mark,
.fname = buf->b_ffname,
.additional_data = buf->additional_data,
};
i++;
}
return buflist_entry;
}
/// Save search pattern to PossiblyFreedShadaEntry
///
/// @param[out] ret_pse Location where result will be saved.
/// @param[in] get_pattern Function used to get pattern.
/// @param[in] is_substitute_pattern True if pattern in question is substitute
/// pattern. Also controls whether some
/// fields should be initialized to default
/// or values from get_pattern.
/// @param[in] search_last_used Result of search_was_last_used().
/// @param[in] search_highlighted True if search pattern was highlighted by
/// &hlsearch and this information should be
/// saved.
static inline void add_search_pattern(PossiblyFreedShadaEntry *const ret_pse,
const SearchPatternGetter get_pattern,
const bool is_substitute_pattern, const bool search_last_used,
const bool search_highlighted)
FUNC_ATTR_ALWAYS_INLINE
{
const ShadaEntry defaults = sd_default_values[kSDItemSearchPattern];
SearchPattern pat;
get_pattern(&pat);
if (pat.pat != NULL) {
*ret_pse = (PossiblyFreedShadaEntry) {
.can_free_entry = false,
.data = {
.type = kSDItemSearchPattern,
.timestamp = pat.timestamp,
.data = {
.search_pattern = {
.magic = pat.magic,
.smartcase = !pat.no_scs,
.has_line_offset = (is_substitute_pattern
? defaults.data.search_pattern.has_line_offset
: pat.off.line),
.place_cursor_at_end = (
is_substitute_pattern
? defaults.data.search_pattern.place_cursor_at_end
: pat.off.end),
.offset = (is_substitute_pattern
? defaults.data.search_pattern.offset
: pat.off.off),
.is_last_used = (is_substitute_pattern ^ search_last_used),
.is_substitute_pattern = is_substitute_pattern,
.highlighted = ((is_substitute_pattern ^ search_last_used)
&& search_highlighted),
.pat = cstr_as_string(pat.pat),
.search_backward = (!is_substitute_pattern && pat.off.dir == '?'),
}
},
.additional_data = pat.additional_data,
}
};
}
}
/// Initialize registers for writing to the ShaDa file
///
/// @param[in] wms The WriteMergerState used when writing.
/// @param[in] max_reg_lines The maximum number of register lines.
static inline void shada_initialize_registers(WriteMergerState *const wms, int max_reg_lines)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE
{
const void *reg_iter = NULL;
const bool limit_reg_lines = max_reg_lines >= 0;
do {
yankreg_T reg;
char name = NUL;
bool is_unnamed = false;
reg_iter = op_global_reg_iter(reg_iter, &name, ®, &is_unnamed);
if (name == NUL) {
break;
}
if (limit_reg_lines && reg.y_size > (size_t)max_reg_lines) {
continue;
}
wms->registers[op_reg_index(name)] = (PossiblyFreedShadaEntry) {
.can_free_entry = false,
.data = {
.type = kSDItemRegister,
.timestamp = reg.timestamp,
.data = {
.reg = {
.contents = reg.y_array,
.contents_size = reg.y_size,
.type = reg.y_type,
.width = (size_t)(reg.y_type == kMTBlockWise ? reg.y_width : 0),
.name = name,
.is_unnamed = is_unnamed,
}
},
.additional_data = reg.additional_data,
}
};
} while (reg_iter != NULL);
}
/// Replace numbered mark in WriteMergerState
///
/// Frees the last mark, moves (including adjusting mark names) marks from idx
/// to the last-but-one one and saves the new mark at given index.
///
/// @param[out] wms Merger state to adjust.
/// @param[in] idx Index at which new mark should be placed.
/// @param[in] entry New mark.
static inline void replace_numbered_mark(WriteMergerState *const wms, const size_t idx,
const PossiblyFreedShadaEntry entry)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE
{
if (ARRAY_LAST_ENTRY(wms->numbered_marks).can_free_entry) {
shada_free_shada_entry(&ARRAY_LAST_ENTRY(wms->numbered_marks).data);
}
for (size_t i = idx; i < ARRAY_SIZE(wms->numbered_marks) - 1; i++) {
if (wms->numbered_marks[i].data.type == kSDItemGlobalMark) {
wms->numbered_marks[i].data.data.filemark.name = (char)('0' + (int)i + 1);
}
}
memmove(wms->numbered_marks + idx + 1, wms->numbered_marks + idx,
sizeof(wms->numbered_marks[0])
* (ARRAY_SIZE(wms->numbered_marks) - 1 - idx));
wms->numbered_marks[idx] = entry;
wms->numbered_marks[idx].data.data.filemark.name = (char)('0' + (int)idx);
}
/// Find buffers ignored due to their location.
///
/// @param[out] removable_bufs Cache of buffers ignored due to their location.
static inline void find_removable_bufs(Set(ptr_t) *removable_bufs)
{
FOR_ALL_BUFFERS(buf) {
if (buf->b_ffname != NULL && shada_removable(buf->b_ffname)) {
set_put(ptr_t, removable_bufs, (ptr_t)buf);
}
}
}
/// Translate a history type number to the associated character
static int hist_type2char(const int type)
FUNC_ATTR_CONST
{
switch (type) {
case HIST_CMD:
return ':';
case HIST_SEARCH:
return '/';
case HIST_EXPR:
return '=';
case HIST_INPUT:
return '@';
case HIST_DEBUG:
return '>';
default:
abort();
}
return NUL;
}
static PackerBuffer packer_buffer_for_file(FileDescriptor *file)
{
if (file_space(file) < SHADA_MPACK_FREE_SPACE) {
file_flush(file);
}
return (PackerBuffer) {
.startptr = file->buffer,
.ptr = file->write_pos,
.endptr = file->buffer + ARENA_BLOCK_SIZE,
.anydata = file,
.anyint = 0, // set to nonzero if error
.packer_flush = flush_file_buffer,
};
}
static void flush_file_buffer(PackerBuffer *buffer)
{
FileDescriptor *fd = buffer->anydata;
fd->write_pos = buffer->ptr;
buffer->anyint = file_flush(fd);
buffer->ptr = fd->write_pos;
}
/// Write ShaDa file
///
/// @param[in] sd_writer Structure containing file writer definition.
/// @param[in] sd_reader Structure containing file reader definition. If it is
/// not NULL then contents of this file will be merged
/// with current Neovim runtime.
static ShaDaWriteResult shada_write(FileDescriptor *const sd_writer,
FileDescriptor *const sd_reader)
FUNC_ATTR_NONNULL_ARG(1)
{
ShaDaWriteResult ret = kSDWriteSuccessful;
int max_kbyte_i = get_shada_parameter('s');
if (max_kbyte_i < 0) {
max_kbyte_i = 10;
}
if (max_kbyte_i == 0) {
return ret;
}
WriteMergerState *const wms = xcalloc(1, sizeof(*wms));
bool dump_one_history[HIST_COUNT];
const bool dump_global_vars = (find_shada_parameter('!') != NULL);
int max_reg_lines = get_shada_parameter('<');
if (max_reg_lines < 0) {
max_reg_lines = get_shada_parameter('"');
}
const bool dump_registers = (max_reg_lines != 0);
Set(ptr_t) removable_bufs = SET_INIT;
const size_t max_kbyte = (size_t)max_kbyte_i;
const size_t num_marked_files = (size_t)get_shada_parameter('\'');
const bool dump_global_marks = get_shada_parameter('f') != 0;
bool dump_history = false;
// Initialize history merger
for (int i = 0; i < HIST_COUNT; i++) {
int num_saved = get_shada_parameter(hist_type2char(i));
if (num_saved == -1) {
num_saved = (int)p_hi;
}
if (num_saved > 0) {
dump_history = true;
dump_one_history[i] = true;
hms_init(&wms->hms[i], (uint8_t)i, (size_t)num_saved, sd_reader != NULL, false);
} else {
dump_one_history[i] = false;
}
}
const unsigned srni_flags = (unsigned)(kSDReadUndisableableData
| kSDReadUnknown
| (dump_history ? kSDReadHistory : 0)
| (dump_registers ? kSDReadRegisters : 0)
| (dump_global_vars ? kSDReadVariables : 0)
| (dump_global_marks ? kSDReadGlobalMarks : 0)
| (num_marked_files ? kSDReadLocalMarks |
kSDReadChanges : 0));
PackerBuffer packer = packer_buffer_for_file(sd_writer);
// Set b_last_cursor for all the buffers that have a window.
//
// It is needed to correctly save '"' mark on exit. Has a side effect of
// setting '"' mark in all windows on :wshada to the current cursor
// position (basically what :wviminfo used to do).
FOR_ALL_TAB_WINDOWS(tp, wp) {
set_last_cursor(wp);
}
find_removable_bufs(&removable_bufs);
// Write header
if (shada_pack_entry(&packer, (ShadaEntry) {
.type = kSDItemHeader,
.timestamp = os_time(),
.data = {
.header = {
.size = 5,
.capacity = 5,
.items = ((KeyValuePair[]) {
{ STATIC_CSTR_AS_STRING("generator"),
STATIC_CSTR_AS_OBJ("nvim") },
{ STATIC_CSTR_AS_STRING("version"),
CSTR_AS_OBJ(longVersion) },
{ STATIC_CSTR_AS_STRING("max_kbyte"),
INTEGER_OBJ((Integer)max_kbyte) },
{ STATIC_CSTR_AS_STRING("pid"),
INTEGER_OBJ((Integer)os_get_pid()) },
{ STATIC_CSTR_AS_STRING("encoding"),
CSTR_AS_OBJ(p_enc) },
}),
}
}
}, 0) == kSDWriteFailed) {
ret = kSDWriteFailed;
goto shada_write_exit;
}
// Write buffer list
if (find_shada_parameter('%') != NULL) {
ShadaEntry buflist_entry = shada_get_buflist(&removable_bufs);
if (shada_pack_entry(&packer, buflist_entry, 0) == kSDWriteFailed) {
xfree(buflist_entry.data.buffer_list.buffers);
ret = kSDWriteFailed;
goto shada_write_exit;
}
xfree(buflist_entry.data.buffer_list.buffers);
}
// Write some of the variables
if (dump_global_vars) {
const void *var_iter = NULL;
const Timestamp cur_timestamp = os_time();
do {
typval_T vartv;
const char *name = NULL;
var_iter = var_shada_iter(var_iter, &name, &vartv, VAR_FLAVOUR_SHADA);
if (name == NULL) {
break;
}
switch (vartv.v_type) {
case VAR_FUNC:
case VAR_PARTIAL:
tv_clear(&vartv);
continue;
case VAR_DICT: {
dict_T *di = vartv.vval.v_dict;
int copyID = get_copyID();
if (!set_ref_in_ht(&di->dv_hashtab, copyID, NULL)
&& copyID == di->dv_copyID) {
tv_clear(&vartv);
continue;
}
break;
}
case VAR_LIST: {
list_T *l = vartv.vval.v_list;
int copyID = get_copyID();
if (!set_ref_in_list_items(l, copyID, NULL)
&& copyID == l->lv_copyID) {
tv_clear(&vartv);
continue;
}
break;
}
default:
break;
}
typval_T tgttv;
tv_copy(&vartv, &tgttv);
ShaDaWriteResult spe_ret;
if ((spe_ret = shada_pack_entry(&packer, (ShadaEntry) {
.type = kSDItemVariable,
.timestamp = cur_timestamp,
.data = {
.global_var = {
.name = (char *)name,
.value = tgttv,
}
},
.additional_data = NULL,
}, max_kbyte)) == kSDWriteFailed) {
tv_clear(&vartv);
tv_clear(&tgttv);
ret = kSDWriteFailed;
goto shada_write_exit;
}
tv_clear(&vartv);
tv_clear(&tgttv);
if (spe_ret == kSDWriteSuccessful) {
set_put(cstr_t, &wms->dumped_variables, name);
}
} while (var_iter != NULL);
}
// Initialize jump list
wms->jumps_size = shada_init_jumps(wms->jumps, &removable_bufs);
const bool search_highlighted = !(no_hlsearch
|| find_shada_parameter('h') != NULL);
const bool search_last_used = search_was_last_used();
// Initialize search pattern
add_search_pattern(&wms->search_pattern, &get_search_pattern, false,
search_last_used, search_highlighted);
// Initialize substitute search pattern
add_search_pattern(&wms->sub_search_pattern, &get_substitute_pattern, true,
search_last_used, search_highlighted);
// Initialize substitute replacement string
{
SubReplacementString sub;
sub_get_replacement(&sub);
wms->replacement = (PossiblyFreedShadaEntry) {
.can_free_entry = false,
.data = {
.type = kSDItemSubString,
.timestamp = sub.timestamp,
.data = {
.sub_string = {
.sub = sub.sub,
}
},
.additional_data = sub.additional_data,
}
};
}
// Initialize global marks
if (dump_global_marks) {
const void *global_mark_iter = NULL;
size_t digit_mark_idx = 0;
do {
char name = NUL;
xfmark_T fm;
global_mark_iter = mark_global_iter(global_mark_iter, &name, &fm);
if (name == NUL) {
break;
}
const char *fname;
if (fm.fmark.fnum == 0) {
assert(fm.fname != NULL);
if (shada_removable(fm.fname)) {
continue;
}
fname = fm.fname;
} else {
const buf_T *const buf = buflist_findnr(fm.fmark.fnum);
if (buf == NULL || buf->b_ffname == NULL
|| set_has(ptr_t, &removable_bufs, (ptr_t)buf)) {
continue;
}
fname = buf->b_ffname;
}
const PossiblyFreedShadaEntry pf_entry = {
.can_free_entry = false,
.data = {
.type = kSDItemGlobalMark,
.timestamp = fm.fmark.timestamp,
.data = {
.filemark = {
.mark = fm.fmark.mark,
.name = name,
.fname = (char *)fname,
}
},
.additional_data = fm.fmark.additional_data,
},
};
if (ascii_isdigit(name)) {
replace_numbered_mark(wms, digit_mark_idx++, pf_entry);
} else {
wms->global_marks[mark_global_index(name)] = pf_entry;
}
} while (global_mark_iter != NULL);
}
// Initialize registers
if (dump_registers) {
shada_initialize_registers(wms, max_reg_lines);
}
// Initialize buffers
if (num_marked_files > 0) {
FOR_ALL_BUFFERS(buf) {
if (buf->b_ffname == NULL || set_has(ptr_t, &removable_bufs, buf)) {
continue;
}
const void *local_marks_iter = NULL;
const char *const fname = buf->b_ffname;
ptr_t *val = pmap_put_ref(cstr_t)(&wms->file_marks, fname, NULL, NULL);
if (*val == NULL) {
*val = xcalloc(1, sizeof(FileMarks));
}
FileMarks *const filemarks = *val;
do {
fmark_T fm;
char name = NUL;
local_marks_iter = mark_buffer_iter(local_marks_iter, buf, &name, &fm);
if (name == NUL) {
break;
}
filemarks->marks[mark_local_index(name)] = (PossiblyFreedShadaEntry) {
.can_free_entry = false,
.data = {
.type = kSDItemLocalMark,
.timestamp = fm.timestamp,
.data = {
.filemark = {
.mark = fm.mark,
.name = name,
.fname = (char *)fname,
}
},
.additional_data = fm.additional_data,
}
};
if (fm.timestamp > filemarks->greatest_timestamp) {
filemarks->greatest_timestamp = fm.timestamp;
}
} while (local_marks_iter != NULL);
for (int i = 0; i < buf->b_changelistlen; i++) {
const fmark_T fm = buf->b_changelist[i];
filemarks->changes[i] = (PossiblyFreedShadaEntry) {
.can_free_entry = false,
.data = {
.type = kSDItemChange,
.timestamp = fm.timestamp,
.data = {
.filemark = {
.mark = fm.mark,
.fname = (char *)fname,
}
},
.additional_data = fm.additional_data,
}
};
if (fm.timestamp > filemarks->greatest_timestamp) {
filemarks->greatest_timestamp = fm.timestamp;
}
}
filemarks->changes_size = (size_t)buf->b_changelistlen;
}
}
if (sd_reader != NULL) {
const ShaDaWriteResult srww_ret = shada_read_when_writing(sd_reader, srni_flags, max_kbyte, wms,
&packer);
if (srww_ret != kSDWriteSuccessful) {
ret = srww_ret;
}
}
// Update numbered marks: '0' should be replaced with the current position,
// '9' should be removed and all other marks shifted.
if (!ignore_buf(curbuf, &removable_bufs) && curwin->w_cursor.lnum != 0) {
replace_numbered_mark(wms, 0, (PossiblyFreedShadaEntry) {
.can_free_entry = false,
.data = {
.type = kSDItemGlobalMark,
.timestamp = os_time(),
.data = {
.filemark = {
.mark = curwin->w_cursor,
.name = '0',
.fname = curbuf->b_ffname,
}
},
.additional_data = NULL,
},
});
}
// Write the rest
#define PACK_WMS_ARRAY(wms_array) \
do { \
for (size_t i_ = 0; i_ < ARRAY_SIZE(wms_array); i_++) { \
if ((wms_array)[i_].data.type != kSDItemMissing) { \
if (shada_pack_pfreed_entry(&packer, (wms_array)[i_], max_kbyte) \
== kSDWriteFailed) { \
ret = kSDWriteFailed; \
goto shada_write_exit; \
} \
} \
} \
} while (0)
PACK_WMS_ARRAY(wms->global_marks);
PACK_WMS_ARRAY(wms->numbered_marks);
PACK_WMS_ARRAY(wms->registers);
for (size_t i = 0; i < wms->jumps_size; i++) {
if (shada_pack_pfreed_entry(&packer, wms->jumps[i], max_kbyte)
== kSDWriteFailed) {
ret = kSDWriteFailed;
goto shada_write_exit;
}
}
#define PACK_WMS_ENTRY(wms_entry) \
do { \
if ((wms_entry).data.type != kSDItemMissing) { \
if (shada_pack_pfreed_entry(&packer, wms_entry, max_kbyte) \
== kSDWriteFailed) { \
ret = kSDWriteFailed; \
goto shada_write_exit; \
} \
} \
} while (0)
PACK_WMS_ENTRY(wms->search_pattern);
PACK_WMS_ENTRY(wms->sub_search_pattern);
PACK_WMS_ENTRY(wms->replacement);
#undef PACK_WMS_ENTRY
const size_t file_markss_size = map_size(&wms->file_marks);
FileMarks **const all_file_markss =
xmalloc(file_markss_size * sizeof(*all_file_markss));
FileMarks **cur_file_marks = all_file_markss;
ptr_t val;
map_foreach_value(&wms->file_marks, val, {
*cur_file_marks++ = val;
})
qsort((void *)all_file_markss, file_markss_size, sizeof(*all_file_markss),
&compare_file_marks);
const size_t file_markss_to_dump = MIN(num_marked_files, file_markss_size);
for (size_t i = 0; i < file_markss_to_dump; i++) {
PACK_WMS_ARRAY(all_file_markss[i]->marks);
for (size_t j = 0; j < all_file_markss[i]->changes_size; j++) {
if (shada_pack_pfreed_entry(&packer, all_file_markss[i]->changes[j],
max_kbyte) == kSDWriteFailed) {
ret = kSDWriteFailed;
goto shada_write_exit;
}
}
for (size_t j = 0; j < all_file_markss[i]->additional_marks_size; j++) {
if (shada_pack_entry(&packer, all_file_markss[i]->additional_marks[j],
0) == kSDWriteFailed) {
shada_free_shada_entry(&all_file_markss[i]->additional_marks[j]);
ret = kSDWriteFailed;
goto shada_write_exit;
}
shada_free_shada_entry(&all_file_markss[i]->additional_marks[j]);
}
xfree(all_file_markss[i]->additional_marks);
}
xfree(all_file_markss);
#undef PACK_WMS_ARRAY
if (dump_history) {
for (int i = 0; i < HIST_COUNT; i++) {
if (dump_one_history[i]) {
hms_insert_whole_neovim_history(&wms->hms[i]);
HMS_ITER(&wms->hms[i], cur_entry, {
if (shada_pack_pfreed_entry(&packer, (PossiblyFreedShadaEntry) {
.data = cur_entry->data,
.can_free_entry = cur_entry->can_free_entry,
}, max_kbyte) == kSDWriteFailed) {
ret = kSDWriteFailed;
break;
}
})
if (ret == kSDWriteFailed) {
goto shada_write_exit;
}
}
}
}
shada_write_exit:
for (int i = 0; i < HIST_COUNT; i++) {
if (dump_one_history[i]) {
hms_dealloc(&wms->hms[i]);
}
}
map_foreach_value(&wms->file_marks, val, {
xfree(val);
})
map_destroy(cstr_t, &wms->file_marks);
set_destroy(ptr_t, &removable_bufs);
packer.packer_flush(&packer);
set_destroy(cstr_t, &wms->dumped_variables);
xfree(wms);
return ret;
}
#undef PACK_KEY
/// Write ShaDa file to a given location
///
/// @param[in] fname File to write to. If it is NULL or empty then default
/// location is used.
/// @param[in] nomerge If true then old file is ignored.
///
/// @return OK if writing was successful, FAIL otherwise.
int shada_write_file(const char *const file, bool nomerge)
{
if (shada_disabled()) {
return FAIL;
}
char *const fname = shada_filename(file);
char *tempname = NULL;
FileDescriptor sd_writer;
FileDescriptor sd_reader;
bool did_open_writer = false;
bool did_open_reader = false;
if (!nomerge) {
int error;
if ((error = file_open(&sd_reader, fname, kFileReadOnly, 0)) != 0) {
if (error != UV_ENOENT) {
semsg(_(SERR "System error while opening ShaDa file %s for reading "
"to merge before writing it: %s"),
fname, os_strerror(error));
// Try writing the file even if opening it emerged any issues besides
// file not existing: maybe writing will succeed nevertheless.
}
nomerge = true;
goto shada_write_file_nomerge;
} else {
did_open_reader = true;
}
tempname = modname(fname, ".tmp.a", false);
if (tempname == NULL) {
nomerge = true;
goto shada_write_file_nomerge;
}
// Save permissions from the original file, with modifications:
int perm = (int)os_getperm(fname);
perm = (perm >= 0) ? ((perm & 0777) | 0600) : 0600;
// ^3 ^1 ^2 ^2,3
// 1: Strip SUID bit if any.
// 2: Make sure that user can always read and write the result.
// 3: If somebody happened to delete the file after it was opened for
// reading use u=rw permissions.
shada_write_file_open: {}
error = file_open(&sd_writer, tempname, kFileCreateOnly|kFileNoSymlink, perm);
if (error) {
if (error == UV_EEXIST || error == UV_ELOOP) {
// File already exists, try another name
char *const wp = tempname + strlen(tempname) - 1;
if (*wp == 'z') {
// Tried names from .tmp.a to .tmp.z, all failed. Something must be
// wrong then.
semsg(_("E138: All %s.tmp.X files exist, cannot write ShaDa file!"),
fname);
xfree(fname);
xfree(tempname);
if (did_open_reader) {
close_file(&sd_reader);
}
return FAIL;
}
(*wp)++;
goto shada_write_file_open;
} else {
semsg(_(SERR "System error while opening temporary ShaDa file %s "
"for writing: %s"), tempname, os_strerror(error));
}
} else {
did_open_writer = true;
}
}
if (nomerge) {
shada_write_file_nomerge: {}
char *const tail = path_tail_with_sep(fname);
if (tail != fname) {
const char tail_save = *tail;
*tail = NUL;
if (!os_isdir(fname)) {
int ret;
char *failed_dir;
if ((ret = os_mkdir_recurse(fname, 0700, &failed_dir, NULL)) != 0) {
semsg(_(SERR "Failed to create directory %s "
"for writing ShaDa file: %s"),
failed_dir, os_strerror(ret));
xfree(fname);
xfree(failed_dir);
return FAIL;
}
}
*tail = tail_save;
}
int error = file_open(&sd_writer, fname, kFileCreate|kFileTruncate, 0600);
if (error) {
semsg(_(SERR "System error while opening ShaDa file %s for writing: %s"),
fname, os_strerror(error));
} else {
did_open_writer = true;
}
}
if (!did_open_writer) {
xfree(fname);
xfree(tempname);
if (did_open_reader) {
close_file(&sd_reader);
}
return FAIL;
}
if (p_verbose > 1) {
verbose_enter();
smsg(0, _("Writing ShaDa file \"%s\""), fname);
verbose_leave();
}
const ShaDaWriteResult sw_ret = shada_write(&sd_writer, (nomerge ? NULL : &sd_reader));
assert(sw_ret != kSDWriteIgnError);
if (!nomerge) {
if (did_open_reader) {
close_file(&sd_reader);
}
bool did_remove = false;
if (sw_ret == kSDWriteSuccessful) {
FileInfo old_info;
if (!os_fileinfo(fname, &old_info)
|| S_ISDIR(old_info.stat.st_mode)
#ifdef UNIX
// For Unix we check the owner of the file. It's not very nice
// to overwrite a user's viminfo file after a "su root", with a
// viminfo file that the user can't read.
|| (getuid() != ROOT_UID
&& !(old_info.stat.st_uid == getuid()
? (old_info.stat.st_mode & 0200)
: (old_info.stat.st_gid == getgid()
? (old_info.stat.st_mode & 0020)
: (old_info.stat.st_mode & 0002))))
#endif
) {
semsg(_("E137: ShaDa file is not writable: %s"), fname);
goto shada_write_file_did_not_remove;
}
#ifdef UNIX
if (getuid() == ROOT_UID) {
if (old_info.stat.st_uid != ROOT_UID
|| old_info.stat.st_gid != getgid()) {
const uv_uid_t old_uid = (uv_uid_t)old_info.stat.st_uid;
const uv_gid_t old_gid = (uv_gid_t)old_info.stat.st_gid;
const int fchown_ret = os_fchown(file_fd(&sd_writer),
old_uid, old_gid);
if (fchown_ret != 0) {
semsg(_(RNERR "Failed setting uid and gid for file %s: %s"),
tempname, os_strerror(fchown_ret));
goto shada_write_file_did_not_remove;
}
}
}
#endif
if (vim_rename(tempname, fname) == -1) {
semsg(_(RNERR "Can't rename ShaDa file from %s to %s!"),
tempname, fname);
} else {
did_remove = true;
os_remove(tempname);
}
} else {
if (sw_ret == kSDWriteReadNotShada) {
semsg(_(RNERR "Did not rename %s because %s "
"does not look like a ShaDa file"), tempname, fname);
} else {
semsg(_(RNERR "Did not rename %s to %s because there were errors "
"during writing it"), tempname, fname);
}
}
if (!did_remove) {
shada_write_file_did_not_remove:
semsg(_(RNERR "Do not forget to remove %s or rename it manually to %s."),
tempname, fname);
}
xfree(tempname);
}
close_file(&sd_writer);
xfree(fname);
return OK;
}
/// Read marks information from ShaDa file
///
/// @return OK in case of success, FAIL otherwise.
int shada_read_marks(void)
{
return shada_read_file(NULL, kShaDaWantMarks);
}
/// Read all information from ShaDa file
///
/// @param[in] fname File to write to. If it is NULL or empty then default
/// @param[in] forceit If true, use forced reading (prioritize file contents
/// over current Neovim state).
/// @param[in] missing_ok If true, do not error out when file is missing.
///
/// @return OK in case of success, FAIL otherwise.
int shada_read_everything(const char *const fname, const bool forceit, const bool missing_ok)
{
return shada_read_file(fname,
kShaDaWantInfo|kShaDaWantMarks|kShaDaGetOldfiles
|(forceit ? kShaDaForceit : 0)
|(missing_ok ? 0 : kShaDaMissingError));
}
static void shada_free_shada_entry(ShadaEntry *const entry)
{
if (entry == NULL) {
return;
}
switch (entry->type) {
case kSDItemMissing:
break;
case kSDItemUnknown:
xfree(entry->data.unknown_item.contents);
break;
case kSDItemHeader:
api_free_dictionary(entry->data.header);
break;
case kSDItemChange:
case kSDItemJump:
case kSDItemGlobalMark:
case kSDItemLocalMark:
xfree(entry->data.filemark.fname);
break;
case kSDItemSearchPattern:
api_free_string(entry->data.search_pattern.pat);
break;
case kSDItemRegister:
for (size_t i = 0; i < entry->data.reg.contents_size; i++) {
xfree(entry->data.reg.contents[i]);
}
xfree(entry->data.reg.contents);
break;
case kSDItemHistoryEntry:
xfree(entry->data.history_item.string);
break;
case kSDItemVariable:
xfree(entry->data.global_var.name);
tv_clear(&entry->data.global_var.value);
break;
case kSDItemSubString:
xfree(entry->data.sub_string.sub);
break;
case kSDItemBufferList:
for (size_t i = 0; i < entry->data.buffer_list.size; i++) {
xfree(entry->data.buffer_list.buffers[i].fname);
xfree(entry->data.buffer_list.buffers[i].additional_data);
}
xfree(entry->data.buffer_list.buffers);
break;
}
XFREE_CLEAR(entry->additional_data);
}
#ifndef HAVE_BE64TOH
static inline uint64_t be64toh(uint64_t big_endian_64_bits)
{
# ifdef ORDER_BIG_ENDIAN
return big_endian_64_bits;
# else
// It may appear that when !defined(ORDER_BIG_ENDIAN) actual order is big
// endian. This variant is suboptimal, but it works regardless of actual
// order.
uint8_t *buf = (uint8_t *)&big_endian_64_bits;
uint64_t ret = 0;
for (size_t i = 8; i; i--) {
ret |= ((uint64_t)buf[i - 1]) << ((8 - i) * 8);
}
return ret;
# endif
}
#endif
/// Read given number of bytes into given buffer, display error if needed
///
/// @param[in] sd_reader Structure containing file reader definition.
/// @param[out] buffer Where to save the results.
/// @param[in] length How many bytes should be read.
///
/// @return kSDReadStatusSuccess if everything was OK, kSDReadStatusNotShaDa if
/// there were not enough bytes to read or kSDReadStatusReadError if
/// there was some error while reading.
static ShaDaReadResult fread_len(FileDescriptor *const sd_reader, char *const buffer,
const size_t length)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
const ptrdiff_t read_bytes = file_read(sd_reader, buffer, length);
if (read_bytes < 0) {
semsg(_(SERR "System error while reading ShaDa file: %s"),
os_strerror((int)read_bytes));
return kSDReadStatusReadError;
}
if (read_bytes != (ptrdiff_t)length) {
semsg(_(RCERR "Error while reading ShaDa file: "
"last entry specified that it occupies %" PRIu64 " bytes, "
"but file ended earlier"),
(uint64_t)length);
return kSDReadStatusNotShaDa;
}
return kSDReadStatusSuccess;
}
/// Read next unsigned integer from file
///
/// Errors out if the result is not an unsigned integer.
///
/// Unlike msgpack own function this one works with `FILE *` and reads *exactly*
/// as much bytes as needed, making it possible to avoid both maintaining own
/// buffer and calling `fseek`.
///
/// One byte from file stream is always consumed, even if it is not correct.
///
/// @param[in] sd_reader Structure containing file reader definition.
/// @param[out] result Location where result is saved.
///
/// @return kSDReadStatusSuccess if reading was successful,
/// kSDReadStatusNotShaDa if there were not enough bytes to read or
/// kSDReadStatusReadError if reading failed for whatever reason.
/// kSDReadStatusFinished if eof and that was allowed
static ShaDaReadResult msgpack_read_uint64(FileDescriptor *const sd_reader, bool allow_eof,
uint64_t *const result)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
const uintmax_t fpos = sd_reader->bytes_read;
uint8_t ret;
ptrdiff_t read_bytes = file_read(sd_reader, (char *)&ret, 1);
if (read_bytes < 0) {
semsg(_(SERR "System error while reading integer from ShaDa file: %s"),
os_strerror((int)read_bytes));
return kSDReadStatusReadError;
} else if (read_bytes == 0) {
if (allow_eof && file_eof(sd_reader)) {
return kSDReadStatusFinished;
}
semsg(_(RCERR "Error while reading ShaDa file: "
"expected positive integer at position %" PRIu64
", but got nothing"),
(uint64_t)fpos);
return kSDReadStatusNotShaDa;
}
int first_char = (int)ret;
if (~first_char & 0x80) {
// Positive fixnum
*result = (uint64_t)((uint8_t)first_char);
} else {
size_t length = 0;
switch (first_char) {
case 0xCC: // uint8
length = 1;
break;
case 0xCD: // uint16
length = 2;
break;
case 0xCE: // uint32
length = 4;
break;
case 0xCF: // uint64
length = 8;
break;
default:
semsg(_(RCERR "Error while reading ShaDa file: "
"expected positive integer at position %" PRIu64),
(uint64_t)fpos);
return kSDReadStatusNotShaDa;
}
uint64_t buf = 0;
char *buf_u8 = (char *)&buf;
ShaDaReadResult fl_ret;
if ((fl_ret = fread_len(sd_reader, &(buf_u8[sizeof(buf) - length]), length))
!= kSDReadStatusSuccess) {
return fl_ret;
}
*result = be64toh(buf);
}
return kSDReadStatusSuccess;
}
#define READERR(entry_name, error_desc) \
RERR "Error while reading ShaDa file: " \
entry_name " entry at position %" PRIu64 " " \
error_desc
/// Iterate over shada file contents
///
/// @param[in] sd_reader Structure containing file reader definition.
/// @param[out] entry Address where next entry contents will be saved.
/// @param[in] flags Flags, determining whether and which items should be
/// skipped (see SRNIFlags enum).
/// @param[in] max_kbyte If non-zero, skip reading entries which have length
/// greater then given.
///
/// @return Any value from ShaDaReadResult enum.
static ShaDaReadResult shada_read_next_item(FileDescriptor *const sd_reader,
ShadaEntry *const entry, const unsigned flags,
const size_t max_kbyte)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
ShaDaReadResult ret = kSDReadStatusMalformed;
shada_read_next_item_start:
// Set entry type to kSDItemMissing and also make sure that all pointers in
// data union are NULL so they are safe to xfree(). This is needed in case
// somebody calls goto shada_read_next_item_error before anything is set in
// the switch.
CLEAR_POINTER(entry);
if (file_eof(sd_reader)) {
return kSDReadStatusFinished;
}
bool verify_but_ignore = false;
// First: manually unpack type, timestamp and length.
// This is needed to avoid both seeking and having to maintain a buffer.
uint64_t type_u64 = (uint64_t)kSDItemMissing;
uint64_t timestamp_u64;
uint64_t length_u64;
const uint64_t initial_fpos = sd_reader->bytes_read;
AdditionalDataBuilder ad = KV_INITIAL_VALUE;
uint32_t read_additional_array_elements = 0;
char *error_alloc = NULL;
ShaDaReadResult mru_ret;
if (((mru_ret = msgpack_read_uint64(sd_reader, true, &type_u64))
!= kSDReadStatusSuccess)
|| ((mru_ret = msgpack_read_uint64(sd_reader, false,
×tamp_u64))
!= kSDReadStatusSuccess)
|| ((mru_ret = msgpack_read_uint64(sd_reader, false,
&length_u64))
!= kSDReadStatusSuccess)) {
return mru_ret;
}
if (length_u64 > PTRDIFF_MAX) {
semsg(_(RCERR "Error while reading ShaDa file: "
"there is an item at position %" PRIu64 " "
"that is stated to be too long"),
initial_fpos);
return kSDReadStatusNotShaDa;
}
const size_t length = (size_t)length_u64;
entry->timestamp = (Timestamp)timestamp_u64;
if (type_u64 == 0) {
// kSDItemUnknown cannot possibly pass that far because it is -1 and that
// will fail in msgpack_read_uint64. But kSDItemMissing may and it will
// otherwise be skipped because (1 << 0) will never appear in flags.
semsg(_(RCERR "Error while reading ShaDa file: "
"there is an item at position %" PRIu64 " "
"that must not be there: Missing items are "
"for internal uses only"),
initial_fpos);
return kSDReadStatusNotShaDa;
}
if ((type_u64 > SHADA_LAST_ENTRY
? !(flags & kSDReadUnknown)
: !((unsigned)(1 << type_u64) & flags))
|| (max_kbyte && length > max_kbyte * 1024)) {
// First entry is unknown or equal to "\n" (10)? Most likely this means that
// current file is not a ShaDa file because first item should normally be
// a header (excluding tests where first item is tested item). Check this by
// parsing entry contents: in non-ShaDa files this will most likely result
// in incomplete MessagePack string.
if (initial_fpos == 0
&& (type_u64 == '\n' || type_u64 > SHADA_LAST_ENTRY)) {
verify_but_ignore = true;
} else {
const ShaDaReadResult srs_ret = sd_reader_skip(sd_reader, length);
if (srs_ret != kSDReadStatusSuccess) {
return srs_ret;
}
goto shada_read_next_item_start;
}
}
const uint64_t parse_pos = sd_reader->bytes_read;
bool buf_allocated = false;
// try to avoid allocation for small items which fits entirely
// in the internal buffer of sd_reader
char *buf = file_try_read_buffered(sd_reader, length);
if (!buf) {
buf_allocated = true;
buf = xmalloc(length);
const ShaDaReadResult fl_ret = fread_len(sd_reader, buf, length);
if (fl_ret != kSDReadStatusSuccess) {
ret = fl_ret;
goto shada_read_next_item_error;
}
}
const char *read_ptr = buf;
size_t read_size = length;
if (verify_but_ignore) {
int status = unpack_skip(&read_ptr, &read_size);
ShaDaReadResult spm_ret = shada_check_status(parse_pos, status, read_size);
if (buf_allocated) {
xfree(buf);
}
if (spm_ret != kSDReadStatusSuccess) {
return spm_ret;
}
goto shada_read_next_item_start;
}
if (type_u64 > SHADA_LAST_ENTRY) {
entry->type = kSDItemUnknown;
entry->data.unknown_item.size = length;
entry->data.unknown_item.type = type_u64;
if (initial_fpos == 0) {
int status = unpack_skip(&read_ptr, &read_size);
ShaDaReadResult spm_ret = shada_check_status(parse_pos, status, read_size);
if (spm_ret != kSDReadStatusSuccess) {
if (buf_allocated) {
xfree(buf);
}
entry->type = kSDItemMissing;
return spm_ret;
}
}
entry->data.unknown_item.contents = buf_allocated ? buf : xmemdup(buf, length);
return kSDReadStatusSuccess;
}
entry->data = sd_default_values[type_u64].data;
switch ((ShadaEntryType)type_u64) {
case kSDItemHeader:
// TODO(bfredl): header is written to file and provides useful debugging
// info. It is never read by nvim (earlier we parsed it back to a
// Dictionary, but that value was never used)
break;
case kSDItemSearchPattern: {
Dict(_shada_search_pat) *it = &entry->data.search_pattern;
if (!unpack_keydict(it, DictHash(_shada_search_pat), &ad, &read_ptr, &read_size,
&error_alloc)) {
semsg(_(READERR("search pattern", "%s")), initial_fpos, error_alloc);
it->pat = NULL_STRING;
goto shada_read_next_item_error;
}
if (!HAS_KEY(it, _shada_search_pat, sp)) { // SEARCH_KEY_PAT
semsg(_(READERR("search pattern", "has no pattern")), initial_fpos);
goto shada_read_next_item_error;
}
entry->data.search_pattern.pat = copy_string(entry->data.search_pattern.pat, NULL);
break;
}
case kSDItemChange:
case kSDItemJump:
case kSDItemGlobalMark:
case kSDItemLocalMark: {
Dict(_shada_mark) it = { 0 };
if (!unpack_keydict(&it, DictHash(_shada_mark), &ad, &read_ptr, &read_size, &error_alloc)) {
semsg(_(READERR("mark", "%s")), initial_fpos, error_alloc);
goto shada_read_next_item_error;
}
if (HAS_KEY(&it, _shada_mark, n)) {
if (type_u64 == kSDItemJump || type_u64 == kSDItemChange) {
semsg(_(READERR("mark", "has n key which is only valid for "
"local and global mark entries")), initial_fpos);
goto shada_read_next_item_error;
}
entry->data.filemark.name = (char)it.n;
}
if (HAS_KEY(&it, _shada_mark, l)) {
entry->data.filemark.mark.lnum = (linenr_T)it.l;
}
if (HAS_KEY(&it, _shada_mark, c)) {
entry->data.filemark.mark.col = (colnr_T)it.c;
}
if (HAS_KEY(&it, _shada_mark, f)) {
entry->data.filemark.fname = xmemdupz(it.f.data, it.f.size);
}
if (entry->data.filemark.fname == NULL) {
semsg(_(READERR("mark", "is missing file name")), initial_fpos);
goto shada_read_next_item_error;
}
if (entry->data.filemark.mark.lnum <= 0) {
semsg(_(READERR("mark", "has invalid line number")), initial_fpos);
goto shada_read_next_item_error;
}
if (entry->data.filemark.mark.col < 0) {
semsg(_(READERR("mark", "has invalid column number")), initial_fpos);
goto shada_read_next_item_error;
}
break;
}
case kSDItemRegister: {
Dict(_shada_register) it = { 0 };
if (!unpack_keydict(&it, DictHash(_shada_register), &ad, &read_ptr, &read_size, &error_alloc)) {
semsg(_(READERR("register", "%s")), initial_fpos, error_alloc);
kv_destroy(it.rc);
goto shada_read_next_item_error;
}
if (it.rc.size == 0) {
semsg(_(READERR("register",
"has " KEY_NAME(REG_KEY_CONTENTS) " key with missing or empty array")),
initial_fpos);
goto shada_read_next_item_error;
}
entry->data.reg.contents_size = it.rc.size;
entry->data.reg.contents = xmalloc(it.rc.size * sizeof(char *));
for (size_t j = 0; j < it.rc.size; j++) {
entry->data.reg.contents[j] = xmemdupz(it.rc.items[j].data, it.rc.items[j].size);
}
kv_destroy(it.rc);
#define REGISTER_VAL(name, loc, type) \
if (HAS_KEY(&it, _shada_register, name)) { \
loc = (type)it.name; \
}
REGISTER_VAL(REG_KEY_UNNAMED, entry->data.reg.is_unnamed, bool)
REGISTER_VAL(REG_KEY_TYPE, entry->data.reg.type, uint8_t)
REGISTER_VAL(KEY_NAME_CHAR, entry->data.reg.name, char)
REGISTER_VAL(REG_KEY_WIDTH, entry->data.reg.width, size_t)
break;
}
case kSDItemHistoryEntry: {
ssize_t len = unpack_array(&read_ptr, &read_size);
if (len < 2) {
semsg(_(READERR("history", "is not an array with enough elements")), initial_fpos);
goto shada_read_next_item_error;
}
Integer hist_type;
if (!unpack_integer(&read_ptr, &read_size, &hist_type)) {
semsg(_(READERR("history", "has wrong history type type")), initial_fpos);
goto shada_read_next_item_error;
}
const String item = unpack_string(&read_ptr, &read_size);
if (!item.data) {
semsg(_(READERR("history", "has wrong history string type")), initial_fpos);
goto shada_read_next_item_error;
}
if (memchr(item.data, 0, item.size) != NULL) {
semsg(_(READERR("history", "contains string with zero byte inside")), initial_fpos);
goto shada_read_next_item_error;
}
entry->data.history_item.histtype = (uint8_t)hist_type;
const bool is_hist_search = entry->data.history_item.histtype == HIST_SEARCH;
if (is_hist_search) {
if (len < 3) {
semsg(_(READERR("search history",
"does not have separator character")), initial_fpos);
goto shada_read_next_item_error;
}
Integer sep_type;
if (!unpack_integer(&read_ptr, &read_size, &sep_type)) {
semsg(_(READERR("search history", "has wrong history separator type")), initial_fpos);
goto shada_read_next_item_error;
}
entry->data.history_item.sep = (char)sep_type;
}
size_t strsize = (item.size
+ 1 // Zero byte
+ 1); // Separator character
entry->data.history_item.string = xmalloc(strsize);
memcpy(entry->data.history_item.string, item.data, item.size);
entry->data.history_item.string[strsize - 2] = 0;
entry->data.history_item.string[strsize - 1] = entry->data.history_item.sep;
read_additional_array_elements = (uint32_t)(len - (2 + is_hist_search));
break;
}
case kSDItemVariable: {
ssize_t len = unpack_array(&read_ptr, &read_size);
if (len < 2) {
semsg(_(READERR("variable", "is not an array with enough elements")), initial_fpos);
goto shada_read_next_item_error;
}
String name = unpack_string(&read_ptr, &read_size);
if (!name.data) {
semsg(_(READERR("variable", "has wrong variable name type")), initial_fpos);
goto shada_read_next_item_error;
}
entry->data.global_var.name = xmemdupz(name.data, name.size);
String binval = unpack_string(&read_ptr, &read_size);
bool is_blob = false;
if (binval.data) {
if (len > 2) {
// A msgpack BIN could be a String or Blob; an additional VAR_TYPE_BLOB
// element is stored with Blobs which can be used to differentiate them
Integer type;
if (!unpack_integer(&read_ptr, &read_size, &type) || type != VAR_TYPE_BLOB) {
semsg(_(READERR("variable", "has wrong variable type")),
initial_fpos);
goto shada_read_next_item_error;
}
is_blob = true;
}
entry->data.global_var.value = decode_string(binval.data, binval.size, is_blob, false);
} else {
int status = unpack_typval(&read_ptr, &read_size, &entry->data.global_var.value);
if (status != MPACK_OK) {
semsg(_(READERR("variable", "has value that cannot "
"be converted to the Vimscript value")), initial_fpos);
goto shada_read_next_item_error;
}
}
read_additional_array_elements = (uint32_t)(len - 2 - (is_blob ? 1 : 0));
break;
}
case kSDItemSubString: {
ssize_t len = unpack_array(&read_ptr, &read_size);
if (len < 1) {
semsg(_(READERR("sub string", "is not an array with enough elements")), initial_fpos);
goto shada_read_next_item_error;
}
String sub = unpack_string(&read_ptr, &read_size);
if (!sub.data) {
semsg(_(READERR("sub string", "has wrong sub string type")), initial_fpos);
goto shada_read_next_item_error;
}
entry->data.sub_string.sub = xmemdupz(sub.data, sub.size);
read_additional_array_elements = (uint32_t)(len - 1);
break;
}
case kSDItemBufferList: {
ssize_t len = unpack_array(&read_ptr, &read_size);
if (len < 0) {
semsg(_(READERR("buffer list", "is not an array")), initial_fpos);
goto shada_read_next_item_error;
}
if (len == 0) {
break;
}
entry->data.buffer_list.buffers = xcalloc((size_t)len,
sizeof(*entry->data.buffer_list.buffers));
for (size_t i = 0; i < (size_t)len; i++) {
entry->data.buffer_list.size++;
Dict(_shada_buflist_item) it = { 0 };
AdditionalDataBuilder it_ad = KV_INITIAL_VALUE;
if (!unpack_keydict(&it, DictHash(_shada_buflist_item), &it_ad, &read_ptr, &read_size,
&error_alloc)) {
semsg(_(RERR "Error while reading ShaDa file: "
"buffer list at position %" PRIu64 " contains entry that %s"),
initial_fpos, error_alloc);
kv_destroy(it_ad);
goto shada_read_next_item_error;
}
struct buffer_list_buffer *e = &entry->data.buffer_list.buffers[i];
e->additional_data = (AdditionalData *)it_ad.items;
e->pos = default_pos;
if (HAS_KEY(&it, _shada_buflist_item, l)) {
e->pos.lnum = (linenr_T)it.l;
}
if (HAS_KEY(&it, _shada_buflist_item, c)) {
e->pos.col = (colnr_T)it.c;
}
if (HAS_KEY(&it, _shada_buflist_item, f)) {
e->fname = xmemdupz(it.f.data, it.f.size);
}
if (e->pos.lnum <= 0) {
semsg(_(RERR "Error while reading ShaDa file: "
"buffer list at position %" PRIu64 " "
"contains entry with invalid line number"),
initial_fpos);
goto shada_read_next_item_error;
}
if (e->pos.col < 0) {
semsg(_(RERR "Error while reading ShaDa file: "
"buffer list at position %" PRIu64 " "
"contains entry with invalid column number"),
initial_fpos);
goto shada_read_next_item_error;
}
if (e->fname == NULL) {
semsg(_(RERR "Error while reading ShaDa file: "
"buffer list at position %" PRIu64 " "
"contains entry that does not have a file name"),
initial_fpos);
goto shada_read_next_item_error;
}
}
break;
}
case kSDItemMissing:
case kSDItemUnknown:
abort();
}
for (uint32_t i = 0; i < read_additional_array_elements; i++) {
const char *item_start = read_ptr;
int status = unpack_skip(&read_ptr, &read_size);
if (status) {
goto shada_read_next_item_error;
}
push_additional_data(&ad, item_start, (size_t)(read_ptr - item_start));
}
if (read_size) {
semsg(_(READERR("item", "additional bytes")), initial_fpos);
goto shada_read_next_item_error;
}
entry->type = (ShadaEntryType)type_u64;
entry->additional_data = (AdditionalData *)ad.items;
ret = kSDReadStatusSuccess;
shada_read_next_item_end:
if (buf_allocated) {
xfree(buf);
}
return ret;
shada_read_next_item_error:
entry->type = (ShadaEntryType)type_u64;
shada_free_shada_entry(entry);
entry->type = kSDItemMissing;
xfree(error_alloc);
kv_destroy(ad);
goto shada_read_next_item_end;
}
/// Check whether "name" is on removable media (according to 'shada')
///
/// @param[in] name Checked name.
///
/// @return True if it is, false otherwise.
static bool shada_removable(const char *name)
FUNC_ATTR_WARN_UNUSED_RESULT
{
char part[MAXPATHL + 1];
bool retval = false;
char *new_name = home_replace_save(NULL, name);
for (char *p = p_shada; *p;) {
copy_option_part(&p, part, ARRAY_SIZE(part), ", ");
if (part[0] == 'r') {
home_replace(NULL, part + 1, NameBuff, MAXPATHL, true);
size_t n = strlen(NameBuff);
if (mb_strnicmp(NameBuff, new_name, n) == 0) {
retval = true;
break;
}
}
}
xfree(new_name);
return retval;
}
/// Initialize ShaDa jumplist entries.
///
/// @param[in,out] jumps Array of ShaDa entries to set.
/// @param[in] removable_bufs Cache of buffers ignored due to their
/// location.
///
/// @return number of jumplist entries
static inline size_t shada_init_jumps(PossiblyFreedShadaEntry *jumps,
Set(ptr_t) *const removable_bufs)
{
// Initialize jump list
size_t jumps_size = 0;
const void *jump_iter = NULL;
setpcmark();
cleanup_jumplist(curwin, false);
do {
xfmark_T fm;
jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm);
if (fm.fmark.mark.lnum == 0) {
siemsg("ShaDa: mark lnum zero (ji:%p, js:%p, len:%i)",
(void *)jump_iter, (void *)&curwin->w_jumplist[0],
curwin->w_jumplistlen);
continue;
}
const buf_T *const buf = (fm.fmark.fnum == 0
? NULL
: buflist_findnr(fm.fmark.fnum));
if (buf != NULL
? set_has(ptr_t, removable_bufs, (ptr_t)buf)
: fm.fmark.fnum != 0) {
continue;
}
const char *const fname =
(fm.fmark.fnum == 0 ? (fm.fname == NULL ? NULL : fm.fname) : buf ? buf->b_ffname : NULL);
if (fname == NULL) {
continue;
}
jumps[jumps_size++] = (PossiblyFreedShadaEntry) {
.can_free_entry = false,
.data = {
.type = kSDItemJump,
.timestamp = fm.fmark.timestamp,
.data = {
.filemark = {
.name = NUL,
.mark = fm.fmark.mark,
.fname = (char *)fname,
}
},
.additional_data = fm.fmark.additional_data,
}
};
} while (jump_iter != NULL);
return jumps_size;
}
/// Write registers ShaDa entries in given msgpack_sbuffer.
///
/// @param[in] sbuf target msgpack_sbuffer to write to.
String shada_encode_regs(void)
FUNC_ATTR_NONNULL_ALL
{
WriteMergerState *const wms = xcalloc(1, sizeof(*wms));
shada_initialize_registers(wms, -1);
PackerBuffer packer = packer_string_buffer();
for (size_t i = 0; i < ARRAY_SIZE(wms->registers); i++) {
if (wms->registers[i].data.type == kSDItemRegister) {
if (kSDWriteFailed
== shada_pack_pfreed_entry(&packer, wms->registers[i], 0)) {
abort();
}
}
}
xfree(wms);
return packer_take_string(&packer);
}
/// Write jumplist ShaDa entries in given msgpack_sbuffer.
///
/// @param[in] sbuf target msgpack_sbuffer to write to.
String shada_encode_jumps(void)
FUNC_ATTR_NONNULL_ALL
{
Set(ptr_t) removable_bufs = SET_INIT;
find_removable_bufs(&removable_bufs);
PossiblyFreedShadaEntry jumps[JUMPLISTSIZE];
size_t jumps_size = shada_init_jumps(jumps, &removable_bufs);
PackerBuffer packer = packer_string_buffer();
for (size_t i = 0; i < jumps_size; i++) {
if (kSDWriteFailed == shada_pack_pfreed_entry(&packer, jumps[i], 0)) {
abort();
}
}
return packer_take_string(&packer);
}
/// Write buffer list ShaDa entry in given msgpack_sbuffer.
///
/// @param[in] sbuf target msgpack_sbuffer to write to.
String shada_encode_buflist(void)
FUNC_ATTR_NONNULL_ALL
{
Set(ptr_t) removable_bufs = SET_INIT;
find_removable_bufs(&removable_bufs);
ShadaEntry buflist_entry = shada_get_buflist(&removable_bufs);
PackerBuffer packer = packer_string_buffer();
if (kSDWriteFailed == shada_pack_entry(&packer, buflist_entry, 0)) {
abort();
}
xfree(buflist_entry.data.buffer_list.buffers);
return packer_take_string(&packer);
}
/// Write global variables ShaDa entries in given msgpack_sbuffer.
///
/// @param[in] sbuf target msgpack_sbuffer to write to.
String shada_encode_gvars(void)
FUNC_ATTR_NONNULL_ALL
{
PackerBuffer packer = packer_string_buffer();
const void *var_iter = NULL;
const Timestamp cur_timestamp = os_time();
do {
typval_T vartv;
const char *name = NULL;
var_iter = var_shada_iter(var_iter, &name, &vartv,
VAR_FLAVOUR_DEFAULT | VAR_FLAVOUR_SESSION | VAR_FLAVOUR_SHADA);
if (name == NULL) {
break;
}
if (vartv.v_type != VAR_FUNC && vartv.v_type != VAR_PARTIAL) {
typval_T tgttv;
tv_copy(&vartv, &tgttv);
ShaDaWriteResult r = shada_pack_entry(&packer, (ShadaEntry) {
.type = kSDItemVariable,
.timestamp = cur_timestamp,
.data = {
.global_var = {
.name = (char *)name,
.value = tgttv,
}
},
.additional_data = NULL,
}, 0);
if (kSDWriteFailed == r) {
abort();
}
tv_clear(&tgttv);
}
tv_clear(&vartv);
} while (var_iter != NULL);
return packer_take_string(&packer);
}
/// Read ShaDa from String.
///
/// @param[in] string string to read from.
/// @param[in] flags Flags, see ShaDaReadFileFlags enum.
void shada_read_string(String string, const int flags)
FUNC_ATTR_NONNULL_ALL
{
if (string.size == 0) {
return;
}
FileDescriptor sd_reader;
file_open_buffer(&sd_reader, string.data, string.size);
shada_read(&sd_reader, flags);
close_file(&sd_reader);
}