Bad Daemons

Abrir un fichero con sudo automáticamente en emacs

Es una de estas cosas que a primera vista parece que deba ser algo simple, pero es algo más complejo cuando empiezas a mirarlo. Ya hace tiempo que encontré una función para hacerlo en local en la fantástica web de emacsredux:

(defun sudo-edit (&optional arg)
  "Edit currently visited file as root.

With a prefix ARG prompt for a file to visit.
Will also prompt for a file to visit if current
buffer is not visiting a file."
  (interactive "P")
  (if (or arg (not buffer-file-name))
      (find-file (concat "/sudo:root@localhost:"
                         (ido-read-file-name "Find file(as root): ")))
    (find-alternate-file (concat "/sudo:root@localhost:" buffer-file-name))))

El funcionamiento es: abrir un fichero y luego ejecutar M-x sudo-edit RET. No es la fiesta, pero funciona bien. El problema es que yo suelo editar muchos ficheros en remoto y por desgracia esta función no funciona, ya que tiene hardcodeado el hostname localhost. Intentando arreglar esto, me fijé en que el sitio del que habia sacado esta función tenia una segunda opción que deseché simplemente por que no la entendia:

(defadvice find-file (after find-file-sudo activate)
  "Find file as root if necessary."
  (unless (and buffer-file-name
               (file-writable-p buffer-file-name))
    (find-alternate-file (concat "/sudo:root@localhost:" buffer-file-name))))

Ahora que ya entiendo un poco más de emacs lisp, se que defadvice sirve para añadir la ejecución de una función o macro antes de otra, una suerte de before-hook. Y en este caso, esta función hace que find-file intente abrir el fichero con sudo si detecta que no tiene permisos para modificarlo. Es posible que esto diése problemas si se tiene la costumbre de modificar los atributos de los ficheros, pero es muy poco habitual.

Como eso no me servia para editar ficheros en máquinas remotas, he modificado la función anterior para que detecte automáticamente si se intenta abrir un fichero en la máquina local o en una remota y si es lo segundo, que modifique la ruta para que pueda hacerlo:

(defadvice purpose-find-file-overload (after find-file-sudo activate)
  "Find file as root if necessary."
  (unless (and buffer-file-name
               (file-writable-p buffer-file-name))

    (let* ((buffer-file (buffer-file-name))
           (coincidence (string-match-p "@" buffer-file))
           (hostname)
           (buffer-name))
      (if coincidence
          (progn
            (setq hostname (substring buffer-file (+ coincidence 1)
                                      (string-match-p ":" buffer-file      (+ coincidence 1))))
            (setq buffer-name
                  (concat
                   (substring buffer-file 0 coincidence) "@"
                   (replace-regexp-in-string ":" (concat "|sudo:" hostname ":")
                                             buffer-file nil nil nil (+ coincidence 1))))
            (find-alternate-file buffer-name))
          (find-alternate-file (concat "/sudo:root@localhost:" buffer-file))))))

En mi caso se modifica la función purpose-find-file-overload, que es la que tengo asignada a C-x C-f, pero se puede cambiar por ido-find-file, find-file o la que sea.

La ruta correcta para abrir un fichero remoto con permiso de superusuario seria así: /ssh:drymer@daemons.it|sudo:root@daemons.it:/. Esto último lo he sacado de este post de Stack Overflow.