diff options
| author | Michael Albinus | 2018-12-16 15:49:07 +0100 |
|---|---|---|
| committer | Michael Albinus | 2018-12-16 15:49:07 +0100 |
| commit | e8199e765f81968be840d8e7e3978f5974c1be9d (patch) | |
| tree | 354c9b7af9c126a49e614b716225c49135d702b4 | |
| parent | 4f230e8dd551d711433e2a8095a19c2ce00c9b4c (diff) | |
| download | emacs-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.texi | 37 | ||||
| -rw-r--r-- | etc/NEWS | 6 | ||||
| -rw-r--r-- | lisp/net/tramp-adb.el | 1 | ||||
| -rw-r--r-- | lisp/net/tramp-archive.el | 1 | ||||
| -rw-r--r-- | lisp/net/tramp-cache.el | 7 | ||||
| -rw-r--r-- | lisp/net/tramp-gvfs.el | 3 | ||||
| -rw-r--r-- | lisp/net/tramp-rclone.el | 3 | ||||
| -rw-r--r-- | lisp/net/tramp-sh.el | 43 | ||||
| -rw-r--r-- | lisp/net/tramp-smb.el | 1 | ||||
| -rw-r--r-- | lisp/net/tramp-sudoedit.el | 880 | ||||
| -rw-r--r-- | lisp/net/tramp.el | 47 | ||||
| -rw-r--r-- | test/lisp/net/tramp-tests.el | 10 |
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 | |||
| 468 | must be used here as user name. The default host name is the same. | 468 | must 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 | |||
| 476 | The @option{sudoedit} method is similar to the @option{sudo} method. | ||
| 477 | However, it is a different implementation: it does not keep an open | ||
| 478 | session running in the background. This is for security reasons; on | ||
| 479 | the backside this method is less performant than the @option{sudo} | ||
| 480 | method, it is restricted to the @samp{localhost} only, and it does not | ||
| 481 | support 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, | |||
| 919 | such as the @command{busybox} and do not host any other encode or | 932 | such as the @command{busybox} and do not host any other encode or |
| 920 | decode programs. | 933 | decode programs. |
| 921 | 934 | ||
| 935 | @item @option{sudoedit} | ||
| 936 | @cindex method @option{sudoedit} | ||
| 937 | @cindex @option{sudoedit} method | ||
| 938 | |||
| 939 | The @option{sudoedit} method allows to edit a file as a different user | ||
| 940 | on the local host. You could regard this as @value{tramp}'s | ||
| 941 | implementation of the @command{sudoedit}. Contrary to the | ||
| 942 | @option{sudo} method, all magic file name functions are implemented by | ||
| 943 | single @command{sudo @dots{}} commands. The purpose is to make | ||
| 944 | editing such a file as secure as possible; there must be no session | ||
| 945 | running in the Emacs background which could be attacked from inside | ||
| 946 | Emacs. | ||
| 947 | |||
| 948 | Consequently, external processes are not implemented. | ||
| 949 | |||
| 950 | The host name of such remote file names must represent the local host. | ||
| 951 | Since the default value is already proper, it is recommended not to | ||
| 952 | use 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 | |||
| 956 | Like the @option{sudo} method, a @option{sudoedit} password expires | ||
| 957 | after 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 |
| @@ -865,6 +865,12 @@ or NextCloud hosted files and directories. | |||
| 865 | storages via the 'rclone' program. This feature is experimental. | 865 | storages via the 'rclone' program. This feature is experimental. |
| 866 | 866 | ||
| 867 | +++ | 867 | +++ |
| 868 | *** New connection method "sudoedit", which allows to edit local files | ||
| 869 | with different user credentials. Contrary to the "sudo" method, no | ||
| 870 | session is run permanently in the background. This is for security | ||
| 871 | reasons. | ||
| 872 | |||
| 873 | +++ | ||
| 868 | *** Connection methods "obex" and "synce" are removed, because they | 874 | *** Connection methods "obex" and "synce" are removed, because they |
| 869 | are obsoleted in GVFS. | 875 | are 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." |
| 1521 | If UID and GID are provided, these values are used; otherwise uid | ||
| 1522 | and gid of the corresponding user is taken. Both parameters must | ||
| 1523 | be 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. | ||
| 63 | This list is used for sudo calls. | ||
| 64 | |||
| 65 | See `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. | ||
| 156 | First arg specifies the OPERATION, second arg is a list of arguments to | ||
| 157 | pass 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. | ||
| 209 | OP must be `copy' or `rename' and indicates the operation to perform. | ||
| 210 | FILENAME specifies the file to copy or rename, NEWNAME is the name of | ||
| 211 | the new file (for copy) or the new name of the file (for rename). | ||
| 212 | OK-IF-ALREADY-EXISTS means don't barf if NEWNAME exists already. | ||
| 213 | KEEP-DATE means to make sure that NEWNAME has the same timestamp | ||
| 214 | as FILENAME. PRESERVE-UID-GID, when non-nil, instructs to keep | ||
| 215 | the uid and gid if both files are on the same host. | ||
| 216 | PRESERVE-EXTENDED-ATTRIBUTES activates selinux and acl commands. | ||
| 217 | |||
| 218 | This function is invoked by `tramp-sudoedit-handle-copy-file' and | ||
| 219 | `tramp-sudoedit-handle-rename-file'. It is an error if OP is | ||
| 220 | neither of `copy' and `rename'. FILENAME and NEWNAME must be | ||
| 221 | absolute 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. | ||
| 354 | If the localname part of the given file name starts with \"/../\" then | ||
| 355 | the 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. | ||
| 601 | If TARGET is a non-Tramp file, it is used verbatim as the target | ||
| 602 | of the symlink. If TARGET is a Tramp file, only the localname | ||
| 603 | component 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. | ||
| 693 | ID-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. | ||
| 701 | ID-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. | ||
| 767 | Does not do anything if a connection is already open, but re-opens the | ||
| 768 | connection 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. | ||
| 791 | If an element of ARGS is a list, it will be flattened. If an | ||
| 792 | element of ARGS is nil, it will be deleted. | ||
| 793 | Erases temporary buffer before sending the command. Returns nil | ||
| 794 | in 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. | ||
| 832 | In 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. | ||
| 4338 | If UID and GID are provided, these values are used; otherwise uid | ||
| 4339 | and gid of the corresponding remote or local user is taken, | ||
| 4340 | depending whether FILENAME is remote or local. Both parameters | ||
| 4341 | must be non-negative integers. | ||
| 4342 | If 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. |
| 4335 | ID-FORMAT valid values are `string' and `integer'." | 4357 | ID-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. |
| 4341 | ID-FORMAT valid values are `string' and `integer'." | 4365 | ID-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'. |
| 3010 | This tests also `file-executable-p', `file-writable-p' and `set-file-modes'." | 3010 | This 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) |