Subject: Re: bug#61326: [DRAFT PATCH v3] Work around zip's filename extension limitation (was: Adding --no-add-suffix to zip patch)

From: Ruijie Yu <[email-masked]>
Date: Mon, 13 Feb 2023 18:35:17 +0800

I believe the functionalities (both update and deletion) are done in
this patch.  I have not added any tests nor updates on documentation, as
I am not sure what and where they should be.

I have done some basic tests locally to confirm that it works:
- update subfile in a zip archive named "z"
- delete subfile in a zip archive named "z"
- update subfile in a zip archive named "x.zip"
- delete subfile in a zip archive named "x.zip"

FTR, if this is of any importance: the filesystem containing the
archives "z" and "x.zip" is btrfs, and /tmp is ext2 on zram.  Not sure
if any problems would arise in other filesystems / OSes -- hopefully
someone else on those platforms can confirm that it works.

Comments welcome.  Particularly on my choices of naming.

>From 0766bec8071c7c793d6c81211577832d9a7a78f9 Mon Sep 17 00:00:00 2001
From: Ruijie Yu <[email-masked]>
Date: Thu, 9 Feb 2023 00:45:19 +0800
Subject: [PATCH] lisp/arc-mode.el Work around zip's filename limitations on
 extension

[DRAFT PATCH]

Fixes 61326.  The "zip" executable requires that the named archive must have
an extension, else it attaches ".zip" to the supplied file name, causing
incorrect behaviors.  This patch looks for such scenarios and temporarily
rename extension-less archives so that "zip" would function correctly.

Currently there are no tests.  I might need some pointers as to where existing
tests are and how to write them.
---
 lisp/arc-mode.el | 64 ++++++++++++++++++++++++++++++++++++------------
 1 file changed, 48 insertions(+), 16 deletions(-)

diff --git a/lisp/arc-mode.el b/lisp/arc-mode.el
index 6f3e922880d..875b3971086 100644
--- a/lisp/arc-mode.el
+++ b/lisp/arc-mode.el
@@ -645,6 +645,49 @@ archive-get-descr
       (if (not noerror)
           (error "Line does not describe a member of the archive")))))
 ;; -------------------------------------------------------------------------
+;;; Section: Helper functions for requiring filename extensions
+
+(defun archive--act-files (command files)
+  (lambda (archive)
+    (apply #'call-process (car command)
+           nil nil nil (append (cdr command) (cons archive files)))))
+
+(defun archive--need-rename-p (&optional archive)
+  (let ((archive
+         (file-name-nondirectory (or archive buffer-file-name))))
+    (cl-case archive-subtype
+      ((zip) (not (seq-contains-p archive ?. #'eq))))))
+
+(defun archive--ensure-extension (archive ensure-extension)
+  (if ensure-extension
+      (make-temp-name (expand-file-name (concat archive "_tmp.")))
+    archive))
+
+(defun archive--maybe-rename (newname need-rename-p)
+  ;; Operating with archive as current buffer, and protect
+  ;; `default-directory' from being modified in `rename-visited-file'.
+  (when need-rename-p
+    (let ((default-directory default-directory))
+      (rename-visited-file newname))))
+
+(defun archive--with-ensure-extension (archive proc-fn)
+  (let ((saved default-directory))
+    (with-current-buffer (find-buffer-visiting archive)
+      (let ((ensure-extension (archive--need-rename-p))
+            (default-directory saved))
+        (unwind-protect
+            ;; Some archive programs (like zip) expect filenames to
+            ;; have an extension, so if necessary, temporarily rename
+            ;; an extensionless file for write accesses.
+            (let ((archive (archive--ensure-extension
+                            archive ensure-extension)))
+              (archive--maybe-rename archive ensure-extension)
+              (let ((exitcode (funcall proc-fn archive)))
+                (or (zerop exitcode)
+                    (error "Updating was unsuccessful (%S)" exitcode))))
+          (progn (archive--maybe-rename archive ensure-extension)
+                 (revert-buffer nil t)))))))
+;; -------------------------------------------------------------------------
 ;;; Section: the mode definition

 ;;;###autoload
@@ -1376,16 +1419,9 @@ archive-*-write-file-member
      (setq ename
        (encode-coding-string ename archive-file-name-coding-system))
           (let* ((coding-system-for-write 'no-conversion)
-        (default-directory (file-name-as-directory archive-tmpdir))
-        (exitcode (apply #'call-process
-                 (car command)
-                 nil
-                 nil
-                 nil
-                 (append (cdr command)
-                     (list archive ename)))))
-            (or (zerop exitcode)
-       (error "Updating was unsuccessful (%S)" exitcode))))
+                 (default-directory (file-name-as-directory archive-tmpdir)))
+            (archive--with-ensure-extension
+             archive (archive--act-files command (list ename)))))
       (archive-delete-local tmpfile))))

 (defun archive-write-file (&optional file)
@@ -1539,12 +1575,8 @@ archive-expunge
         (revert-buffer))))))

 (defun archive-*-expunge (archive files command)
-  (apply #'call-process
-    (car command)
-    nil
-    nil
-    nil
-    (append (cdr command) (cons archive files))))
+  (archive--with-ensure-extension
+   archive (archive--act-files command files)))

 (defun archive-rename-entry (newname)
   "Change the name associated with this entry in the archive file."
--
2.39.1

--
Best,

RY