diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 3471106afd..baafe8cbea 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -901,7 +901,7 @@ A jump table for the options with a short description can be found at |Q_op|. again not rename the file. *'backupdir'* *'bdir'* -'backupdir' 'bdir' string (default "$XDG_DATA_HOME/nvim/backup") +'backupdir' 'bdir' string (default ".,$XDG_DATA_HOME/nvim/backup") global List of directories for the backup file, separated with commas. - The backup file will be created in the first directory in the list @@ -6695,8 +6695,8 @@ A jump table for the options with a short description can be found at |Q_op|. global Alias for 'term', see above. - *'undodir'* *'udir'* -'undodir' 'udir' string (default ".") + *'undodir'* *'udir'* *E926* +'undodir' 'udir' string (default "$XDG_DATA_HOME/nvim/undo") global {only when compiled with the |+persistent_undo| feature} List of directory names for undo files, separated with commas. @@ -6705,8 +6705,10 @@ A jump table for the options with a short description can be found at |Q_op|. "file.txt" is ".file.txt.un~". For other directories the file name is the full path of the edited file, with path separators replaced with "%". - When writing: The first directory that exists is used. "." always - works, no directories after "." will be used for writing. + When writing: The first directory that exists is used. "." always + works, no directories after "." will be used for writing. If none of + the directories exist Neovim will attempt to create last directory in + the list. When reading all entries are tried to find an undo file. The first undo file that exists is used. When it cannot be read an error is given, no further entry is used. diff --git a/src/nvim/eval.c b/src/nvim/eval.c index a1ac713a75..7685e422fc 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -16678,10 +16678,11 @@ static void f_undofile(typval_T *argvars, typval_T *rettv) /* If there is no file name there will be no undo file. */ rettv->vval.v_string = NULL; } else { - char_u *ffname = (char_u *)FullName_save((char *)fname, FALSE); + char *ffname = FullName_save((char *)fname, false); - if (ffname != NULL) - rettv->vval.v_string = u_get_undo_file_name(ffname, FALSE); + if (ffname != NULL) { + rettv->vval.v_string = (char_u *)u_get_undo_file_name(ffname, false); + } xfree(ffname); } } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index e160281145..70cf5fd029 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7193,7 +7193,7 @@ static void ex_wundo(exarg_T *eap) char_u hash[UNDO_HASH_SIZE]; u_compute_hash(hash); - u_write_undo(eap->arg, eap->forceit, curbuf, hash); + u_write_undo((char *) eap->arg, eap->forceit, curbuf, hash); } static void ex_rundo(exarg_T *eap) @@ -7201,7 +7201,7 @@ static void ex_rundo(exarg_T *eap) char_u hash[UNDO_HASH_SIZE]; u_compute_hash(hash); - u_read_undo(eap->arg, hash, NULL); + u_read_undo((char *) eap->arg, hash, NULL); } /* diff --git a/src/nvim/memline.c b/src/nvim/memline.c index dbb24e67a1..85f545f960 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -2990,7 +2990,7 @@ static void ml_lineadd(buf_T *buf, int count) * If it worked returns OK and the resolved link in "buf[MAXPATHL]". * Otherwise returns FAIL. */ -int resolve_symlink(char_u *fname, char_u *buf) +int resolve_symlink(const char_u *fname, char_u *buf) { char_u tmp[MAXPATHL]; int ret; diff --git a/src/nvim/option.c b/src/nvim/option.c index f30d10d0bb..cc4df28837 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -685,9 +685,13 @@ void set_init_1(void) #endif false); + char *backupdir = stdpaths_user_data_subpath("backup", 0); + const size_t backupdir_len = strlen(backupdir); + backupdir = xrealloc(backupdir, backupdir_len + 3); + memmove(backupdir + 2, backupdir, backupdir_len + 1); + memmove(backupdir, ".,", 2); set_string_default("viewdir", stdpaths_user_data_subpath("view", 0), true); - set_string_default("backupdir", stdpaths_user_data_subpath("backup", 0), - true); + set_string_default("backupdir", backupdir, true); set_string_default("directory", stdpaths_user_data_subpath("swap", 2), true); set_string_default("undodir", stdpaths_user_data_subpath("undo", 0), true); // Set default for &runtimepath. All necessary expansions are performed in diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c index ce374828f9..c9631a434c 100644 --- a/src/nvim/os/stdpaths.c +++ b/src/nvim/os/stdpaths.c @@ -25,7 +25,7 @@ static const char *const xdg_defaults[] = { [kXDGConfigHome] = "$LOCALAPPDATA\\nvim\\config", [kXDGDataHome] = "$LOCALAPPDATA\\nvim\\data", [kXDGCacheHome] = "$LOCALAPPDATA\\nvim\\cache", - [kXDGRuntimeDir] = "", + [kXDGRuntimeDir] = NULL, [kXDGConfigDirs] = NULL, [kXDGDataDirs] = NULL, #else @@ -33,7 +33,7 @@ static const char *const xdg_defaults[] = { [kXDGConfigHome] = "~/.config", [kXDGDataHome] = "~/.local/share", [kXDGCacheHome] = "~/.cache", - [kXDGRuntimeDir] = "", + [kXDGRuntimeDir] = NULL, [kXDGConfigDirs] = "/etc/xdg/", [kXDGDataDirs] = "/usr/local/share/:/usr/share/", #endif @@ -82,7 +82,7 @@ static char *get_xdg_home(const XDGVarType idx) /// /// @return [allocated] `$XDG_CONFIG_HOME/nvim/{fname}` char *stdpaths_user_conf_subpath(const char *fname) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { return concat_fnames_realloc(get_xdg_home(kXDGConfigHome), fname, true); } @@ -95,7 +95,7 @@ char *stdpaths_user_conf_subpath(const char *fname) /// @return [allocated] `$XDG_DATA_HOME/nvim/{fname}` char *stdpaths_user_data_subpath(const char *fname, const size_t trailing_pathseps) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { char *ret = concat_fnames_realloc(get_xdg_home(kXDGDataHome), fname, true); if (trailing_pathseps) { diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 2b0ffefa7e..d8158bf7cd 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -638,64 +638,89 @@ void u_compute_hash(char_u *hash) sha256_finish(&ctx, hash); } -/* - * Return an allocated string of the full path of the target undofile. - * When "reading" is TRUE find the file to read, go over all directories in - * 'undodir'. - * When "reading" is FALSE use the first name where the directory exists. - * Returns NULL when there is no place to write or no file to read. - */ -char_u *u_get_undo_file_name(char_u *buf_ffname, int reading) +/// Return an allocated string of the full path of the target undofile. +/// +/// @param[in] buf_ffname Full file name for which undo file location should +/// be found. +/// @param[in] reading If true, find the file to read by traversing all of the +/// directories in &undodir. If false use the first +/// existing directory. If none of the directories in +/// &undodir option exist then last directory in the list +/// will be automatically created. +/// +/// @return [allocated] File name to read from/write to or NULL. +char *u_get_undo_file_name(const char *const buf_ffname, const bool reading) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - char_u *dirp; - char_u dir_name[IOSIZE + 1]; - char_u *munged_name = NULL; - char_u *undo_file_name = NULL; - char_u *p; - char_u *ffname = buf_ffname; + char *dirp; + char dir_name[MAXPATHL + 1]; + char *munged_name = NULL; + char *undo_file_name = NULL; + const char *ffname = buf_ffname; #ifdef HAVE_READLINK - char_u fname_buf[MAXPATHL]; + char fname_buf[MAXPATHL]; #endif - if (ffname == NULL) + if (ffname == NULL) { return NULL; + } #ifdef HAVE_READLINK - /* Expand symlink in the file name, so that we put the undo file with the - * actual file instead of with the symlink. */ - if (resolve_symlink(ffname, fname_buf) == OK) + // Expand symlink in the file name, so that we put the undo file with the + // actual file instead of with the symlink. + if (resolve_symlink((const char_u *)ffname, (char_u *)fname_buf) == OK) { ffname = fname_buf; + } #endif - /* Loop over 'undodir'. When reading find the first file that exists. - * When not reading use the first directory that exists or ".". */ - dirp = p_udir; + // Loop over 'undodir'. When reading find the first file that exists. + // When not reading use the first directory that exists or ".". + dirp = (char *) p_udir; while (*dirp != NUL) { - size_t dir_len = copy_option_part(&dirp, dir_name, IOSIZE, ","); + size_t dir_len = copy_option_part((char_u **)&dirp, (char_u *)dir_name, + MAXPATHL, ","); if (dir_len == 1 && dir_name[0] == '.') { - /* Use same directory as the ffname, - * "dir/name" -> "dir/.name.un~" */ - undo_file_name = vim_strnsave(ffname, STRLEN(ffname) + 5); - p = path_tail(undo_file_name); - memmove(p + 1, p, STRLEN(p) + 1); - *p = '.'; - STRCAT(p, ".un~"); + // Use same directory as the ffname, + // "dir/name" -> "dir/.name.un~" + const size_t ffname_len = strlen(ffname); + undo_file_name = xmalloc(ffname_len + 6); + memmove(undo_file_name, ffname, ffname_len + 1); + char *const tail = (char *) path_tail((char_u *) undo_file_name); + const size_t tail_len = strlen(tail); + memmove(tail + 1, tail, tail_len + 1); + *tail = '.'; + memmove(tail + tail_len + 1, ".un~", sizeof(".un~")); } else { dir_name[dir_len] = NUL; - if (os_isdir(dir_name)) { - if (munged_name == NULL) { - munged_name = vim_strsave(ffname); - for (p = munged_name; *p != NUL; mb_ptr_adv(p)) - if (vim_ispathsep(*p)) - *p = '%'; + bool has_directory = os_isdir((char_u *)dir_name); + if (!has_directory && *dirp == NUL && !reading) { + // Last directory in the list does not exist, create it. + int ret; + char *failed_dir; + if ((ret = os_mkdir_recurse(dir_name, 0755, &failed_dir)) != 0) { + EMSG3(_("E926: Unable to create directory \"%s\" for undo file: %s"), + failed_dir, os_strerror(ret)); + xfree(failed_dir); + } else { + has_directory = true; } - undo_file_name = (char_u *)concat_fnames((char *)dir_name, (char *)munged_name, TRUE); + } + if (has_directory) { + if (munged_name == NULL) { + munged_name = xstrdup(ffname); + for (char *p = munged_name; *p != NUL; mb_ptr_adv(p)) { + if (vim_ispathsep(*p)) { + *p = '%'; + } + } + } + undo_file_name = concat_fnames(dir_name, munged_name, true); } } // When reading check if the file exists. - if (undo_file_name != NULL && - (!reading || os_file_exists(undo_file_name))) { + if (undo_file_name != NULL + && (!reading || os_file_exists((char_u *)undo_file_name))) { break; } xfree(undo_file_name); @@ -706,7 +731,13 @@ char_u *u_get_undo_file_name(char_u *buf_ffname, int reading) return undo_file_name; } -static void corruption_error(char *mesg, char_u *file_name) +/// Display an error for corrupted undo file +/// +/// @param[in] mesg Identifier of the corruption kind. +/// @param[in] file_name File in which error occurred. +static void corruption_error(const char *const mesg, + const char *const file_name) + FUNC_ATTR_NONNULL_ALL { EMSG3(_("E825: Corrupted undo file (%s): %s"), mesg, file_name); } @@ -821,7 +852,8 @@ static bool serialize_uhp(bufinfo_T *bi, u_header_T *uhp) return true; } -static u_header_T *unserialize_uhp(bufinfo_T *bi, char_u *file_name) +static u_header_T *unserialize_uhp(bufinfo_T *bi, + const char *file_name) { u_header_T *uhp = xmalloc(sizeof(u_header_T)); memset(uhp, 0, sizeof(u_header_T)); @@ -918,7 +950,8 @@ static bool serialize_uep(bufinfo_T *bi, u_entry_T *uep) return true; } -static u_entry_T *unserialize_uep(bufinfo_T * bi, bool *error, char_u *file_name) +static u_entry_T *unserialize_uep(bufinfo_T * bi, bool *error, + const char *file_name) { u_entry_T *uep = xmalloc(sizeof(u_entry_T)); memset(uep, 0, sizeof(u_entry_T)); @@ -1000,19 +1033,20 @@ static void unserialize_visualinfo(bufinfo_T *bi, visualinfo_T *info) info->vi_curswant = undo_read_4c(bi); } -/* - * Write the undo tree in an undo file. - * When "name" is not NULL, use it as the name of the undo file. - * Otherwise use buf->b_ffname to generate the undo file name. - * "buf" must never be null, buf->b_ffname is used to obtain the original file - * permissions. - * "forceit" is TRUE for ":wundo!", FALSE otherwise. - * "hash[UNDO_HASH_SIZE]" must be the hash value of the buffer text. - */ -void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) +/// Write the undo tree in an undo file. +/// +/// @param[in] name Name of the undo file or NULL if this function needs to +/// generate the undo file name based on buf->b_ffname. +/// @param[in] forceit True for `:wundo!`, false otherwise. +/// @param[in] buf Buffer for which undo file is written. +/// @param[in] hash Hash value of the buffer text. Must have #UNDO_HASH_SIZE +/// size. +void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, + char_u *const hash) + FUNC_ATTR_NONNULL_ARG(3, 4) { u_header_T *uhp; - char_u *file_name; + char *file_name; int mark; #ifdef U_DEBUG int headers_written = 0; @@ -1024,7 +1058,7 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) bufinfo_T bi; if (name == NULL) { - file_name = u_get_undo_file_name(buf->b_ffname, FALSE); + file_name = u_get_undo_file_name((char *) buf->b_ffname, false); if (file_name == NULL) { if (p_verbose > 0) { verbose_enter(); @@ -1033,8 +1067,9 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) } return; } - } else - file_name = name; + } else { + file_name = (char *) name; + } /* * Decide about the permission to use for the undo file. If the buffer @@ -1054,10 +1089,10 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) /* If the undo file already exists, verify that it actually is an undo * file, and delete it. */ - if (os_file_exists(file_name)) { + if (os_file_exists((char_u *)file_name)) { if (name == NULL || !forceit) { /* Check we can read it and it's an undo file. */ - fd = os_open((char *)file_name, O_RDONLY, 0); + fd = os_open(file_name, O_RDONLY, 0); if (fd < 0) { if (name != NULL || p_verbose > 0) { if (name == NULL) @@ -1086,7 +1121,7 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) } } } - os_remove((char *)file_name); + os_remove(file_name); } /* If there is no undo information at all, quit here after deleting any @@ -1097,13 +1132,12 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) goto theend; } - fd = os_open((char *)file_name, - O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, perm); + fd = os_open(file_name, O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, perm); if (fd < 0) { EMSG2(_(e_not_open), file_name); goto theend; } - (void)os_setperm(file_name, perm); + (void)os_setperm((char_u *)file_name, perm); if (p_verbose > 0) { verbose_enter(); smsg(_("Writing undo file: %s"), file_name); @@ -1125,10 +1159,10 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) FileInfo file_info_new; if (buf->b_ffname != NULL && os_fileinfo((char *)buf->b_ffname, &file_info_old) - && os_fileinfo((char *)file_name, &file_info_new) + && os_fileinfo(file_name, &file_info_new) && file_info_old.stat.st_gid != file_info_new.stat.st_gid && os_fchown(fd, (uv_uid_t)-1, (uv_gid_t)file_info_old.stat.st_gid)) { - os_setperm(file_name, (perm & 0707) | ((perm & 07) << 3)); + os_setperm((char_u *)file_name, (perm & 0707) | ((perm & 07) << 3)); } # ifdef HAVE_SELINUX if (buf->b_ffname != NULL) @@ -1140,7 +1174,7 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) if (fp == NULL) { EMSG2(_(e_not_open), file_name); close(fd); - os_remove((char *)file_name); + os_remove(file_name); goto theend; } @@ -1209,7 +1243,7 @@ write_error: /* For systems that support ACL: get the ACL from the original file. */ acl = mch_get_acl(buf->b_ffname); - mch_set_acl(file_name, acl); + mch_set_acl((char_u *)file_name, acl); mch_free_acl(acl); } #endif @@ -1224,15 +1258,15 @@ theend: /// a bit more verbose. /// Otherwise use curbuf->b_ffname to generate the undo file name. /// "hash[UNDO_HASH_SIZE]" must be the hash value of the buffer text. -void u_read_undo(char_u *name, char_u *hash, char_u *orig_name) +void u_read_undo(char *name, char_u *hash, char_u *orig_name) FUNC_ATTR_NONNULL_ARG(2) { u_header_T **uhp_table = NULL; char_u *line_ptr = NULL; - char_u *file_name; + char *file_name; if (name == NULL) { - file_name = u_get_undo_file_name(curbuf->b_ffname, TRUE); + file_name = u_get_undo_file_name((char *) curbuf->b_ffname, true); if (file_name == NULL) { return; } @@ -1256,7 +1290,7 @@ void u_read_undo(char_u *name, char_u *hash, char_u *orig_name) } #endif } else { - file_name = name; + file_name = (char *) name; } if (p_verbose > 0) { @@ -1265,7 +1299,7 @@ void u_read_undo(char_u *name, char_u *hash, char_u *orig_name) verbose_leave(); } - FILE *fp = mch_fopen((char *)file_name, "r"); + FILE *fp = mch_fopen(file_name, "r"); if (fp == NULL) { if (name != NULL || p_verbose > 0) { EMSG2(_("E822: Cannot open undo file for reading: %s"), file_name);