aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Albinus2018-12-16 15:49:07 +0100
committerMichael Albinus2018-12-16 15:49:07 +0100
commite8199e765f81968be840d8e7e3978f5974c1be9d (patch)
tree354c9b7af9c126a49e614b716225c49135d702b4
parent4f230e8dd551d711433e2a8095a19c2ce00c9b4c (diff)
downloademacs-e8199e765f81968be840d8e7e3978f5974c1be9d.tar.gz
emacs-e8199e765f81968be840d8e7e3978f5974c1be9d.zip
Add Tramp sudoedit method
* doc/misc/tramp.texi (Quick Start Guide): New section "Using sudoedit". (External methods) <sudoedit>: Describe. * lisp/net/tramp-adb.el (tramp-adb-file-name-handler-alist): * lisp/net/tramp-gvfs.el (tramp-gvfs-file-name-handler-alist): * lisp/net/tramp-rclone.el (tramp-rclone-file-name-handler-alist): * lisp/net/tramp-sh.el (tramp-sh-file-name-handler-alist) * lisp/net/tramp-smb.el (tramp-smb-file-name-handler-alist): Add handler. * lisp/net/tramp-sh.el (tramp-sh-handle-set-file-uid-gid): Rename from `tramp-sh-handle-set-file-uid-gid'. Handle only remote file names. * lisp/net/tramp-sudoedit.el: New file. * lisp/net/tramp.el (tramp-file-name-for-operation): Handle also `tramp-set-file-uid-gid'. (tramp-set-file-uid-gid): New defun. (tramp-get-local-uid, tramp-get-local-gid): Cache result. * test/lisp/net/tramp-tests.el (tramp--test-sudoedit-p): New defun. (tramp-test20-file-modes, tramp-test22-file-times) (tramp--test-sudoedit-p): Use it.
-rw-r--r--doc/misc/tramp.texi37
-rw-r--r--etc/NEWS6
-rw-r--r--lisp/net/tramp-adb.el1
-rw-r--r--lisp/net/tramp-archive.el1
-rw-r--r--lisp/net/tramp-cache.el7
-rw-r--r--lisp/net/tramp-gvfs.el3
-rw-r--r--lisp/net/tramp-rclone.el3
-rw-r--r--lisp/net/tramp-sh.el43
-rw-r--r--lisp/net/tramp-smb.el1
-rw-r--r--lisp/net/tramp-sudoedit.el880
-rw-r--r--lisp/net/tramp.el47
-rw-r--r--test/lisp/net/tramp-tests.el10
12 files changed, 995 insertions, 44 deletions
diff --git a/doc/misc/tramp.texi b/doc/misc/tramp.texi
index a4946f0b8de..c9f1e75d8e6 100644
--- a/doc/misc/tramp.texi
+++ b/doc/misc/tramp.texi
@@ -468,6 +468,19 @@ The method @option{sg} stands for ``switch group''; the changed group
468must be used here as user name. The default host name is the same. 468must be used here as user name. The default host name is the same.
469 469
470 470
471@anchor{Quick Start Guide: @option{sudoedit} method}
472@section Using @command{sudoedit}
473@cindex method @option{sudoedit}
474@cindex @option{sudoedit} method
475
476The @option{sudoedit} method is similar to the @option{sudo} method.
477However, it is a different implementation: it does not keep an open
478session running in the background. This is for security reasons; on
479the backside this method is less performant than the @option{sudo}
480method, it is restricted to the @samp{localhost} only, and it does not
481support external processes.
482
483
471@anchor{Quick Start Guide: @option{smb} method} 484@anchor{Quick Start Guide: @option{smb} method}
472@section Using @command{smbclient} 485@section Using @command{smbclient}
473@cindex method @option{smb} 486@cindex method @option{smb}
@@ -919,6 +932,30 @@ NAS hosts. These dumb devices have severely restricted local shells,
919such as the @command{busybox} and do not host any other encode or 932such as the @command{busybox} and do not host any other encode or
920decode programs. 933decode programs.
921 934
935@item @option{sudoedit}
936@cindex method @option{sudoedit}
937@cindex @option{sudoedit} method
938
939The @option{sudoedit} method allows to edit a file as a different user
940on the local host. You could regard this as @value{tramp}'s
941implementation of the @command{sudoedit}. Contrary to the
942@option{sudo} method, all magic file name functions are implemented by
943single @command{sudo @dots{}} commands. The purpose is to make
944editing such a file as secure as possible; there must be no session
945running in the Emacs background which could be attacked from inside
946Emacs.
947
948Consequently, external processes are not implemented.
949
950The host name of such remote file names must represent the local host.
951Since the default value is already proper, it is recommended not to
952use any host name in the remote file name, like
953@file{@trampfn{sudoedit,,/path/to/file}} or
954@file{@trampfn{sudoedit,user@@,/path/to/file}}.
955
956Like the @option{sudo} method, a @option{sudoedit} password expires
957after a predefined timeout.
958
922@item @option{ftp} 959@item @option{ftp}
923@cindex method @option{ftp} 960@cindex method @option{ftp}
924@cindex @option{ftp} method 961@cindex @option{ftp} method
diff --git a/etc/NEWS b/etc/NEWS
index 0624c5690bc..c88f6ef5ca4 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -865,6 +865,12 @@ or NextCloud hosted files and directories.
865storages via the 'rclone' program. This feature is experimental. 865storages via the 'rclone' program. This feature is experimental.
866 866
867+++ 867+++
868*** New connection method "sudoedit", which allows to edit local files
869with different user credentials. Contrary to the "sudo" method, no
870session is run permanently in the background. This is for security
871reasons.
872
873+++
868*** Connection methods "obex" and "synce" are removed, because they 874*** Connection methods "obex" and "synce" are removed, because they
869are obsoleted in GVFS. 875are obsoleted in GVFS.
870 876
diff --git a/lisp/net/tramp-adb.el b/lisp/net/tramp-adb.el
index 7906ec9f7cf..7bf709b79a1 100644
--- a/lisp/net/tramp-adb.el
+++ b/lisp/net/tramp-adb.el
@@ -161,6 +161,7 @@ It is used for TCP/IP devices."
161 (start-file-process . tramp-adb-handle-start-file-process) 161 (start-file-process . tramp-adb-handle-start-file-process)
162 (substitute-in-file-name . tramp-handle-substitute-in-file-name) 162 (substitute-in-file-name . tramp-handle-substitute-in-file-name)
163 (temporary-file-directory . tramp-handle-temporary-file-directory) 163 (temporary-file-directory . tramp-handle-temporary-file-directory)
164 (tramp-set-file-uid-gid . ignore)
164 (unhandled-file-name-directory . ignore) 165 (unhandled-file-name-directory . ignore)
165 (vc-registered . ignore) 166 (vc-registered . ignore)
166 (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime) 167 (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime)
diff --git a/lisp/net/tramp-archive.el b/lisp/net/tramp-archive.el
index cb072ac720f..02580359f73 100644
--- a/lisp/net/tramp-archive.el
+++ b/lisp/net/tramp-archive.el
@@ -273,6 +273,7 @@ It must be supported by libarchive(3).")
273 (start-file-process . tramp-archive-handle-not-implemented) 273 (start-file-process . tramp-archive-handle-not-implemented)
274 ;; `substitute-in-file-name' performed by default handler. 274 ;; `substitute-in-file-name' performed by default handler.
275 (temporary-file-directory . tramp-archive-handle-temporary-file-directory) 275 (temporary-file-directory . tramp-archive-handle-temporary-file-directory)
276 ;; `tramp-set-file-uid-gid' performed by default handler.
276 (unhandled-file-name-directory . ignore) 277 (unhandled-file-name-directory . ignore)
277 (vc-registered . ignore) 278 (vc-registered . ignore)
278 (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime) 279 (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime)
diff --git a/lisp/net/tramp-cache.el b/lisp/net/tramp-cache.el
index 0a799d721d6..d13e6ee9f58 100644
--- a/lisp/net/tramp-cache.el
+++ b/lisp/net/tramp-cache.el
@@ -50,10 +50,11 @@
50;; definitions already sent to the remote shell, "last-cmd-time" is 50;; definitions already sent to the remote shell, "last-cmd-time" is
51;; the time stamp a command has been sent to the remote process. 51;; the time stamp a command has been sent to the remote process.
52;; 52;;
53;; - The key is `nil'. This are temporary properties related to the 53;; - The key is nil. This are temporary properties related to the
54;; local machine. Examples: "parse-passwd" and "parse-group" keep 54;; local machine. Examples: "parse-passwd" and "parse-group" keep
55;; the results of parsing "/etc/passwd" and "/etc/group", "locale" 55;; the results of parsing "/etc/passwd" and "/etc/group",
56;; is the used shell locale. 56;; "{uid,gid}-{integer,string}" are the local uid and gid, and
57;; "locale" is the used shell locale.
57 58
58;; Some properties are handled special: 59;; Some properties are handled special:
59;; 60;;
diff --git a/lisp/net/tramp-gvfs.el b/lisp/net/tramp-gvfs.el
index e034f7bba56..295b288d06e 100644
--- a/lisp/net/tramp-gvfs.el
+++ b/lisp/net/tramp-gvfs.el
@@ -589,6 +589,7 @@ It has been changed in GVFS 1.14.")
589 (start-file-process . ignore) 589 (start-file-process . ignore)
590 (substitute-in-file-name . tramp-handle-substitute-in-file-name) 590 (substitute-in-file-name . tramp-handle-substitute-in-file-name)
591 (temporary-file-directory . tramp-handle-temporary-file-directory) 591 (temporary-file-directory . tramp-handle-temporary-file-directory)
592 (tramp-set-file-uid-gid . ignore)
592 (unhandled-file-name-directory . ignore) 593 (unhandled-file-name-directory . ignore)
593 (vc-registered . ignore) 594 (vc-registered . ignore)
594 (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime) 595 (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime)
@@ -1843,7 +1844,7 @@ connection if a previous connection has died for some reason."
1843 (tramp-get-connection-process vec) "connected" t)))) 1844 (tramp-get-connection-process vec) "connected" t))))
1844 1845
1845 ;; In `tramp-check-cached-permissions', the connection properties 1846 ;; In `tramp-check-cached-permissions', the connection properties
1846 ;; {uig,gid}-{integer,string} are used. We set them to proper values. 1847 ;; "{uid,gid}-{integer,string}" are used. We set them to proper values.
1847 (unless tramp-gvfs-get-remote-uid-gid-in-progress 1848 (unless tramp-gvfs-get-remote-uid-gid-in-progress
1848 (let ((tramp-gvfs-get-remote-uid-gid-in-progress t)) 1849 (let ((tramp-gvfs-get-remote-uid-gid-in-progress t))
1849 (tramp-gvfs-get-remote-uid vec 'integer) 1850 (tramp-gvfs-get-remote-uid vec 'integer)
diff --git a/lisp/net/tramp-rclone.el b/lisp/net/tramp-rclone.el
index 5ea42c07bf2..18cb971bd17 100644
--- a/lisp/net/tramp-rclone.el
+++ b/lisp/net/tramp-rclone.el
@@ -134,6 +134,7 @@
134 (start-file-process . ignore) 134 (start-file-process . ignore)
135 (substitute-in-file-name . tramp-handle-substitute-in-file-name) 135 (substitute-in-file-name . tramp-handle-substitute-in-file-name)
136 (temporary-file-directory . tramp-handle-temporary-file-directory) 136 (temporary-file-directory . tramp-handle-temporary-file-directory)
137 (tramp-set-file-uid-gid . ignore)
137 (unhandled-file-name-directory . ignore) 138 (unhandled-file-name-directory . ignore)
138 (vc-registered . ignore) 139 (vc-registered . ignore)
139 (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime) 140 (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime)
@@ -575,7 +576,7 @@ connection if a previous connection has died for some reason."
575 (tramp-cleanup-connection vec 'keep-debug 'keep-password))))) 576 (tramp-cleanup-connection vec 'keep-debug 'keep-password)))))
576 577
577 ;; In `tramp-check-cached-permissions', the connection properties 578 ;; In `tramp-check-cached-permissions', the connection properties
578 ;; {uig,gid}-{integer,string} are used. We set them to proper values. 579 ;; "{uid,gid}-{integer,string}" are used. We set them to proper values.
579 (with-tramp-connection-property 580 (with-tramp-connection-property
580 vec "uid-integer" (tramp-get-local-uid 'integer)) 581 vec "uid-integer" (tramp-get-local-uid 'integer))
581 (with-tramp-connection-property 582 (with-tramp-connection-property
diff --git a/lisp/net/tramp-sh.el b/lisp/net/tramp-sh.el
index a6e9d299a87..a3038780e6c 100644
--- a/lisp/net/tramp-sh.el
+++ b/lisp/net/tramp-sh.el
@@ -1044,6 +1044,7 @@ of command line.")
1044 (start-file-process . tramp-sh-handle-start-file-process) 1044 (start-file-process . tramp-sh-handle-start-file-process)
1045 (substitute-in-file-name . tramp-handle-substitute-in-file-name) 1045 (substitute-in-file-name . tramp-handle-substitute-in-file-name)
1046 (temporary-file-directory . tramp-handle-temporary-file-directory) 1046 (temporary-file-directory . tramp-handle-temporary-file-directory)
1047 (tramp-set-file-uid-gid . tramp-sh-handle-set-file-uid-gid)
1047 (unhandled-file-name-directory . ignore) 1048 (unhandled-file-name-directory . ignore)
1048 (vc-registered . tramp-sh-handle-vc-registered) 1049 (vc-registered . tramp-sh-handle-vc-registered)
1049 (verify-visited-file-modtime . tramp-sh-handle-verify-visited-file-modtime) 1050 (verify-visited-file-modtime . tramp-sh-handle-verify-visited-file-modtime)
@@ -1516,39 +1517,26 @@ of."
1516 "") 1517 "")
1517 (tramp-shell-quote-argument localname))))))) 1518 (tramp-shell-quote-argument localname)))))))
1518 1519
1519(defun tramp-set-file-uid-gid (filename &optional uid gid) 1520(defun tramp-sh-handle-set-file-uid-gid (filename &optional uid gid)
1520 "Set the ownership for FILENAME. 1521 "Like `tramp-set-file-uid-gid' for Tramp files."
1521If UID and GID are provided, these values are used; otherwise uid
1522and gid of the corresponding user is taken. Both parameters must
1523be non-negative integers."
1524 ;; Modern Unices allow chown only for root. So we might need 1522 ;; Modern Unices allow chown only for root. So we might need
1525 ;; another implementation, see `dired-do-chown'. OTOH, it is mostly 1523 ;; another implementation, see `dired-do-chown'. OTOH, it is mostly
1526 ;; working with su(do)? when it is needed, so it shall succeed in 1524 ;; working with su(do)? when it is needed, so it shall succeed in
1527 ;; the majority of cases. 1525 ;; the majority of cases.
1528 ;; Don't modify `last-coding-system-used' by accident. 1526 ;; Don't modify `last-coding-system-used' by accident.
1529 (let ((last-coding-system-used last-coding-system-used)) 1527 (let ((last-coding-system-used last-coding-system-used))
1530 (if (tramp-tramp-file-p filename) 1528 (with-parsed-tramp-file-name filename nil
1531 (with-parsed-tramp-file-name filename nil 1529 (if (and (zerop (user-uid)) (tramp-local-host-p v))
1532 (if (and (zerop (user-uid)) (tramp-local-host-p v)) 1530 ;; If we are root on the local host, we can do it directly.
1533 ;; If we are root on the local host, we can do it directly. 1531 (tramp-set-file-uid-gid localname uid gid)
1534 (tramp-set-file-uid-gid localname uid gid) 1532 (let ((uid (or (and (natnump uid) uid)
1535 (let ((uid (or (and (natnump uid) uid) 1533 (tramp-get-remote-uid v 'integer)))
1536 (tramp-get-remote-uid v 'integer))) 1534 (gid (or (and (natnump gid) gid)
1537 (gid (or (and (natnump gid) gid) 1535 (tramp-get-remote-gid v 'integer))))
1538 (tramp-get-remote-gid v 'integer)))) 1536 (tramp-send-command
1539 (tramp-send-command 1537 v (format
1540 v (format 1538 "chown %d:%d %s" uid gid
1541 "chown %d:%d %s" uid gid 1539 (tramp-shell-quote-argument localname))))))))
1542 (tramp-shell-quote-argument localname))))))
1543
1544 ;; We handle also the local part, because there doesn't exist
1545 ;; `set-file-uid-gid'. On W32 "chown" does not work.
1546 (unless (memq system-type '(ms-dos windows-nt))
1547 (let ((uid (or (and (natnump uid) uid) (tramp-get-local-uid 'integer)))
1548 (gid (or (and (natnump gid) gid) (tramp-get-local-gid 'integer))))
1549 (tramp-call-process
1550 nil "chown" nil nil nil
1551 (format "%d:%d" uid gid) (shell-quote-argument filename)))))))
1552 1540
1553(defun tramp-remote-selinux-p (vec) 1541(defun tramp-remote-selinux-p (vec)
1554 "Check, whether SELINUX is enabled on the remote host." 1542 "Check, whether SELINUX is enabled on the remote host."
@@ -2114,6 +2102,7 @@ file names."
2114 2102
2115 ;; Handle `preserve-extended-attributes'. We ignore possible 2103 ;; Handle `preserve-extended-attributes'. We ignore possible
2116 ;; errors, because ACL strings could be incompatible. 2104 ;; errors, because ACL strings could be incompatible.
2105 ;; `set-file-extended-attributes' exists since Emacs 24.4.
2117 (when attributes 2106 (when attributes
2118 (ignore-errors 2107 (ignore-errors
2119 (apply 'set-file-extended-attributes (list newname attributes)))) 2108 (apply 'set-file-extended-attributes (list newname attributes))))
diff --git a/lisp/net/tramp-smb.el b/lisp/net/tramp-smb.el
index 5b7998ac970..fcc6f6c6ef0 100644
--- a/lisp/net/tramp-smb.el
+++ b/lisp/net/tramp-smb.el
@@ -282,6 +282,7 @@ See `tramp-actions-before-shell' for more info.")
282 (start-file-process . tramp-smb-handle-start-file-process) 282 (start-file-process . tramp-smb-handle-start-file-process)
283 (substitute-in-file-name . tramp-smb-handle-substitute-in-file-name) 283 (substitute-in-file-name . tramp-smb-handle-substitute-in-file-name)
284 (temporary-file-directory . tramp-handle-temporary-file-directory) 284 (temporary-file-directory . tramp-handle-temporary-file-directory)
285 (tramp-set-file-uid-gid . ignore)
285 (unhandled-file-name-directory . ignore) 286 (unhandled-file-name-directory . ignore)
286 (vc-registered . ignore) 287 (vc-registered . ignore)
287 (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime) 288 (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime)
diff --git a/lisp/net/tramp-sudoedit.el b/lisp/net/tramp-sudoedit.el
new file mode 100644
index 00000000000..640fa570ff4
--- /dev/null
+++ b/lisp/net/tramp-sudoedit.el
@@ -0,0 +1,880 @@
1;;; tramp-sudoedit.el --- Functions for accessing under root permissions -*- lexical-binding:t -*-
2
3;; Copyright (C) 2018 Free Software Foundation, Inc.
4
5;; Author: Michael Albinus <michael.albinus@gmx.de>
6;; Keywords: comm, processes
7;; Package: tramp
8
9;; This file is part of GNU Emacs.
10
11;; GNU Emacs is free software: you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
13;; the Free Software Foundation, either version 3 of the License, or
14;; (at your option) any later version.
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
22;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
23
24;;; Commentary:
25
26;; The "sudoedit" Tramp method allows to edit a file as a different
27;; user on the local host. Contrary to the "sudo" method, all magic
28;; file name functions are implemented by single "sudo ..." commands.
29;; The purpose is to make editing such a file as secure as possible;
30;; there must be no session running in the Emacs background which
31;; could be attacked from inside Emacs.
32
33;; Consequently, external processes are not implemented.
34
35;;; Code:
36
37(require 'tramp)
38(require 'server)
39
40;;;###tramp-autoload
41(defconst tramp-sudoedit-method "sudoedit"
42 "When this method name is used, call sudoedit for editing a file.")
43
44;;;###tramp-autoload
45(add-to-list 'tramp-methods
46 `(,tramp-sudoedit-method
47 (tramp-sudo-login (("sudo") ("-u" "%u") ("-S") ("-H")
48 ("-p" "Password:") ("--")))))
49
50;;;###tramp-autoload
51(add-to-list 'tramp-default-user-alist '("\\`sudoedit\\'" nil "root"))
52
53;;;###tramp-autoload
54(eval-after-load 'tramp
55 '(tramp-set-completion-function
56 tramp-sudoedit-method tramp-completion-function-alist-su))
57
58(defconst tramp-sudoedit-sudo-actions
59 '((tramp-password-prompt-regexp tramp-action-password)
60 (tramp-wrong-passwd-regexp tramp-action-permission-denied)
61 (tramp-process-alive-regexp tramp-sudoedit-action-sudo))
62 "List of pattern/action pairs.
63This list is used for sudo calls.
64
65See `tramp-actions-before-shell' for more info.")
66
67;;;###tramp-autoload
68(defconst tramp-sudoedit-file-name-handler-alist
69 '((access-file . ignore)
70 (add-name-to-file . tramp-sudoedit-handle-add-name-to-file)
71 (byte-compiler-base-file-name . ignore)
72 ;; `copy-directory' performed by default handler.
73 (copy-file . tramp-sudoedit-handle-copy-file)
74 (delete-directory . tramp-sudoedit-handle-delete-directory)
75 (delete-file . tramp-sudoedit-handle-delete-file)
76 (diff-latest-backup-file . ignore)
77 ;; `directory-file-name' performed by default handler.
78 (directory-files . tramp-handle-directory-files)
79 (directory-files-and-attributes
80 . tramp-handle-directory-files-and-attributes)
81 (dired-compress-file . ignore)
82 (dired-uncache . tramp-handle-dired-uncache)
83 (exec-path . ignore)
84 (expand-file-name . tramp-sudoedit-handle-expand-file-name)
85 (file-accessible-directory-p . tramp-handle-file-accessible-directory-p)
86 (file-acl . tramp-sudoedit-handle-file-acl)
87 (file-attributes . tramp-sudoedit-handle-file-attributes)
88 (file-directory-p . tramp-handle-file-directory-p)
89 (file-equal-p . tramp-handle-file-equal-p)
90 (file-executable-p . tramp-sudoedit-handle-file-executable-p)
91 (file-exists-p . tramp-sudoedit-handle-file-exists-p)
92 (file-in-directory-p . tramp-handle-file-in-directory-p)
93 (file-local-copy . tramp-handle-file-local-copy)
94 (file-modes . tramp-handle-file-modes)
95 (file-name-all-completions
96 . tramp-sudoedit-handle-file-name-all-completions)
97 (file-name-as-directory . tramp-handle-file-name-as-directory)
98 (file-name-case-insensitive-p . tramp-handle-file-name-case-insensitive-p)
99 (file-name-completion . tramp-handle-file-name-completion)
100 (file-name-directory . tramp-handle-file-name-directory)
101 (file-name-nondirectory . tramp-handle-file-name-nondirectory)
102 ;; `file-name-sans-versions' performed by default handler.
103 (file-newer-than-file-p . tramp-handle-file-newer-than-file-p)
104 (file-notify-add-watch . ignore)
105 (file-notify-rm-watch . ignore)
106 (file-notify-valid-p . ignore)
107 (file-ownership-preserved-p . ignore)
108 (file-readable-p . tramp-sudoedit-handle-file-readable-p)
109 (file-regular-p . tramp-handle-file-regular-p)
110 (file-remote-p . tramp-handle-file-remote-p)
111 (file-selinux-context . tramp-sudoedit-handle-file-selinux-context)
112 (file-symlink-p . tramp-handle-file-symlink-p)
113 (file-system-info . tramp-sudoedit-handle-file-system-info)
114 (file-truename . tramp-sudoedit-handle-file-truename)
115 (file-writable-p . tramp-sudoedit-handle-file-writable-p)
116 (find-backup-file-name . tramp-handle-find-backup-file-name)
117 ;; `get-file-buffer' performed by default handler.
118 (insert-directory . tramp-handle-insert-directory)
119 (insert-file-contents . tramp-handle-insert-file-contents)
120 (load . tramp-handle-load)
121 (make-auto-save-file-name . tramp-handle-make-auto-save-file-name)
122 (make-directory . tramp-sudoedit-handle-make-directory)
123 (make-directory-internal . ignore)
124 (make-nearby-temp-file . tramp-handle-make-nearby-temp-file)
125 (make-symbolic-link . tramp-sudoedit-handle-make-symbolic-link)
126 (process-file . ignore)
127 (rename-file . tramp-sudoedit-handle-rename-file)
128 (set-file-acl . tramp-sudoedit-handle-set-file-acl)
129 (set-file-modes . tramp-sudoedit-handle-set-file-modes)
130 (set-file-selinux-context . tramp-sudoedit-handle-set-file-selinux-context)
131 (set-file-times . tramp-sudoedit-handle-set-file-times)
132 (set-visited-file-modtime . tramp-handle-set-visited-file-modtime)
133 (shell-command . ignore)
134 (start-file-process . ignore)
135 (substitute-in-file-name . tramp-handle-substitute-in-file-name)
136 (temporary-file-directory . tramp-handle-temporary-file-directory)
137 (tramp-set-file-uid-gid . tramp-sudoedit-handle-set-file-uid-gid)
138 (unhandled-file-name-directory . ignore)
139 (vc-registered . ignore)
140 (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime)
141 (write-region . tramp-sudoedit-handle-write-region))
142 "Alist of handler functions for Tramp SUDOEDIT method.")
143
144;; It must be a `defsubst' in order to push the whole code into
145;; tramp-loaddefs.el. Otherwise, there would be recursive autoloading.
146;;;###tramp-autoload
147(defsubst tramp-sudoedit-file-name-p (filename)
148 "Check if it's a filename for SUDOEDIT."
149 (and (tramp-tramp-file-p filename)
150 (string= (tramp-file-name-method (tramp-dissect-file-name filename))
151 tramp-sudoedit-method)))
152
153;;;###tramp-autoload
154(defun tramp-sudoedit-file-name-handler (operation &rest args)
155 "Invoke the SUDOEDIT handler for OPERATION.
156First arg specifies the OPERATION, second arg is a list of arguments to
157pass to the OPERATION."
158 (let ((fn (assoc operation tramp-sudoedit-file-name-handler-alist)))
159 (if fn
160 (save-match-data (apply (cdr fn) args))
161 (tramp-run-real-handler operation args))))
162
163;;;###tramp-autoload
164(tramp-register-foreign-file-name-handler
165 'tramp-sudoedit-file-name-p 'tramp-sudoedit-file-name-handler)
166
167
168;; File name primitives.
169
170(defun tramp-sudoedit-handle-add-name-to-file
171 (filename newname &optional ok-if-already-exists)
172 "Like `add-name-to-file' for Tramp files."
173 (unless (tramp-equal-remote filename newname)
174 (with-parsed-tramp-file-name
175 (if (tramp-tramp-file-p filename) filename newname) nil
176 (tramp-error
177 v 'file-error
178 "add-name-to-file: %s"
179 "only implemented for same method, same user, same host")))
180 (with-parsed-tramp-file-name filename v1
181 (with-parsed-tramp-file-name newname v2
182 ;; Do the 'confirm if exists' thing.
183 (when (file-exists-p newname)
184 ;; What to do?
185 (if (or (null ok-if-already-exists) ; not allowed to exist
186 (and (numberp ok-if-already-exists)
187 (not (yes-or-no-p
188 (format
189 "File %s already exists; make it a link anyway? "
190 v2-localname)))))
191 (tramp-error v2 'file-already-exists newname)
192 (delete-file newname)))
193 (tramp-flush-file-properties v2 (file-name-directory v2-localname))
194 (tramp-flush-file-properties v2 v2-localname)
195 (unless
196 (tramp-sudoedit-send-command
197 v1 "ln"
198 (tramp-compat-file-name-unquote v1-localname)
199 (tramp-compat-file-name-unquote v2-localname))
200 (tramp-error
201 v1 'file-error
202 "error with add-name-to-file, see buffer `%s' for details"
203 (buffer-name))))))
204
205(defun tramp-sudoedit-do-copy-or-rename-file
206 (op filename newname &optional ok-if-already-exists keep-date
207 preserve-uid-gid preserve-extended-attributes)
208 "Copy or rename a remote file.
209OP must be `copy' or `rename' and indicates the operation to perform.
210FILENAME specifies the file to copy or rename, NEWNAME is the name of
211the new file (for copy) or the new name of the file (for rename).
212OK-IF-ALREADY-EXISTS means don't barf if NEWNAME exists already.
213KEEP-DATE means to make sure that NEWNAME has the same timestamp
214as FILENAME. PRESERVE-UID-GID, when non-nil, instructs to keep
215the uid and gid if both files are on the same host.
216PRESERVE-EXTENDED-ATTRIBUTES activates selinux and acl commands.
217
218This function is invoked by `tramp-sudoedit-handle-copy-file' and
219`tramp-sudoedit-handle-rename-file'. It is an error if OP is
220neither of `copy' and `rename'. FILENAME and NEWNAME must be
221absolute file names."
222 (unless (memq op '(copy rename))
223 (error "Unknown operation `%s', must be `copy' or `rename'" op))
224
225 (setq filename (file-truename filename))
226 (if (file-directory-p filename)
227 (progn
228 (copy-directory filename newname keep-date t)
229 (when (eq op 'rename) (delete-directory filename 'recursive)))
230
231 (let ((t1 (tramp-sudoedit-file-name-p filename))
232 (t2 (tramp-sudoedit-file-name-p newname))
233 (file-times (tramp-compat-file-attribute-modification-time
234 (file-attributes filename)))
235 (file-modes (tramp-default-file-modes filename))
236 ;; `file-extended-attributes' exists since Emacs 24.4.
237 (attributes (and preserve-extended-attributes
238 (apply 'file-extended-attributes (list filename))))
239 (sudoedit-operation
240 (cond
241 ((and (eq op 'copy) preserve-uid-gid) '("cp" "-f" "-p"))
242 ((eq op 'copy) '("cp" "-f"))
243 ((eq op 'rename) '("mv" "-f"))))
244 (msg-operation (if (eq op 'copy) "Copying" "Renaming")))
245
246 (with-parsed-tramp-file-name (if t1 filename newname) nil
247 (when (and (not ok-if-already-exists) (file-exists-p newname))
248 (tramp-error v 'file-already-exists newname))
249
250 (if (or (and (file-remote-p filename) (not t1))
251 (and (file-remote-p newname) (not t2)))
252 ;; We cannot copy or rename directly.
253 (let ((tmpfile (tramp-compat-make-temp-file filename)))
254 (if (eq op 'copy)
255 (copy-file filename tmpfile t)
256 (rename-file filename tmpfile t))
257 (rename-file tmpfile newname ok-if-already-exists))
258
259 ;; Direct action.
260 (with-tramp-progress-reporter
261 v 0 (format "%s %s to %s" msg-operation filename newname)
262 (unless (tramp-sudoedit-send-command
263 v sudoedit-operation
264 (tramp-compat-file-name-unquote
265 (tramp-compat-file-local-name filename))
266 (tramp-compat-file-name-unquote
267 (tramp-compat-file-local-name newname)))
268 (tramp-error
269 v 'file-error
270 "Error %s `%s' `%s'" msg-operation filename newname))))
271
272 ;; When `newname' is local, we must change the ownership to
273 ;; the local user.
274 (unless (file-remote-p newname)
275 (tramp-set-file-uid-gid
276 (concat (file-remote-p filename) newname)
277 (tramp-get-local-uid 'integer)
278 (tramp-get-local-gid 'integer)))
279
280 ;; Set the time and mode. Mask possible errors.
281 (when keep-date
282 (ignore-errors
283 (set-file-times newname file-times)
284 (set-file-modes newname file-modes)))
285
286 ;; Handle `preserve-extended-attributes'. We ignore possible
287 ;; errors, because ACL strings could be incompatible.
288 ;; `set-file-extended-attributes' exists since Emacs 24.4.
289 (when attributes
290 (ignore-errors
291 (apply 'set-file-extended-attributes (list newname attributes))))
292
293 (when (and t1 (eq op 'rename))
294 (with-parsed-tramp-file-name filename v1
295 (tramp-flush-file-properties
296 v1 (file-name-directory v1-localname))
297 (tramp-flush-file-properties v1 v1-localname)))
298
299 (when t2
300 (with-parsed-tramp-file-name newname v2
301 (tramp-flush-file-properties
302 v2 (file-name-directory v2-localname))
303 (tramp-flush-file-properties v2 v2-localname)
304 (when (tramp-rclone-file-name-p newname))))))))
305
306(defun tramp-sudoedit-handle-copy-file
307 (filename newname &optional ok-if-already-exists keep-date
308 preserve-uid-gid preserve-extended-attributes)
309 "Like `copy-file' for Tramp files."
310 (setq filename (expand-file-name filename))
311 (setq newname (expand-file-name newname))
312 ;; At least one file a Tramp file?
313 (if (or (tramp-tramp-file-p filename)
314 (tramp-tramp-file-p newname))
315 (tramp-sudoedit-do-copy-or-rename-file
316 'copy filename newname ok-if-already-exists keep-date
317 preserve-uid-gid preserve-extended-attributes)
318 (tramp-run-real-handler
319 'copy-file
320 (list filename newname ok-if-already-exists keep-date
321 preserve-uid-gid preserve-extended-attributes))))
322
323(defun tramp-sudoedit-handle-delete-directory
324 (directory &optional recursive trash)
325 "Like `delete-directory' for Tramp files."
326 (setq directory (expand-file-name directory))
327 (with-parsed-tramp-file-name directory nil
328 (tramp-flush-file-properties v (file-name-directory localname))
329 (tramp-flush-directory-properties v localname)
330 (unless
331 (tramp-sudoedit-send-command
332 v (or (and trash "trash")
333 (if recursive '("rm" "-rf") "rmdir"))
334 (tramp-compat-file-name-unquote localname))
335 (tramp-error v 'file-error "Couldn't delete %s" directory))))
336
337(defun tramp-sudoedit-handle-delete-file (filename &optional trash)
338 "Like `delete-file' for Tramp files."
339 (with-parsed-tramp-file-name filename nil
340 (tramp-flush-file-properties v (file-name-directory localname))
341 (tramp-flush-file-properties v localname)
342 (unless
343 (tramp-sudoedit-send-command
344 v (if (and trash delete-by-moving-to-trash) "trash" "rm")
345 (tramp-compat-file-name-unquote localname))
346 ;; Propagate the error.
347 (with-current-buffer (tramp-get-connection-buffer v)
348 (goto-char (point-min))
349 (tramp-error-with-buffer
350 nil v 'file-error "Couldn't delete %s" filename)))))
351
352(defun tramp-sudoedit-handle-expand-file-name (name &optional dir)
353 "Like `expand-file-name' for Tramp files.
354If the localname part of the given file name starts with \"/../\" then
355the result will be a local, non-Tramp, file name."
356 ;; If DIR is not given, use `default-directory' or "/".
357 (setq dir (or dir default-directory "/"))
358 ;; Unless NAME is absolute, concat DIR and NAME.
359 (unless (file-name-absolute-p name)
360 (setq name (concat (file-name-as-directory dir) name)))
361 (with-parsed-tramp-file-name name nil
362 ;; Tilde expansion if necessary. We cannot accept "~/", because
363 ;; under sudo "~/" is expanded to the local user home directory
364 ;; but to the root home directory.
365 (when (zerop (length localname))
366 (setq localname "~"))
367 (unless (file-name-absolute-p localname)
368 (setq localname (format "~%s/%s" user localname)))
369 (when (string-match "\\`\\(~[^/]*\\)\\(.*\\)\\'" localname)
370 (let ((uname (match-string 1 localname))
371 (fname (match-string 2 localname)))
372 (when (string-equal uname "~")
373 (setq uname (concat uname user)))
374 (setq localname (concat uname fname))))
375 ;; Do normal `expand-file-name' (this does "~user/", "/./" and "/../").
376 (tramp-make-tramp-file-name v (expand-file-name localname))))
377
378(defun tramp-sudoedit-remote-acl-p (vec)
379 "Check, whether ACL is enabled on the remote host."
380 (with-tramp-connection-property (tramp-get-connection-process vec) "acl-p"
381 (zerop (tramp-call-process vec "getfacl" nil nil nil "/"))))
382
383(defun tramp-sudoedit-handle-file-acl (filename)
384 "Like `file-acl' for Tramp files."
385 (with-parsed-tramp-file-name filename nil
386 (with-tramp-file-property v localname "file-acl"
387 (let ((result (and (tramp-sudoedit-remote-acl-p v)
388 (tramp-sudoedit-send-command-string
389 v "getfacl" "-acp"
390 (tramp-compat-file-name-unquote localname)))))
391 ;; The acl string must have a trailing \n, which is not
392 ;; provided by `tramp-sudoedit-send-command-string'. Add it.
393 (and (stringp result) (concat result "\n"))))))
394
395(defun tramp-sudoedit-handle-file-attributes (filename &optional id-format)
396 "Like `file-attributes' for Tramp files."
397 (unless id-format (setq id-format 'integer))
398 (with-parsed-tramp-file-name (expand-file-name filename) nil
399 (with-tramp-file-property
400 v localname (format "file-attributes-%s" id-format)
401 (tramp-message v 5 "file attributes: %s" localname)
402 (ignore-errors
403 (tramp-convert-file-attributes
404 v
405 (tramp-sudoedit-send-command-and-read
406 v "env" "QUOTING_STYLE=locale" "stat" "-c"
407 (format
408 ;; Apostrophes in the stat output are masked as
409 ;; `tramp-stat-marker', in order to make a proper shell
410 ;; escape of them in file names.
411 "((%s%%N%s) %%h %s %s %%X %%Y %%Z %%s %s%%A%s t %%i -1)"
412 tramp-stat-marker tramp-stat-marker
413 (if (eq id-format 'integer)
414 "%u"
415 (eval-when-compile
416 (concat tramp-stat-marker "%U" tramp-stat-marker)))
417 (if (eq id-format 'integer)
418 "%g"
419 (eval-when-compile
420 (concat tramp-stat-marker "%G" tramp-stat-marker)))
421 tramp-stat-marker tramp-stat-marker)
422 (tramp-compat-file-name-unquote localname)))))))
423
424(defun tramp-sudoedit-handle-file-executable-p (filename)
425 "Like `file-executable-p' for Tramp files."
426 (with-parsed-tramp-file-name filename nil
427 (with-tramp-file-property v localname "file-executable-p"
428 (tramp-sudoedit-send-command
429 v "test" "-x" (tramp-compat-file-name-unquote localname)))))
430
431(defun tramp-sudoedit-handle-file-exists-p (filename)
432 "Like `file-exists-p' for Tramp files."
433 (with-parsed-tramp-file-name filename nil
434 (with-tramp-file-property v localname "file-exists-p"
435 (tramp-sudoedit-send-command
436 v "test" "-e" (tramp-compat-file-name-unquote localname)))))
437
438(defun tramp-sudoedit-handle-file-name-all-completions (filename directory)
439 "Like `file-name-all-completions' for Tramp files."
440 (all-completions
441 filename
442 (with-parsed-tramp-file-name (expand-file-name directory) nil
443 (with-tramp-file-property v localname "file-name-all-completions"
444 (tramp-sudoedit-send-command
445 v "ls" "-a1" "--quoting-style=literal" "--show-control-chars"
446 (if (zerop (length localname))
447 "" (tramp-compat-file-name-unquote localname)))
448 (mapcar
449 (lambda (f)
450 (if (file-directory-p (expand-file-name f directory))
451 (file-name-as-directory f)
452 f))
453 (with-current-buffer (tramp-get-connection-buffer v)
454 (delq
455 nil
456 (mapcar
457 (lambda (l) (and (not (string-match-p "^[[:space:]]*$" l)) l))
458 (split-string (buffer-string) "\n" 'omit)))))))))
459
460(defun tramp-sudoedit-handle-file-readable-p (filename)
461 "Like `file-readable-p' for Tramp files."
462 (with-parsed-tramp-file-name filename nil
463 (with-tramp-file-property v localname "file-readable-p"
464 (tramp-sudoedit-send-command
465 v "test" "-r" (tramp-compat-file-name-unquote localname)))))
466
467(defun tramp-sudoedit-handle-set-file-modes (filename mode)
468 "Like `set-file-modes' for Tramp files."
469 (with-parsed-tramp-file-name filename nil
470 (tramp-flush-file-properties v (file-name-directory localname))
471 (tramp-flush-file-properties v localname)
472 (unless (tramp-sudoedit-send-command
473 v "chmod" (format "%o" mode)
474 (tramp-compat-file-name-unquote localname))
475 (tramp-error
476 v 'file-error "Error while changing file's mode %s" filename))))
477
478(defun tramp-sudoedit-remote-selinux-p (vec)
479 "Check, whether SELINUX is enabled on the remote host."
480 (with-tramp-connection-property (tramp-get-connection-process vec) "selinux-p"
481 (zerop (tramp-call-process vec "selinuxenabled"))))
482
483(defun tramp-sudoedit-handle-file-selinux-context (filename)
484 "Like `file-selinux-context' for Tramp files."
485 (with-parsed-tramp-file-name filename nil
486 (with-tramp-file-property v localname "file-selinux-context"
487 (let ((context '(nil nil nil nil))
488 (regexp (eval-when-compile
489 (concat "\\([a-z0-9_]+\\):" "\\([a-z0-9_]+\\):"
490 "\\([a-z0-9_]+\\):" "\\([a-z0-9_]+\\)"))))
491 (when (and (tramp-sudoedit-remote-selinux-p v)
492 (tramp-sudoedit-send-command
493 v "ls" "-d" "-Z"
494 (tramp-compat-file-name-unquote localname)))
495 (with-current-buffer (tramp-get-connection-buffer v)
496 (goto-char (point-min))
497 (when (re-search-forward regexp (point-at-eol) t)
498 (setq context (list (match-string 1) (match-string 2)
499 (match-string 3) (match-string 4))))))
500 ;; Return the context.
501 context))))
502
503(defun tramp-sudoedit-handle-file-system-info (filename)
504 "Like `file-system-info' for Tramp files."
505 (ignore-errors
506 (with-parsed-tramp-file-name (expand-file-name filename) nil
507 (tramp-message v 5 "file system info: %s" localname)
508 (when (tramp-sudoedit-send-command
509 v "df" "--block-size=1" "--output=size,used,avail"
510 (tramp-compat-file-name-unquote localname)))
511 (with-current-buffer (tramp-get-connection-buffer v)
512 (goto-char (point-min))
513 (forward-line)
514 (when (looking-at
515 (eval-when-compile
516 (concat "[[:space:]]*\\([[:digit:]]+\\)"
517 "[[:space:]]+\\([[:digit:]]+\\)"
518 "[[:space:]]+\\([[:digit:]]+\\)")))
519 (list (string-to-number (match-string 1))
520 ;; The second value is the used size. We need the
521 ;; free size.
522 (- (string-to-number (match-string 1))
523 (string-to-number (match-string 2)))
524 (string-to-number (match-string 3))))))))
525
526(defun tramp-sudoedit-handle-set-file-times (filename &optional time)
527 "Like `set-file-times' for Tramp files."
528 (with-parsed-tramp-file-name filename nil
529 (tramp-flush-file-properties v (file-name-directory localname))
530 (tramp-flush-file-properties v localname)
531 (let ((time
532 (if (or (null time)
533 (tramp-compat-time-equal-p time tramp-time-doesnt-exist)
534 (tramp-compat-time-equal-p time tramp-time-dont-know))
535 (current-time)
536 time)))
537 (tramp-sudoedit-send-command
538 v "env" "TZ=UTC" "touch" "-t"
539 (format-time-string "%Y%m%d%H%M.%S" time t)
540 (tramp-compat-file-name-unquote localname)))))
541
542(defun tramp-sudoedit-handle-file-truename (filename)
543 "Like `file-truename' for Tramp files."
544 ;; Preserve trailing "/".
545 (funcall
546 (if (string-equal (file-name-nondirectory filename) "")
547 'file-name-as-directory 'identity)
548 (with-parsed-tramp-file-name (expand-file-name filename) nil
549 (tramp-make-tramp-file-name
550 v
551 (with-tramp-file-property v localname "file-truename"
552 (let ((quoted (tramp-compat-file-name-quoted-p localname))
553 (localname (tramp-compat-file-name-unquote localname))
554 result)
555 (tramp-message v 4 "Finding true name for `%s'" filename)
556 (setq result (tramp-sudoedit-send-command-string
557 v "readlink" "--canonicalize-missing" localname))
558 ;; Detect cycle.
559 (when (and (file-symlink-p filename)
560 (string-equal result localname))
561 (tramp-error
562 v 'file-error
563 "Apparent cycle of symbolic links for %s" filename))
564 ;; If the resulting localname looks remote, we must quote it
565 ;; for security reasons.
566 (when (or quoted (file-remote-p result))
567 (let (file-name-handler-alist)
568 (setq result (tramp-compat-file-name-quote result))))
569 (tramp-message v 4 "True name of `%s' is `%s'" localname result)
570 result))
571 'nohop))))
572
573(defun tramp-sudoedit-handle-file-writable-p (filename)
574 "Like `file-writable-p' for Tramp files."
575 (with-parsed-tramp-file-name filename nil
576 (with-tramp-file-property v localname "file-writable-p"
577 (if (file-exists-p filename)
578 (tramp-sudoedit-send-command
579 v "test" "-w" (tramp-compat-file-name-unquote localname))
580 (let ((dir (file-name-directory filename)))
581 (and (file-exists-p dir)
582 (file-writable-p dir)))))))
583
584(defun tramp-sudoedit-handle-make-directory (dir &optional parents)
585 "Like `make-directory' for Tramp files."
586 (setq dir (expand-file-name dir))
587 (with-parsed-tramp-file-name dir nil
588 ;; When PARENTS is non-nil, DIR could be a chain of non-existent
589 ;; directories a/b/c/... Instead of checking, we simply flush the
590 ;; whole cache.
591 (tramp-flush-directory-properties
592 v (if parents "/" (file-name-directory localname)))
593 (unless (tramp-sudoedit-send-command
594 v (if parents '("mkdir" "-p") "mkdir")
595 (tramp-compat-file-name-unquote localname))
596 (tramp-error v 'file-error "Couldn't make directory %s" dir))))
597
598(defun tramp-sudoedit-handle-make-symbolic-link
599 (target linkname &optional ok-if-already-exists)
600 "Like `make-symbolic-link' for Tramp files.
601If TARGET is a non-Tramp file, it is used verbatim as the target
602of the symlink. If TARGET is a Tramp file, only the localname
603component is used as the target of the symlink."
604 (if (not (tramp-tramp-file-p (expand-file-name linkname)))
605 (tramp-run-real-handler
606 'make-symbolic-link (list target linkname ok-if-already-exists))
607
608 (with-parsed-tramp-file-name linkname nil
609 ;; If TARGET is a Tramp name, use just the localname component.
610 (when (and (tramp-tramp-file-p target)
611 (tramp-file-name-equal-p v (tramp-dissect-file-name target)))
612 (setq target
613 (tramp-file-name-localname
614 (tramp-dissect-file-name (expand-file-name target)))))
615
616 ;; If TARGET is still remote, quote it.
617 (if (tramp-tramp-file-p target)
618 (make-symbolic-link
619 (let (file-name-handler-alist) (tramp-compat-file-name-quote target))
620 linkname ok-if-already-exists)
621
622 ;; Do the 'confirm if exists' thing.
623 (when (file-exists-p linkname)
624 ;; What to do?
625 (if (or (null ok-if-already-exists) ; not allowed to exist
626 (and (numberp ok-if-already-exists)
627 (not
628 (yes-or-no-p
629 (format
630 "File %s already exists; make it a link anyway? "
631 localname)))))
632 (tramp-error v 'file-already-exists localname)
633 (delete-file linkname)))
634
635 (tramp-flush-file-properties v (file-name-directory localname))
636 (tramp-flush-file-properties v localname)
637 (tramp-sudoedit-send-command
638 v "ln" "-sf"
639 (tramp-compat-file-name-unquote target)
640 (tramp-compat-file-name-unquote localname))))))
641
642(defun tramp-sudoedit-handle-rename-file
643 (filename newname &optional ok-if-already-exists)
644 "Like `rename-file' for Tramp files."
645 (setq filename (expand-file-name filename))
646 (setq newname (expand-file-name newname))
647 ;; At least one file a Tramp file?
648 (if (or (tramp-tramp-file-p filename)
649 (tramp-tramp-file-p newname))
650 (tramp-sudoedit-do-copy-or-rename-file
651 'rename filename newname ok-if-already-exists
652 'keep-date 'preserve-uid-gid)
653 (tramp-run-real-handler
654 'rename-file (list filename newname ok-if-already-exists))))
655
656(defun tramp-sudoedit-handle-set-file-acl (filename acl-string)
657 "Like `set-file-acl' for Tramp files."
658 (with-parsed-tramp-file-name (expand-file-name filename) nil
659 (when (and (stringp acl-string) (tramp-sudoedit-remote-acl-p v))
660 ;; Massage `acl-string'.
661 (setq acl-string
662 (mapconcat 'identity (split-string acl-string "\n" 'omit) ","))
663 (prog1
664 (tramp-sudoedit-send-command
665 v "setfacl" "-m"
666 acl-string (tramp-compat-file-name-unquote localname))
667 (tramp-flush-file-property v localname "file-acl")))))
668
669(defun tramp-sudoedit-handle-set-file-selinux-context (filename context)
670 "Like `set-file-selinux-context' for Tramp files."
671 (with-parsed-tramp-file-name filename nil
672 (when (and (consp context)
673 (tramp-sudoedit-remote-selinux-p v))
674 (let ((user (and (stringp (nth 0 context)) (nth 0 context)))
675 (role (and (stringp (nth 1 context)) (nth 1 context)))
676 (type (and (stringp (nth 2 context)) (nth 2 context)))
677 (range (and (stringp (nth 3 context)) (nth 3 context))))
678 (when (tramp-sudoedit-send-command
679 v "chcon"
680 (when user (format "--user=%s" user))
681 (when role (format "--role=%s" role))
682 (when type (format "--type=%s" type))
683 (when range (format "--range=%s" range))
684 (tramp-compat-file-name-unquote localname))
685 (if (and user role type range)
686 (tramp-set-file-property
687 v localname "file-selinux-context" context)
688 (tramp-flush-file-property v localname "file-selinux-context"))
689 t)))))
690
691(defun tramp-sudoedit-get-remote-uid (vec id-format)
692 "The uid of the remote connection VEC, in ID-FORMAT.
693ID-FORMAT valid values are `string' and `integer'."
694 (with-tramp-connection-property vec (format "uid-%s" id-format)
695 (if (equal id-format 'integer)
696 (tramp-sudoedit-send-command-and-read vec "id" "-u")
697 (tramp-sudoedit-send-command-string vec "id" "-un"))))
698
699(defun tramp-sudoedit-get-remote-gid (vec id-format)
700 "The gid of the remote connection VEC, in ID-FORMAT.
701ID-FORMAT valid values are `string' and `integer'."
702 (with-tramp-connection-property vec (format "gid-%s" id-format)
703 (if (equal id-format 'integer)
704 (tramp-sudoedit-send-command-and-read vec "id" "-g")
705 (tramp-sudoedit-send-command-string vec "id" "-gn"))))
706
707(defun tramp-sudoedit-handle-set-file-uid-gid (filename &optional uid gid)
708 "Like `tramp-set-file-uid-gid' for Tramp files."
709 (with-parsed-tramp-file-name filename nil
710 (tramp-sudoedit-send-command
711 v "chown"
712 (format "%d:%d"
713 (or uid (tramp-sudoedit-get-remote-uid v 'integer))
714 (or gid (tramp-sudoedit-get-remote-gid v 'integer)))
715 (tramp-compat-file-name-unquote
716 (tramp-compat-file-local-name filename)))))
717
718(defun tramp-sudoedit-handle-write-region
719 (start end filename &optional append visit lockname mustbenew)
720 "Like `write-region' for Tramp files."
721 (with-parsed-tramp-file-name filename nil
722 (let ((uid (or (tramp-compat-file-attribute-user-id
723 (file-attributes filename 'integer))
724 (tramp-sudoedit-get-remote-uid v 'integer)))
725 (gid (or (tramp-compat-file-attribute-group-id
726 (file-attributes filename 'integer))
727 (tramp-sudoedit-get-remote-gid v 'integer)))
728 (modes (tramp-default-file-modes filename)))
729 (prog1
730 (tramp-handle-write-region
731 start end filename append visit lockname mustbenew)
732
733 ;; Set the ownership and modes. This is not performed in
734 ;; `tramp-handle-write-region'.
735 (unless (and (= (tramp-compat-file-attribute-user-id
736 (file-attributes filename 'integer))
737 uid)
738 (= (tramp-compat-file-attribute-group-id
739 (file-attributes filename 'integer))
740 gid))
741 (tramp-set-file-uid-gid filename uid gid))
742 (set-file-modes filename modes)))))
743
744
745;; Internal functions.
746
747;; Used in `tramp-sudoedit-sudo-actions'.
748(defun tramp-sudoedit-action-sudo (proc vec)
749 "Check, whether a sudo process copy has finished."
750 ;; There might be pending output for the exit status.
751 (tramp-accept-process-output proc 0.1)
752 (when (not (process-live-p proc))
753 ;; Delete narrowed region, it would be in the way reading a Lisp form.
754 (goto-char (point-min))
755 (widen)
756 (delete-region (point-min) (point))
757 ;; Delete empty lines.
758 (goto-char (point-min))
759 (while (and (not (eobp)) (= (point) (point-at-eol)))
760 (forward-line))
761 (delete-region (point-min) (point))
762 (tramp-message vec 3 "Process has finished.")
763 (throw 'tramp-action 'ok)))
764
765(defun tramp-sudoedit-maybe-open-connection (vec)
766 "Maybe open a connection VEC.
767Does not do anything if a connection is already open, but re-opens the
768connection if a previous connection has died for some reason."
769 ;; We need a process bound to the connection buffer. Therefore, we
770 ;; create a dummy process. Maybe there is a better solution?
771 (unless (tramp-get-connection-process vec)
772 (let ((p (make-network-process
773 :name (tramp-buffer-name vec)
774 :buffer (tramp-get-connection-buffer vec)
775 :server t :host 'local :service t :noquery t)))
776 (process-put p 'vector vec)
777 (set-process-query-on-exit-flag p nil)
778
779 ;; Set connection-local variables.
780 (tramp-set-connection-local-variables vec))
781
782 ;; In `tramp-check-cached-permissions', the connection properties
783 ;; "{uid,gid}-{integer,string}" are used. We set them to proper values.
784 (tramp-sudoedit-get-remote-uid vec 'integer)
785 (tramp-sudoedit-get-remote-gid vec 'integer)
786 (tramp-sudoedit-get-remote-uid vec 'string)
787 (tramp-sudoedit-get-remote-gid vec 'string)))
788
789(defun tramp-sudoedit-send-command (vec &rest args)
790 "Send commands ARGS to connection VEC.
791If an element of ARGS is a list, it will be flattened. If an
792element of ARGS is nil, it will be deleted.
793Erases temporary buffer before sending the command. Returns nil
794in case of error, t otherwise."
795 (tramp-sudoedit-maybe-open-connection vec)
796 (with-current-buffer (tramp-get-connection-buffer vec)
797 (erase-buffer)
798 (let* ((login (tramp-get-method-parameter vec 'tramp-sudo-login))
799 (host (or (tramp-file-name-host vec) ""))
800 (user (or (tramp-file-name-user vec) ""))
801 (spec (format-spec-make ?h host ?u user))
802 (args (append
803 (tramp-compat-flatten-list
804 (mapcar
805 (lambda (x)
806 (setq x (mapcar (lambda (y) (format-spec y spec)) x))
807 (unless (member "" x) x))
808 login))
809 (tramp-compat-flatten-list (delq nil args))))
810 (delete-exited-processes t)
811 (process-connection-type tramp-process-connection-type)
812 (p (apply 'start-process
813 (tramp-get-connection-name vec) (current-buffer) args))
814 ;; We suppress the messages `Waiting for prompts from remote shell'.
815 (tramp-verbose (if (= tramp-verbose 3) 2 tramp-verbose))
816 ;; We do not want to save the password.
817 auth-source-save-behavior)
818 (tramp-message vec 6 "%s" (mapconcat 'identity (process-command p) " "))
819 ;; Avoid process status message in output buffer.
820 (set-process-sentinel p 'ignore)
821 (process-put p 'vector vec)
822 (process-put p 'adjust-window-size-function 'ignore)
823 (set-process-query-on-exit-flag p nil)
824 (tramp-process-actions p vec nil tramp-sudoedit-sudo-actions)
825 (tramp-message vec 6 "%s\n%s" (process-exit-status p) (buffer-string))
826 (prog1
827 (zerop (process-exit-status p))
828 (delete-process p)))))
829
830(defun tramp-sudoedit-send-command-and-read (vec &rest args)
831 "Run command ARGS and return the output, which must be a Lisp expression.
832In case there is no valid Lisp expression, it raises an error."
833 (when (apply 'tramp-sudoedit-send-command vec args)
834 (with-current-buffer (tramp-get-connection-buffer vec)
835 ;; Replace stat marker.
836 (goto-char (point-min))
837 (when (search-forward tramp-stat-marker nil t)
838 (goto-char (point-min))
839 (while (search-forward "\"" nil t)
840 (replace-match "\\\"" nil 'literal))
841 (goto-char (point-min))
842 (while (search-forward tramp-stat-marker nil t)
843 (replace-match "\"")))
844 ;; Read the expression.
845 (tramp-message vec 6 "\n%s" (buffer-string))
846 (goto-char (point-min))
847 (condition-case nil
848 (prog1 (read (current-buffer))
849 ;; Error handling.
850 (when (re-search-forward "\\S-" (point-at-eol) t)
851 (error nil)))
852 (error (tramp-error
853 vec 'file-error
854 "`%s' does not return a valid Lisp expression: `%s'"
855 (car args) (buffer-string)))))))
856
857(defun tramp-sudoedit-send-command-string (vec &rest args)
858 "Run command ARGS and return the output as astring."
859 (when (apply 'tramp-sudoedit-send-command vec args)
860 (with-current-buffer (tramp-get-connection-buffer vec)
861 (tramp-message vec 6 "\n%s" (buffer-string))
862 (goto-char (point-max))
863 ;(delete-blank-lines)
864 (while (looking-back "[ \t\n]+" nil 'greedy)
865 (delete-region (match-beginning 0) (point)))
866 (when (> (point-max) (point-min))
867 (substring-no-properties (buffer-string))))))
868
869(add-hook 'tramp-unload-hook
870 (lambda ()
871 (unload-feature 'tramp-sudoedit 'force)))
872
873(provide 'tramp-sudoedit)
874
875;;; TODO:
876
877;; * Fix *-selinux functions. Likely, this is due to wrong file
878;; ownership after `write-region' and/or `copy-file'.
879
880;;; tramp-sudoedit.el ends here
diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el
index a44abfdcbbd..a1514f85a32 100644
--- a/lisp/net/tramp.el
+++ b/lisp/net/tramp.el
@@ -2234,7 +2234,9 @@ ARGS are the arguments OPERATION has been called with."
2234 ;; Emacs 26+ only. 2234 ;; Emacs 26+ only.
2235 file-name-case-insensitive-p 2235 file-name-case-insensitive-p
2236 ;; Emacs 27+ only. 2236 ;; Emacs 27+ only.
2237 file-system-info)) 2237 file-system-info
2238 ;; Tramp internal magic file name function.
2239 tramp-set-file-uid-gid))
2238 (if (file-name-absolute-p (nth 0 args)) 2240 (if (file-name-absolute-p (nth 0 args))
2239 (nth 0 args) 2241 (nth 0 args)
2240 default-directory)) 2242 default-directory))
@@ -4329,24 +4331,49 @@ This is used internally by `tramp-file-mode-from-int'."
4329 (and suid (upcase suid-text)) ; suid, !execute 4331 (and suid (upcase suid-text)) ; suid, !execute
4330 (and x "x") "-")))) ; !suid 4332 (and x "x") "-")))) ; !suid
4331 4333
4334;; This is a Tramp internal function. A general `set-file-uid-gid'
4335;; outside Tramp is not needed, I believe.
4336(defun tramp-set-file-uid-gid (filename &optional uid gid)
4337 "Set the ownership for FILENAME.
4338If UID and GID are provided, these values are used; otherwise uid
4339and gid of the corresponding remote or local user is taken,
4340depending whether FILENAME is remote or local. Both parameters
4341must be non-negative integers.
4342If FILENAME is remote, a file name handler is called."
4343 (let ((handler (find-file-name-handler filename 'tramp-set-file-uid-gid)))
4344 (if handler
4345 (funcall handler 'tramp-set-file-uid-gid filename uid gid)
4346 ;; On W32 "chown" does not work.
4347 (unless (memq system-type '(ms-dos windows-nt))
4348 (let ((uid (or (and (natnump uid) uid) (tramp-get-local-uid 'integer)))
4349 (gid (or (and (natnump gid) gid) (tramp-get-local-gid 'integer))))
4350 (tramp-call-process
4351 nil "chown" nil nil nil
4352 (format "%d:%d" uid gid) (shell-quote-argument filename)))))))
4353
4332;;;###tramp-autoload 4354;;;###tramp-autoload
4333(defun tramp-get-local-uid (id-format) 4355(defun tramp-get-local-uid (id-format)
4334 "The uid of the local user, in ID-FORMAT. 4356 "The uid of the local user, in ID-FORMAT.
4335ID-FORMAT valid values are `string' and `integer'." 4357ID-FORMAT valid values are `string' and `integer'."
4336 (if (equal id-format 'integer) (user-uid) (user-login-name))) 4358 ;; We use key nil for local connection properties.
4359 (with-tramp-connection-property nil (format "uid-%s" id-format)
4360 (if (equal id-format 'integer) (user-uid) (user-login-name))))
4337 4361
4338;;;###tramp-autoload 4362;;;###tramp-autoload
4339(defun tramp-get-local-gid (id-format) 4363(defun tramp-get-local-gid (id-format)
4340 "The gid of the local user, in ID-FORMAT. 4364 "The gid of the local user, in ID-FORMAT.
4341ID-FORMAT valid values are `string' and `integer'." 4365ID-FORMAT valid values are `string' and `integer'."
4342 (cond 4366 ;; We use key nil for local connection properties.
4343 ;; `group-gid' has been introduced with Emacs 24.4. 4367 (with-tramp-connection-property nil (format "gid-%s" id-format)
4344 ((and (fboundp 'group-gid) (equal id-format 'integer)) 4368 (cond
4345 (tramp-compat-funcall 'group-gid)) 4369 ;; `group-gid' has been introduced with Emacs 24.4.
4346 ;; `group-name' has been introduced with Emacs 27.1. 4370 ((and (fboundp 'group-gid) (equal id-format 'integer))
4347 ((and (fboundp 'group-name) (equal id-format 'string)) 4371 (tramp-compat-funcall 'group-gid))
4348 (tramp-compat-funcall 'group-name (tramp-compat-funcall 'group-gid))) 4372 ;; `group-name' has been introduced with Emacs 27.1.
4349 ((tramp-compat-file-attribute-group-id (file-attributes "~/" id-format))))) 4373 ((and (fboundp 'group-name) (equal id-format 'string))
4374 (tramp-compat-funcall 'group-name (tramp-compat-funcall 'group-gid)))
4375 ((tramp-compat-file-attribute-group-id
4376 (file-attributes "~/" id-format))))))
4350 4377
4351(defun tramp-get-local-locale (&optional vec) 4378(defun tramp-get-local-locale (&optional vec)
4352 "Determine locale, supporting UTF8 if possible. 4379 "Determine locale, supporting UTF8 if possible.
diff --git a/test/lisp/net/tramp-tests.el b/test/lisp/net/tramp-tests.el
index d68804a1c4e..056b6ce8360 100644
--- a/test/lisp/net/tramp-tests.el
+++ b/test/lisp/net/tramp-tests.el
@@ -3009,7 +3009,7 @@ This tests also `file-readable-p', `file-regular-p' and
3009 "Check `file-modes'. 3009 "Check `file-modes'.
3010This tests also `file-executable-p', `file-writable-p' and `set-file-modes'." 3010This tests also `file-executable-p', `file-writable-p' and `set-file-modes'."
3011 (skip-unless (tramp--test-enabled)) 3011 (skip-unless (tramp--test-enabled))
3012 (skip-unless (tramp--test-sh-p)) 3012 (skip-unless (or (tramp--test-sh-p) (tramp--test-sudoedit-p)))
3013 3013
3014 (dolist (quoted (if (tramp--test-expensive-test) '(nil t) '(nil))) 3014 (dolist (quoted (if (tramp--test-expensive-test) '(nil t) '(nil)))
3015 (let ((tmp-name (tramp--test-make-temp-name nil quoted))) 3015 (let ((tmp-name (tramp--test-make-temp-name nil quoted)))
@@ -3309,7 +3309,8 @@ This tests also `make-symbolic-link', `file-truename' and `add-name-to-file'."
3309(ert-deftest tramp-test22-file-times () 3309(ert-deftest tramp-test22-file-times ()
3310 "Check `set-file-times' and `file-newer-than-file-p'." 3310 "Check `set-file-times' and `file-newer-than-file-p'."
3311 (skip-unless (tramp--test-enabled)) 3311 (skip-unless (tramp--test-enabled))
3312 (skip-unless (or (tramp--test-adb-p) (tramp--test-sh-p))) 3312 (skip-unless
3313 (or (tramp--test-adb-p) (tramp--test-sh-p) (tramp--test-sudoedit-p)))
3313 3314
3314 (dolist (quoted (if (tramp--test-expensive-test) '(nil t) '(nil))) 3315 (dolist (quoted (if (tramp--test-expensive-test) '(nil t) '(nil)))
3315 (let ((tmp-name1 (tramp--test-make-temp-name nil quoted)) 3316 (let ((tmp-name1 (tramp--test-make-temp-name nil quoted))
@@ -4567,6 +4568,10 @@ This does not support special file names."
4567 (tramp-find-foreign-file-name-handler tramp-test-temporary-file-directory) 4568 (tramp-find-foreign-file-name-handler tramp-test-temporary-file-directory)
4568 'tramp-sh-file-name-handler)) 4569 'tramp-sh-file-name-handler))
4569 4570
4571(defun tramp--test-sudoedit-p ()
4572 "Check, whether the sudoedit method is used."
4573 (tramp-sudoedit-file-name-p tramp-test-temporary-file-directory))
4574
4570(defun tramp--test-windows-nt () 4575(defun tramp--test-windows-nt ()
4571 "Check, whether the locale host runs MS Windows." 4576 "Check, whether the locale host runs MS Windows."
4572 (eq system-type 'windows-nt)) 4577 (eq system-type 'windows-nt))
@@ -4761,6 +4766,7 @@ This requires restrictions of file name syntax."
4761 (list 4766 (list
4762 (if (or (tramp--test-gvfs-p) 4767 (if (or (tramp--test-gvfs-p)
4763 (tramp--test-rclone-p) 4768 (tramp--test-rclone-p)
4769 (tramp--test-sudoedit-p)
4764 (tramp--test-windows-nt-or-smb-p)) 4770 (tramp--test-windows-nt-or-smb-p))
4765 "foo bar baz" 4771 "foo bar baz"
4766 (if (or (tramp--test-adb-p) 4772 (if (or (tramp--test-adb-p)