Search code examples
emacscommon-lispslime

Loading a local file in a remote Lisp with swank/slime


Say that I am connected to a remote Lisp using swank/slime. I have a data file on my local machine on disk or, perhaps, in an emacs buffer. I want to process this file using the remote Lisp.

I can obviously drop out to a shell and scp the file to the server, load the file, process it, close, delete the file so that I don't make a mess on the server.

I can also paste the file into the repl:

> (defparameter *my-file* "[paste file here]")

But this is nasty if the text has quotes in it or the file is binary.

Both options are cumbersome.

Is there a nice way to get the local emacs to tunnel a file to the remote Lisp so that the remote Lisp can open it as a stream?

I'm picturing something like:

> (swank:with-open-client-file (s #p"/path/to/local/file") ... )

Edit: This example feels like it could open some holes in my local file system. Could it be done without causing serious security problems?


Solution

  • The Emacs-side of the setup could start an HTTP server that restricts which files are published. It looks like it is possible to do it in Emacs directly (e.g. https://gist.github.com/TeMPOraL/f6f5333ae93de4ce9b5bd82cdad87d32).

    The remote part could then obtain streams with drakma or dexador.

    This does not rely on Slime/swank.

    Alternatively, you can call eval-in-emacs to execute an Emacs Lisp form that would return a string containing the content of the file. You can write a custom function in Emacs to grant access or not to the files.

    For example, an unsafe version is as follows:

    (defun get-file (filename)
      (with-temp-buffer
        (insert-file-contents filename)
          (buffer-string))))
    

    Then:

    CL-USER> (swank:eval-in-emacs '(get-file "/etc/passwd"))
    "......"
    

    The above requires Slime to be configured so that eval-in-emacs is enabled.

    Once this works, you can write a helper function:

    (defun call-with-open-client-file (function file)
      (with-input-from-string (stream (swank:eval-in-emacs `(get-file ,file)))
        (funcall function stream)))
    

    And a macro:

    (defmacro with-open-client-file (var name &body body)
      `(call-with-open-client-file (lambda (,var) ,@body) ,name)