Subject: bug#9879: 23.3; making script executable with tramp

From: Michael Albinus <[email-masked]>
Date: Wed, 16 Nov 2011 20:59:56 +0100

Michael Albinus <[email-masked]> writes:

> You are working on "/u/gildea/xx.sh", which is a symlink to
> "/home/vax7/u19/gildea/xx.sh". You have written "/u/gildea/xx.sh". Tramp
> tries to check "/home/vax7/u19/gildea/xx.sh" file properties afterwards,
> and it decides that this file doesn't exist.
>
> This is likely because of Tramp's cache, which hasn't been refreshed
> accordingly for the symlink's target after writing.
>
> I will check how to fix this; unfortunately there is no simple patch. It
> might take a day, or two.

The following two patches shall solve the problem, could you, please
verify?

The patches are for Emacs 23.3, however, the line numbers might be a
little bit different.

--8<---------------cut here---------------start------------->8---
Index: tramp.el
===================================================================
RCS file: /sources/tramp/tramp/lisp/tramp.el,v
retrieving revision 2.814.2.16
diff -c -r2.814.2.16 tramp.el
*** tramp.el	12 Oct 2011 14:56:21 -0000	2.814.2.16
--- tramp.el	16 Nov 2011 19:50:48 -0000
***************
*** 2671,2776 ****
  (defun tramp-handle-file-truename (filename &optional counter prev-dirs)
    "Like `file-truename' for Tramp files."
    (with-parsed-tramp-file-name (expand-file-name filename) nil
!     (with-file-property v localname "file-truename"
!       (let ((result nil))			; result steps in reverse order
! 	(tramp-message v 4 "Finding true name for `%s'" filename)
! 	(cond
! 	 ;; Use GNU readlink --canonicalize-missing where available.
! 	 ((tramp-get-remote-readlink v)
! 	  (setq result
! 		(tramp-send-command-and-read
! 		 v
! 		 (format "echo \"\\\"`%s --canonicalize-missing %s`\\\"\""
! 			 (tramp-get-remote-readlink v)
! 			 (tramp-shell-quote-argument localname)))))
! 
! 	 ;; Use Perl implementation.
! 	 ((and (tramp-get-remote-perl v)
! 	       (tramp-get-connection-property v "perl-file-spec" nil)
! 	       (tramp-get-connection-property v "perl-cwd-realpath" nil))
! 	  (tramp-maybe-send-script
! 	   v tramp-perl-file-truename "tramp_perl_file_truename")
! 	  (setq result
! 		(tramp-send-command-and-read
! 		 v
! 		 (format "tramp_perl_file_truename %s"
! 			 (tramp-shell-quote-argument localname)))))
! 
! 	 ;; Do it yourself.  We bind `directory-sep-char' here for
! 	 ;; XEmacs on Windows, which would otherwise use backslash.
! 	 (t (let* ((directory-sep-char ?/)
! 		   (steps (tramp-compat-split-string localname "/"))
! 		   (localnamedir (tramp-run-real-handler
! 				  'file-name-as-directory (list localname)))
! 		   (is-dir (string= localname localnamedir))
! 		   (thisstep nil)
! 		   (numchase 0)
! 		   ;; Don't make the following value larger than
! 		   ;; necessary.  People expect an error message in a
! 		   ;; timely fashion when something is wrong;
! 		   ;; otherwise they might think that Emacs is hung.
! 		   ;; Of course, correctness has to come first.
! 		   (numchase-limit 20)
! 		   symlink-target)
! 	      (while (and steps (< numchase numchase-limit))
! 		(setq thisstep (pop steps))
! 		(tramp-message
! 		 v 5 "Check %s"
! 		 (mapconcat 'identity
! 			    (append '("") (reverse result) (list thisstep))
! 			    "/"))
! 		(setq symlink-target
! 		      (nth 0 (file-attributes
! 			      (tramp-make-tramp-file-name
! 			       method user host
! 			       (mapconcat 'identity
! 					  (append '("")
! 						  (reverse result)
! 						  (list thisstep))
! 					  "/")))))
! 		(cond ((string= "." thisstep)
! 		       (tramp-message v 5 "Ignoring step `.'"))
! 		      ((string= ".." thisstep)
! 		       (tramp-message v 5 "Processing step `..'")
! 		       (pop result))
! 		      ((stringp symlink-target)
! 		       ;; It's a symlink, follow it.
! 		       (tramp-message v 5 "Follow symlink to %s" symlink-target)
! 		       (setq numchase (1+ numchase))
! 		       (when (file-name-absolute-p symlink-target)
! 			 (setq result nil))
! 		       ;; If the symlink was absolute, we'll get a string like
! 		       ;; "/user@host:/some/target"; extract the
! 		       ;; "/some/target" part from it.
! 		       (when (tramp-tramp-file-p symlink-target)
! 			 (unless (tramp-equal-remote filename symlink-target)
! 			   (tramp-error
! 			    v 'file-error
! 			    "Symlink target `%s' on wrong host" symlink-target))
! 			 (setq symlink-target localname))
! 		       (setq steps
! 			     (append (tramp-compat-split-string
! 				      symlink-target "/")
! 				     steps)))
! 		      (t
! 		       ;; It's a file.
! 		       (setq result (cons thisstep result)))))
! 	      (when (>= numchase numchase-limit)
! 		(tramp-error
! 		 v 'file-error
! 		 "Maximum number (%d) of symlinks exceeded" numchase-limit))
! 	      (setq result (reverse result))
! 	      ;; Combine list to form string.
! 	      (setq result
! 		    (if result
! 			(mapconcat 'identity (cons "" result) "/")
! 		      "/"))
! 	      (when (and is-dir (or (string= "" result)
! 				    (not (string= (substring result -1) "/"))))
! 		(setq result (concat result "/"))))))
  
!         (tramp-message v 4 "True name of `%s' is `%s'" filename result)
!         (tramp-make-tramp-file-name method user host result)))))
  
  ;; Basic functions.
  
--- 2671,2780 ----
  (defun tramp-handle-file-truename (filename &optional counter prev-dirs)
    "Like `file-truename' for Tramp files."
    (with-parsed-tramp-file-name (expand-file-name filename) nil
!     (tramp-make-tramp-file-name method user host
!       (with-file-property v localname "file-truename"
! 	(let ((result nil))			; result steps in reverse order
! 	  (tramp-message v 4 "Finding true name for `%s'" filename)
! 	  (cond
! 	   ;; Use GNU readlink --canonicalize-missing where available.
! 	   ((tramp-get-remote-readlink v)
! 	    (setq result
! 		  (tramp-send-command-and-read
! 		   v
! 		   (format "echo \"\\\"`%s --canonicalize-missing %s`\\\"\""
! 			   (tramp-get-remote-readlink v)
! 			   (tramp-shell-quote-argument localname)))))
! 
! 	   ;; Use Perl implementation.
! 	   ((and (tramp-get-remote-perl v)
! 		 (tramp-get-connection-property v "perl-file-spec" nil)
! 		 (tramp-get-connection-property v "perl-cwd-realpath" nil))
! 	    (tramp-maybe-send-script
! 	     v tramp-perl-file-truename "tramp_perl_file_truename")
! 	    (setq result
! 		  (tramp-send-command-and-read
! 		   v
! 		   (format "tramp_perl_file_truename %s"
! 			   (tramp-shell-quote-argument localname)))))
! 
! 	   ;; Do it yourself.  We bind `directory-sep-char' here for
! 	   ;; XEmacs on Windows, which would otherwise use backslash.
! 	   (t (let* ((directory-sep-char ?/)
! 		     (steps (tramp-compat-split-string localname "/"))
! 		     (localnamedir (tramp-run-real-handler
! 				    'file-name-as-directory (list localname)))
! 		     (is-dir (string= localname localnamedir))
! 		     (thisstep nil)
! 		     (numchase 0)
! 		     ;; Don't make the following value larger than
! 		     ;; necessary.  People expect an error message in
! 		     ;; a timely fashion when something is wrong;
! 		     ;; otherwise they might think that Emacs is hung.
! 		     ;; Of course, correctness has to come first.
! 		     (numchase-limit 20)
! 		     symlink-target)
! 		(while (and steps (< numchase numchase-limit))
! 		  (setq thisstep (pop steps))
! 		  (tramp-message
! 		   v 5 "Check %s"
! 		   (mapconcat 'identity
! 			      (append '("") (reverse result) (list thisstep))
! 			      "/"))
! 		  (setq symlink-target
! 			(nth 0 (file-attributes
! 				(tramp-make-tramp-file-name
! 				 method user host
! 				 (mapconcat 'identity
! 					    (append '("")
! 						    (reverse result)
! 						    (list thisstep))
! 					    "/")))))
! 		  (cond ((string= "." thisstep)
! 			 (tramp-message v 5 "Ignoring step `.'"))
! 			((string= ".." thisstep)
! 			 (tramp-message v 5 "Processing step `..'")
! 			 (pop result))
! 			((stringp symlink-target)
! 			 ;; It's a symlink, follow it.
! 			 (tramp-message
! 			  v 5 "Follow symlink to %s" symlink-target)
! 			 (setq numchase (1+ numchase))
! 			 (when (file-name-absolute-p symlink-target)
! 			   (setq result nil))
! 			 ;; If the symlink was absolute, we'll get a
! 			 ;; string like "/user@host:/some/target";
! 			 ;; extract the "/some/target" part from it.
! 			 (when (tramp-tramp-file-p symlink-target)
! 			   (unless (tramp-equal-remote filename symlink-target)
! 			     (tramp-error
! 			      v 'file-error
! 			      "Symlink target `%s' on wrong host"
! 			      symlink-target))
! 			   (setq symlink-target localname))
! 			 (setq steps
! 			       (append (tramp-compat-split-string
! 					symlink-target "/")
! 				       steps)))
! 			(t
! 			 ;; It's a file.
! 			 (setq result (cons thisstep result)))))
! 		(when (>= numchase numchase-limit)
! 		  (tramp-error
! 		   v 'file-error
! 		   "Maximum number (%d) of symlinks exceeded" numchase-limit))
! 		(setq result (reverse result))
! 		;; Combine list to form string.
! 		(setq result
! 		      (if result
! 			  (mapconcat 'identity (cons "" result) "/")
! 			"/"))
! 		(when (and is-dir
! 			   (or (string= "" result)
! 			       (not (string= (substring result -1) "/"))))
! 		  (setq result (concat result "/"))))))
  
! 	  (tramp-message v 4 "True name of `%s' is `%s'" filename result)
! 	  result)))))
  
  ;; Basic functions.
  
--8<---------------cut here---------------end--------------->8---

--8<---------------cut here---------------start------------->8---
Index: tramp-cache.el
===================================================================
RCS file: /sources/tramp/tramp/lisp/tramp-cache.el,v
retrieving revision 2.57.2.1
diff -c -r2.57.2.1 tramp-cache.el
*** tramp-cache.el	24 Jan 2011 10:09:40 -0000	2.57.2.1
--- tramp-cache.el	16 Nov 2011 19:54:34 -0000
***************
*** 145,150 ****
--- 145,155 ----
  
  (defun tramp-flush-file-property (vec file)
    "Remove all properties of FILE in the cache context of VEC."
+   ;; Remove file property of symlinks.
+   (let ((truename (tramp-get-file-property vec file "file-truename" nil)))
+     (when (and (stringp truename)
+ 	       (not (string-equal file truename)))
+       (tramp-flush-file-property vec truename)))
    ;; Unify localname.
    (setq vec (copy-sequence vec))
    (aset vec 3 (tramp-run-real-handler 'directory-file-name (list file)))
--8<---------------cut here---------------end--------------->8---

Best regards, Michael.