5

I'm trying to save the contents of an Emacs scratch buffer, which I can't access any more due to an inaccessible terminal.

On my Linux box I've ssh'ed to a server, and started Emacs. My Linux box has now frozen, however I can still see the Emacs process alive on the server ssh'ed into.

Is there a way to get Emacs to dump/save the contents of its scratch without having direct access? I had two thoughts:

  1. Send a signal to the process so that Emacs dumps its core, and then reload the core (and then save scratch)
  2. Send keystrokes to the stdin of the process which would instruct the Emacs process to save the file, ie: via echo "abd" > /proc/<pid>/fd/0. I tried this by opening two terminals, and the keystrokes appear on the target terminal, however they aren't captured by Emacs.
Trent Gm
  • 183
  • 7

2 Answers2

3

The Emacs server

The best way to do this is to contact the Emacs server and run an Emacs Lisp command.

emacsclient -e '(with-current-buffer "*scratch*" (write-file "~/scratch.txt"))'

Or you could attach Emacs to the current terminal and do whatever you want there, such as switch to the `scratch buffer and save its contents.

emacsclient -nw

Unfortunately for you, you need to start the server explicitly. I recommend that you add (server-start) to your init file, because it is very useful, but if you haven't done that already, it won't help you now.

Sending input to Emacs's terminal

When you run echo "abd" > /proc/$emacs_pid/fd/0, this sends the text to the terminal that Emacs is running on. Writing data to a terminal displays that data. You can't send input to a terminal: input is what comes from a terminal.

Screen

If you run a program in a screen or tmux session, you can attach to the existing session and continue interacting with the program. Both screen and tmux also support programmatically sending input to a program running inside. This requires having run the program in the first place, though, so it won't help you this time.

With Emacs, Screen won't buy you much over connecting to the Emacs server with emacsclient.

Core dump

Sifting through core dump is an option. Emacs stores the content of a buffer in a single chunk, with a gap in the middle (the gap may contain arbitrary junk; if the last operation was a deletion, it's what was deleted).

The SIGQUIT signal causes a process to die a leave a core dump (if enabled) by default. However, Emacs catches SIGQUIT (it quits to the toplevel, which is convenient in some situations but doesn't help you). So instead you'll need to attach to it with a debugger and make it dump core. This has benefits over SIGQUIT anyway: it leaves the process running and works even if core dumps are disabled.

$ gdb -p $(pidof emacs)
(gdb) gcore

You can then sift through the core dump to extract the data

Reattach Emacs to a new terminal

There's no Unix or even Linux feature to detach a program from one terminal and attach it on another. However, it is possible to attach a debugger to any program and make it open a different terminal. This can be hit-and-miss: sometimes it works, sometimes it crashes the program. There are several tools that attempt to do this as best as they can; see How can I disown a running process and associate it to a new screen shell?

  • Core dump - grepping the proc file shows no limits set, however sending SIGQUIT to the emacs process doesn't seem to do anything?! kill -s 3 – Trent Gm Dec 29 '14 at 01:09
  • Sending input to Emacs's terminal - doesn't input into Emacs come from stdin (fd 0)? I thought during normal operating the input path is: keyboard -> terminal -> stdin -> emacs, and that writing to stdin above would achieve the same thing as typing at the keyboard? – Trent Gm Dec 29 '14 at 01:30
  • @Taras What does cat /proc/sys/kernel/core_pattern show? Some distributions set up core dumps differently, e.g. Ubuntu's apport. Regarding your second comment: Emacs's input comes from its stdin, which is the terminal; but when you write to the terminal, that doesn't become input for Emacs: it becomes input for the terminal emulator, which it displays. You'd need to provide output to the terminal emulator which it would translate as input for the program running in it. – Gilles 'SO- stop being evil' Dec 29 '14 at 12:00
  • core_pattern shows /var/core/core.%e.%p.%h.%t.%u. Linux version is: Linux version 2.6.18-274.17.1.el5 (ancient!) – Trent Gm Dec 29 '14 at 21:57
  • @Taras So is there a core file in /var/core? – Gilles 'SO- stop being evil' Dec 29 '14 at 22:01
  • No - emacs remains open and usable after I send the SIGQUIT signal (and there's no core file). Sending SIGKILL to the same pid kills emacs, so sending signals is working... – Trent Gm Dec 29 '14 at 22:03
  • Regarding the input/output, so writing to /proc/pid_of_emacs/fd/0 will write to the output of the terminal rather than emacs, even though the pid is that of emacs? I would have expected by writing to fd/0, that it would bypass the terminal completely? Additionally, writing to fd/1 gives the same results, so not sure how to provide output for the terminal (if this is even possible) – Trent Gm Dec 29 '14 at 22:13
  • @Taras Ah, sorry to lead you on a wild goose chase: Emacs catches SIGQUIT. You can make it dump a core (and keep running) with gdb's gcore command. Regarding the input/output, don't think of /proc/PID/fd/0 as “the standard input of Emacs”, but as “the file that Emacs has open on its standard input”. /proc/PID/fd/0 doesn't bypass the terminal: it is the terminal, same as /proc/PID/fd/1. – Gilles 'SO- stop being evil' Dec 29 '14 at 22:15
3

I've had some luck with attaching gdb to the running process. Borrowing heavily from: How to attach terminal to detached process?

write-file

  1. mkfifo /tmp/some_name
  2. gdb -p [pid]
  3. (within gdb): call close(0)
  4. (within gdb): call open('/tmp/some_name', 0600). At this point gdb will appear to hang
  5. (from the shell): echo '(write-file "savedresults")' > /tmp/some_name
  6. (within gbd): ctrl-d

The contents of scratch are written out to the file 'savedresults'. Interestingly (write-file "savedresults") is appended to the file (unsure why).

C-x C-w name

  1. mkfifo /tmp/some_name
  2. gdb -p [pid]
  3. (within gdb): call close(0)
  4. (within gdb): call open('/tmp/some_name', 0600). At this point gdb will appear to hang
  5. (from the shell): cmd="^X^Wsavedresults" (input ^X via key sequence: CTRL-V CTRL-X, similar for ^W"
  6. (from the shell): echo "$cmd" > /tmp/some_name
  7. (within gbd): ctrl-d

Contents are written to file 'savedresults'.

Trent Gm
  • 183
  • 7