aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobin Haberkorn <rhaberkorn@fmsbw.de>2025-12-09 01:12:57 +0100
committerRobin Haberkorn <rhaberkorn@fmsbw.de>2025-12-09 01:12:57 +0100
commita886813f86f88b4b1cc874a81229b7b59d0d463a (patch)
treeb8eec4c0f1363e0aecf06651bcb9b9230e1e677c
parent4aaff7294131552be8c731e2f4d230106a1149f7 (diff)
fixed rub out of file writes to non-existing symlinks
* teco_file_get_absolute_path() does not currently guarantee to resolve non-existent parts of the path. When opening a symbolic link to a non-existing file, it would only be created when writing out the file. The undo token to remove it, however would remove the original unresolved path. When rubbing out the EW, the symlink would get removed instead of the newly written file. * We now resolve/canonicalize the path again immediately after opening the new file, which should ensure that it resolves. * As an alternative, we might have also tried to reliably canonicalize non-existent symlinks. This however is tricky and there would have to be a break condition to guard against cyclic symlinke. In the end there is no guarantee to be able to resolve a path exactly like the OS does. Therefore, teco_file_get_absolute_path() was not touched, not even on UNIX. * A test case was not added since it would rely on creating real symlinks. It wouldn't work on MSYS when `ln -s` falls back to hardlinks. Perhaps other non-UNIX platforms would have similar restrictions.
-rw-r--r--src/file-utils.c13
-rw-r--r--src/file-utils.h4
-rw-r--r--src/view.c19
3 files changed, 27 insertions, 9 deletions
diff --git a/src/file-utils.c b/src/file-utils.c
index 75bcb48..db06ff3 100644
--- a/src/file-utils.c
+++ b/src/file-utils.c
@@ -109,6 +109,14 @@ teco_file_set_attributes(const gchar *filename, teco_file_attributes_t attrs)
#ifdef G_OS_UNIX
+/*
+ * NOTE: This version does not resolve symlinks to non-existing paths.
+ * It could be improved by repeating readlink() and g_canonicalize_filename(),
+ * but it would require glib v2.58.0.
+ * Alternatively we could also iteratively resolve all path components.
+ * Currently, we simply do not rely on successful canonicalization of
+ * yet non-existing paths.
+ */
gchar *
teco_file_get_absolute_path(const gchar *path)
{
@@ -137,13 +145,12 @@ teco_file_is_visible(const gchar *path)
#if GLIB_CHECK_VERSION(2,58,0)
/*
- * FIXME: This should perhaps be preferred on any platform.
- * But it will complicate preprocessing.
+ * NOTE: Does not resolve symlinks.
*/
gchar *
teco_file_get_absolute_path(const gchar *path)
{
- return g_canonicalize_filename(path, NULL);
+ return path ? g_canonicalize_filename(path, NULL) : NULL;
}
#else /* !GLIB_CHECK_VERSION(2,58,0) */
diff --git a/src/file-utils.h b/src/file-utils.h
index 12a9b83..9a2f8d6 100644
--- a/src/file-utils.h
+++ b/src/file-utils.h
@@ -32,9 +32,7 @@ void teco_file_set_attributes(const gchar *filename, teco_file_attributes_t attr
/**
* Get absolute/full version of a possibly relative path.
* The path is tried to be canonicalized so it does
- * not contain relative components.
- * Works with existing and non-existing paths (in the latter case,
- * heuristics may be applied).
+ * not contain relative components and symlinks.
* Depending on platform and existence of the path,
* canonicalization might fail, but the path returned is
* always absolute.
diff --git a/src/view.c b/src/view.c
index 702c5b6..53199bd 100644
--- a/src/view.c
+++ b/src/view.c
@@ -550,16 +550,16 @@ teco_view_save_to_file(teco_view_t *ctx, const gchar *filename, GError **error)
file_stat.st_gid = -1;
#endif
teco_file_attributes_t attributes = TECO_FILE_INVALID_ATTRIBUTES;
+ gboolean undo_remove_file = FALSE;
if (teco_undo_enabled) {
- if (g_file_test(filename, G_FILE_TEST_IS_REGULAR)) {
+ undo_remove_file = !g_file_test(filename, G_FILE_TEST_IS_REGULAR);
+ if (!undo_remove_file) {
#ifdef G_OS_UNIX
g_stat(filename, &file_stat);
#endif
attributes = teco_file_get_attributes(filename);
teco_make_savepoint(filename);
- } else {
- teco_undo_remove_file_push(filename);
}
}
@@ -568,6 +568,18 @@ teco_view_save_to_file(teco_view_t *ctx, const gchar *filename, GError **error)
if (!channel)
return FALSE;
+ if (undo_remove_file) {
+ /*
+ * The file is new, so has to be removed on undo.
+ * If `filename` is a symlink, it's crucial to resolve it now,
+ * since early canonicalization may have failed (for non-existent
+ * path segments).
+ * Now, `filename` is guaranteed to exist.
+ */
+ g_autofree gchar *filename_canon = teco_file_get_absolute_path(filename);
+ teco_undo_remove_file_push(filename_canon);
+ }
+
/*
* teco_view_save_to_channel() expects a buffered and blocking channel
*/
@@ -576,6 +588,7 @@ teco_view_save_to_file(teco_view_t *ctx, const gchar *filename, GError **error)
if (!teco_view_save_to_channel(ctx, channel, error)) {
g_prefix_error(error, "Error writing file \"%s\": ", filename);
+ /* file might also be removed (in interactive mode) */
return FALSE;
}