* Teaching emacsclient to act as a pager, and more @ 2016-06-07 1:25 Spencer Baugh 2016-06-07 1:25 ` [PATCH 1/5] process: add features for direct use of FDs Spencer Baugh ` (9 more replies) 0 siblings, 10 replies; 40+ messages in thread From: Spencer Baugh @ 2016-06-07 1:25 UTC (permalink / raw) To: emacs-devel Hi emacs-devel, I've added the ability for emacsclient to act as a pager, such that you can pipe data to emacsclient and page through it in the emacs frame of your choice. The attached patches are on top of commit 549470fdf234acb4da7941e3bb9b28ed63a51876 Here is a video demo: https://catern.com/files/emacspager.webm To do this, I've used the file descriptor passing feature of Unix sockets, which allows a process to transmit a duplicate of any of its open file descriptors over a Unix socket to another process. The other process can then make full use of that file descriptor, including, of course, reading and writing from it. In the attached patches, I taught emacsclient to (when a new option --pipeline/-l is passed) send its stdin/stdout/stderr to the emacs server, and I taught the emacs server to accept those file descriptors and make an Elisp process out of them. Then the process machinery does the rest of the work of reading data from the Elisp process (which is actually data coming in on emacsclient's stdin) and putting it in a buffer. This functionality is exposed to Elisp by simply directly passing received file descriptor numbers to the process filter function (with a new argument, to processes that have opted in with a new keyword argument :ancillary). Those file descriptor numbers can be passed to a new function make-fd-process. I've written a function in Elisp, server-pager, which should be run with emacsclient -l --eval (server-pager). I added a new dynamic variable server-emacsclient-proc which is non-nil if we are currently evaluating Lisp for an emacsclient, and holds the Elisp process corresponding to that Emacsclient. server-pager uses this variable to retrieve the file descriptors for the current emacs-client, invoke make-fd-process, and pop-to-buffer the output buffer. server-pager stores the Elisp process it creates in the plist of the corresponding emacsclient, so when the Elisp process is killed, emacsclient is told to exit (if it didn't also open frames). Likewise if emacsclient is killed, the server-pager Elisp process is killed. The primary issue here, it seems to me, is that this leads to huge amounts of file descriptor leakage - if any passed-in file descriptor is unused, it is eternally left open. I think a good way to fix this is to add a native Elisp file descriptor type, so the file descriptor can be closed on garbage collect. But perhaps there's a better solution? In any case I would need guidance on how to create such a new Elisp type - this is my first attempt to hack on the Emacs C codebase. I am sure that these patches are terrible style, not in keeping with the Emacs coding conventions, and totally unportable (I wrote this on GNU/Linux) - I just wanted to get out a quick proof of concept. ^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH 1/5] process: add features for direct use of FDs 2016-06-07 1:25 Teaching emacsclient to act as a pager, and more Spencer Baugh @ 2016-06-07 1:25 ` Spencer Baugh 2016-06-07 1:25 ` [PATCH 2/5] server.el: accept FDs from emacsclient Spencer Baugh ` (8 subsequent siblings) 9 siblings, 0 replies; 40+ messages in thread From: Spencer Baugh @ 2016-06-07 1:25 UTC (permalink / raw) To: emacs-devel; +Cc: Spencer Baugh - A new keyword argument for make-network-process, :ancillary. When non-nil, Emacs will check for ancillary data when reading from the network process. If any is found, it is passed as an additional argument to the process filter function. At the moment, this only supports reading passed file descriptors out of ancillary data. This is inherited by the children of server processes. - A new Lisp function make-fd-process, which accepts keyword arguments exactly like make-pipe-process, but also accepts :infd and :outfd, which take Lisp integers which should be file descriptors that can be read from and written to, respectively. --- src/process.c | 215 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/process.h | 2 + 2 files changed, 212 insertions(+), 5 deletions(-) diff --git a/src/process.c b/src/process.c index 9ca3e594..dc96166 100644 --- a/src/process.c +++ b/src/process.c @@ -21,6 +21,7 @@ along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */ #include <config.h> +#include <sys/socket.h> #include <stdio.h> #include <errno.h> #include <sys/types.h> /* Some typedefs are used in sys/file.h. */ @@ -2085,6 +2086,157 @@ create_pty (Lisp_Object process) p->pid = -2; } +DEFUN ("make-fd-process", Fmake_fd_process, Smake_fd_process, + 0, MANY, 0, + doc: /* Create a process from passed file descriptors. + +:infd FD + +:outfd FD + +usage: (make-fd-process &rest ARGS) */) + (ptrdiff_t nargs, Lisp_Object *args) +{ + Lisp_Object proc, contact; + struct Lisp_Process *p; + Lisp_Object name, buffer; + Lisp_Object tem; + int infd, outfd; + ptrdiff_t specpdl_count; + int inchannel, outchannel; + + if (nargs == 0) + return Qnil; + + contact = Flist (nargs, args); + + infd = XINT (Fplist_get (contact, QCinfd)); + outfd = XINT (Fplist_get (contact, QCoutfd)); + + name = Fplist_get (contact, QCname); + CHECK_STRING (name); + proc = make_process (name); + specpdl_count = SPECPDL_INDEX (); + record_unwind_protect (remove_process, proc); + p = XPROCESS (proc); + + outchannel = outfd; + inchannel = infd; + p->open_fd[WRITE_TO_SUBPROCESS] = outfd; + p->open_fd[READ_FROM_SUBPROCESS] = infd; + + fcntl (inchannel, F_SETFL, O_NONBLOCK); + fcntl (outchannel, F_SETFL, O_NONBLOCK); + +#ifdef WINDOWSNT + register_aux_fd (inchannel); +#endif + + /* Record this as an active process, with its channels. */ + chan_process[inchannel] = proc; + p->infd = inchannel; + p->outfd = outchannel; + + if (inchannel > max_process_desc) + max_process_desc = inchannel; + + buffer = Fplist_get (contact, QCbuffer); + if (NILP (buffer)) + buffer = name; + buffer = Fget_buffer_create (buffer); + pset_buffer (p, buffer); + + pset_childp (p, contact); + pset_plist (p, Fcopy_sequence (Fplist_get (contact, QCplist))); + pset_type (p, Qpipe); + pset_sentinel (p, Fplist_get (contact, QCsentinel)); + pset_filter (p, Fplist_get (contact, QCfilter)); + pset_log (p, Qnil); + if (tem = Fplist_get (contact, QCnoquery), !NILP (tem)) + p->kill_without_query = 1; + if (tem = Fplist_get (contact, QCstop), !NILP (tem)) + pset_command (p, Qt); + eassert (! p->pty_flag); + + if (!EQ (p->command, Qt)) + { + FD_SET (inchannel, &input_wait_mask); + FD_SET (inchannel, &non_keyboard_wait_mask); + } + p->adaptive_read_buffering + = (NILP (Vprocess_adaptive_read_buffering) ? 0 + : EQ (Vprocess_adaptive_read_buffering, Qt) ? 1 : 2); + + /* Make the process marker point into the process buffer (if any). */ + if (BUFFERP (buffer)) + set_marker_both (p->mark, buffer, + BUF_ZV (XBUFFER (buffer)), + BUF_ZV_BYTE (XBUFFER (buffer))); + + { + /* Setup coding systems for communicating with the network stream. */ + + /* Qt denotes we have not yet called Ffind_operation_coding_system. */ + Lisp_Object coding_systems = Qt; + Lisp_Object val; + + tem = Fplist_get (contact, QCcoding); + val = Qnil; + if (!NILP (tem)) + { + val = tem; + if (CONSP (val)) + val = XCAR (val); + } + else if (!NILP (Vcoding_system_for_read)) + val = Vcoding_system_for_read; + else if ((!NILP (buffer) && NILP (BVAR (XBUFFER (buffer), enable_multibyte_characters))) + || (NILP (buffer) && NILP (BVAR (&buffer_defaults, enable_multibyte_characters)))) + /* We dare not decode end-of-line format by setting VAL to + Qraw_text, because the existing Emacs Lisp libraries + assume that they receive bare code including a sequence of + CR LF. */ + val = Qnil; + else + { + if (CONSP (coding_systems)) + val = XCAR (coding_systems); + else if (CONSP (Vdefault_process_coding_system)) + val = XCAR (Vdefault_process_coding_system); + else + val = Qnil; + } + pset_decode_coding_system (p, val); + + if (!NILP (tem)) + { + val = tem; + if (CONSP (val)) + val = XCDR (val); + } + else if (!NILP (Vcoding_system_for_write)) + val = Vcoding_system_for_write; + else if (NILP (BVAR (current_buffer, enable_multibyte_characters))) + val = Qnil; + else + { + if (CONSP (coding_systems)) + val = XCDR (coding_systems); + else if (CONSP (Vdefault_process_coding_system)) + val = XCDR (Vdefault_process_coding_system); + else + val = Qnil; + } + pset_encode_coding_system (p, val); + } + /* This may signal an error. */ + setup_process_coding_systems (proc); + + specpdl_ptr = specpdl + specpdl_count; + + return proc; +} + DEFUN ("make-pipe-process", Fmake_pipe_process, Smake_pipe_process, 0, MANY, 0, doc: /* Create and return a bidirectional pipe process. @@ -3919,6 +4071,7 @@ usage: (make-network-process &rest ARGS) */) p->kill_without_query = 1; if ((tem = Fplist_get (contact, QCstop), !NILP (tem))) pset_command (p, Qt); + p->ancillary_data = !NILP (Fplist_get (contact, QCancillary_data)); p->pid = 0; p->backlog = 5; p->is_non_blocking_client = false; @@ -4574,6 +4727,11 @@ server_accept_connection (Lisp_Object server, int channel) p = XPROCESS (proc); + /* TODO: I think this is supposed to be done by checking for a + property in contact, not by just copying the field from the + server process, but I'm not sure exactly what is correct */ + p->ancillary_data = ps->ancillary_data; + /* Build new contact information for this setup. */ contact = Fcopy_sequence (ps->childp); contact = Fplist_put (contact, QCserver, Qnil); @@ -5587,6 +5745,7 @@ read_process_output_error_handler (Lisp_Object error_val) static void read_and_dispose_of_process_output (struct Lisp_Process *p, char *chars, ssize_t nbytes, + Lisp_Object *fds, size_t nfds, struct coding_system *coding); /* Read pending output from the process channel, @@ -5611,6 +5770,9 @@ read_process_output (Lisp_Object proc, int channel) ptrdiff_t count = SPECPDL_INDEX (); Lisp_Object odeactivate; char chars[sizeof coding->carryover + readmax]; + char cbuf[512] = {}; + int nfds = 0; + int *fd_data = NULL; if (carryover) /* See the comment above. */ @@ -5639,8 +5801,34 @@ read_process_output (Lisp_Object proc, int channel) readmax - buffered); else #endif - nbytes = emacs_read (channel, chars + carryover + buffered, - readmax - buffered); + { + struct stat statbuf; + fstat(channel, &statbuf); + if (S_ISSOCK(statbuf.st_mode) && p->ancillary_data) { + /* we declare cbuf outside of here since it is the backing + storage for the fd_data array */ + struct iovec iov = { .iov_base = chars + carryover + buffered, + .iov_len = readmax - buffered, }; + struct msghdr msgh = { .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cbuf, + .msg_controllen = sizeof cbuf, }; + nbytes = recvmsg(channel, &msgh, MSG_CMSG_CLOEXEC); + /* check for control messages */ + for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msgh, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET + && cmsg->cmsg_type == SCM_RIGHTS) { + nfds = cmsg->cmsg_len / (sizeof(int)); + fd_data = (int *) CMSG_DATA(cmsg); + } + } + } else { + nbytes = emacs_read (channel, chars + carryover + buffered, + readmax - buffered); + } + } + if (nbytes > 0 && p->adaptive_read_buffering) { int delay = p->read_output_delay; @@ -5669,6 +5857,12 @@ read_process_output (Lisp_Object proc, int channel) nbytes += buffered; nbytes += buffered && nbytes <= 0; } + /* if we saw any fds, put them in an array of Lisp_Objects; this is + a no-op if we saw no fds */ + Lisp_Object fds[nfds]; + for (int i = 0; i < nfds; i++) { + fds[i] = make_number(fd_data[i]); + } p->decoding_carryover = 0; @@ -5690,7 +5884,7 @@ read_process_output (Lisp_Object proc, int channel) friends don't expect current-buffer to be changed from under them. */ record_unwind_current_buffer (); - read_and_dispose_of_process_output (p, chars, nbytes, coding); + read_and_dispose_of_process_output (p, chars, nbytes, fds, nfds, coding); /* Handling the process output should not deactivate the mark. */ Vdeactivate_mark = odeactivate; @@ -5702,6 +5896,7 @@ read_process_output (Lisp_Object proc, int channel) static void read_and_dispose_of_process_output (struct Lisp_Process *p, char *chars, ssize_t nbytes, + Lisp_Object *fds, size_t nfds, struct coding_system *coding) { Lisp_Object outstream = p->filter; @@ -5775,14 +5970,20 @@ read_and_dispose_of_process_output (struct Lisp_Process *p, char *chars, coding->carryover_bytes); p->decoding_carryover = coding->carryover_bytes; } - if (SBYTES (text) > 0) + if (SBYTES (text) > 0) { /* FIXME: It's wrong to wrap or not based on debug-on-error, and sometimes it's simply wrong to wrap (e.g. when called from accept-process-output). */ + Lisp_Object form; + if (p->ancillary_data) + form = list4 (outstream, make_lisp_proc (p), text, Flist(nfds, fds)); + else + form = list3 (outstream, make_lisp_proc (p), text); internal_condition_case_1 (read_process_output_call, - list3 (outstream, make_lisp_proc (p), text), + form, !NILP (Vdebug_on_error) ? Qnil : Qerror, read_process_output_error_handler); + } /* If we saved the match data nonrecursively, restore it now. */ restore_search_regs (); @@ -7866,6 +8067,7 @@ syms_of_process (void) DEFSYM (QCnowait, ":nowait"); DEFSYM (QCsentinel, ":sentinel"); DEFSYM (QCuse_external_socket, ":use-external-socket"); + DEFSYM (QCancillary_data, ":ancillary-data"); DEFSYM (QCtls_parameters, ":tls-parameters"); DEFSYM (Qnsm_verify_connection, "nsm-verify-connection"); DEFSYM (QClog, ":log"); @@ -7877,6 +8079,8 @@ syms_of_process (void) DEFSYM (QCstderr, ":stderr"); DEFSYM (Qpty, "pty"); DEFSYM (Qpipe, "pipe"); + DEFSYM (QCinfd, ":infd"); + DEFSYM (QCoutfd, ":outfd"); DEFSYM (Qlast_nonmenu_event, "last-nonmenu-event"); @@ -7979,6 +8183,7 @@ The variable takes effect when `start-process' is called. */); defsubr (&Sprocess_list); defsubr (&Smake_process); defsubr (&Smake_pipe_process); + defsubr (&Smake_fd_process); defsubr (&Sserial_process_configure); defsubr (&Smake_serial_process); defsubr (&Sset_network_process_option); diff --git a/src/process.h b/src/process.h index a5f690d..72ba204 100644 --- a/src/process.h +++ b/src/process.h @@ -166,6 +166,8 @@ struct Lisp_Process bool_bf is_non_blocking_client : 1; /* Whether this is a server or a client socket. */ bool_bf is_server : 1; + /* Whether the filter should have ancillary data passed to it */ + bool_bf ancillary_data : 1; int raw_status; /* The length of the socket backlog. */ int backlog; -- 2.8.2 ^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 2/5] server.el: accept FDs from emacsclient 2016-06-07 1:25 Teaching emacsclient to act as a pager, and more Spencer Baugh 2016-06-07 1:25 ` [PATCH 1/5] process: add features for direct use of FDs Spencer Baugh @ 2016-06-07 1:25 ` Spencer Baugh 2016-06-07 1:25 ` [PATCH 3/5] emacsclient: support passing stdin/out/err to emacs Spencer Baugh ` (7 subsequent siblings) 9 siblings, 0 replies; 40+ messages in thread From: Spencer Baugh @ 2016-06-07 1:25 UTC (permalink / raw) To: emacs-devel; +Cc: Spencer Baugh The emacs server passes :ancillary t to make-network-process, and stores any file descriptors it receives from a client. --- lisp/server.el | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/lisp/server.el b/lisp/server.el index e4cf431..894f8ac 100644 --- a/lisp/server.el +++ b/lisp/server.el @@ -352,6 +352,9 @@ server-delete-client (when (and terminal (eq (terminal-live-p terminal) t)) (delete-terminal terminal)))) + ;; Delete associated processes using this client's fds + (mapc #'delete-process (process-get proc :pipelines)) + ;; Delete the client's process. (if (eq (process-status proc) 'open) (delete-process proc)) @@ -670,6 +673,7 @@ server-start :plist '(:authenticated nil)) (list :family 'local :service server-file + :ancillary-data t :plist '(:authenticated t))))) (unless server-process (error "Could not start server process")) (process-put server-process :server-file server-file) @@ -915,7 +919,7 @@ server-execute-continuation (process-put proc 'continuation nil) (if continuation (ignore-errors (funcall continuation))))) -(cl-defun server-process-filter (proc string) +(cl-defun server-process-filter (proc string &optional ancillary) "Process a request from the server to edit some files. PROC is the server process. STRING consists of a sequence of commands prefixed by a dash. Some commands have arguments; @@ -1015,6 +1019,9 @@ server-execute-continuation Suspend this terminal, i.e., stop the client process. Sent when the user presses C-z." (server-log (concat "Received " string) proc) + (when ancillary + (server-log (format "Received fds %s" ancillary) proc) + (process-put proc :fds ancillary)) ;; First things first: let's check the authentication (unless (process-get proc :authenticated) (if (and (string-match "-auth \\([!-~]+\\)\n?" string) @@ -1262,6 +1269,50 @@ server-execute-continuation ;; condition-case (error (server-return-error proc err)))) +(defvar server-emacsclient-proc nil + "Non-nil if running commands for a client of our server. +If we are currently evaluating Lisp in response to client commands, +this variable contains the process for communicating with that +client.") + +(defun server-pager-sentinel (proc event) + (internal-default-process-sentinel proc event) + (when (equal event "finished\n") + (let ((emacsclient (process-get proc :emacsclient))) + (setf (process-get emacsclient :pipelines) + (delq proc (process-get emacsclient :pipelines))) + (server-delete-client emacsclient)))) + +(defun server-pager () + "Start a process reading from FDs passed in by the current client. +This function will start a process which will begin reading from the +FDs passed in by the current client and copying their input to a +*pager* buffer. + +This function should only be run by passing --eval to an emacsclient +that also has the -l or --pipeline option, like so: + echo some data | emacsclient -l --eval '(server-pager)'" + ;; we remove two fds from the emacsclient process, and add ourselves + ;; in for later deletion when the emacsclient quits + (if (null server-emacsclient-proc) + (error "Cannot be run out of emacsclient --eval context") + (let ((buf (get-buffer "*pager*"))) + (when buf (kill-buffer buf))) + (let* ((infd (pop (process-get server-emacsclient-proc :fds))) + (outfd (pop (process-get server-emacsclient-proc :fds))) + (buffer (generate-new-buffer "*pager*")) + (proc (make-fd-process :name "pager-proc" + :buffer buffer + :noquery t + :sentinel #'server-pager-sentinel + :infd infd + :outfd outfd + :plist (list :emacsclient server-emacsclient-proc)))) + (push proc (process-get server-emacsclient-proc :pipelines)) + (pop-to-buffer buffer) + proc))) + + (defun server-execute (proc files nowait commands dontkill frame tty-name) ;; This is run from timers and process-filters, i.e. "asynchronously". ;; But w.r.t the user, this is not really asynchronous since the timer @@ -1272,7 +1323,8 @@ server-execute ;; including code that needs to wait. (with-local-quit (condition-case err - (let ((buffers (server-visit-files files proc nowait))) + (let ((buffers (server-visit-files files proc nowait)) + (server-emacsclient-proc proc)) (mapc 'funcall (nreverse commands)) ;; If we were told only to open a new client, obey @@ -1294,7 +1346,7 @@ server-execute ;; Client requested nowait; return immediately. (server-log "Close nowait client" proc) (server-delete-client proc)) - ((and (not dontkill) (null buffers)) + ((and (not dontkill) (null buffers) (null (process-get proc :pipelines))) ;; This client is empty; get rid of it immediately. (server-log "Close empty client" proc) (server-delete-client proc))) -- 2.8.2 ^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 3/5] emacsclient: support passing stdin/out/err to emacs 2016-06-07 1:25 Teaching emacsclient to act as a pager, and more Spencer Baugh 2016-06-07 1:25 ` [PATCH 1/5] process: add features for direct use of FDs Spencer Baugh 2016-06-07 1:25 ` [PATCH 2/5] server.el: accept FDs from emacsclient Spencer Baugh @ 2016-06-07 1:25 ` Spencer Baugh 2016-06-07 1:25 ` [PATCH 4/5] server: add pager tapping and show-active Spencer Baugh ` (6 subsequent siblings) 9 siblings, 0 replies; 40+ messages in thread From: Spencer Baugh @ 2016-06-07 1:25 UTC (permalink / raw) To: emacs-devel; +Cc: Spencer Baugh To make this more useful, the terminal name is now also determined by looking at stderr, not stdout. If stderr is redirected, we'll still have trouble, though... --- lib-src/emacsclient.c | 45 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c index aab9c4b..1b1f75a 100644 --- a/lib-src/emacsclient.c +++ b/lib-src/emacsclient.c @@ -76,6 +76,7 @@ char *w32_getenv (const char *); #include <stdio.h> #include <getopt.h> #include <unistd.h> +#include <fcntl.h> #include <pwd.h> #include <sys/stat.h> @@ -119,6 +120,12 @@ int quiet = 0; /* Nonzero means args are expressions to be evaluated. --eval. */ int eval = 0; +/* Nonzero means we will pass stdin/stdout/stderr to Emacs. --pipeline. */ +int pipeline = 0; + +/* Nonzero means pass stdin/stdout/stderr to Emacs on next write. */ +int send_fds_once = 0; + /* Nonzero means don't open a new frame. Inverse of --create-frame. */ int current_frame = 1; @@ -163,6 +170,7 @@ struct option longopts[] = { "version", no_argument, NULL, 'V' }, { "tty", no_argument, NULL, 't' }, { "nw", no_argument, NULL, 't' }, + { "pipeline", no_argument, NULL, 'l' }, { "create-frame", no_argument, NULL, 'c' }, { "alternate-editor", required_argument, NULL, 'a' }, { "frame-parameters", required_argument, NULL, 'F' }, @@ -468,7 +476,7 @@ decode_options (int argc, char **argv) { int opt = getopt_long_only (argc, argv, #ifndef NO_SOCKETS_IN_FILE_SYSTEM - "VHneqa:s:f:d:F:tc", + "VHneqla:s:f:d:F:tc", #else "VHneqa:f:d:F:tc", #endif @@ -492,6 +500,11 @@ decode_options (int argc, char **argv) case 's': socket_name = optarg; break; + + case 'l': + pipeline = 1; + send_fds_once = 1; + break; #endif case 'f': @@ -738,7 +751,33 @@ send_to_emacs (HSOCKET s, const char *data) if (sblen == SEND_BUFFER_SIZE || (sblen > 0 && send_buffer[sblen-1] == '\n')) { - int sent = send (s, send_buffer, sblen, 0); + int sent; + if (send_fds_once) { + struct iovec iov; + struct msghdr msgh; + char cbuf[512] = {}; + iov = (struct iovec) { .iov_base = send_buffer, + .iov_len = sblen, }; + msgh = (struct msghdr) { .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cbuf, + .msg_controllen = sizeof cbuf, }; + struct cmsghdr *cmsg; + int myfds[3] = { 0, 1, 2 }; + cmsg = CMSG_FIRSTHDR(&msgh); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof (myfds)); + /* Initialize the payload: */ + memcpy(CMSG_DATA(cmsg), myfds, sizeof (myfds)); + /* Sum of the length of all control messages in the buffer: */ + msgh.msg_controllen = cmsg->cmsg_len; + + sent = sendmsg (s, &msgh, 0); + send_fds_once = 0; + } else { + sent = send (s, send_buffer, sblen, 0); + } if (sent < 0) { message (true, "%s: failed to send %d bytes to socket: %s\n", @@ -1019,7 +1058,7 @@ static int find_tty (const char **tty_type, const char **tty_name, int noabort) { const char *type = egetenv ("TERM"); - const char *name = ttyname (fileno (stdout)); + const char *name = ttyname (fileno (stderr)); if (!name) { -- 2.8.2 ^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 4/5] server: add pager tapping and show-active 2016-06-07 1:25 Teaching emacsclient to act as a pager, and more Spencer Baugh ` (2 preceding siblings ...) 2016-06-07 1:25 ` [PATCH 3/5] emacsclient: support passing stdin/out/err to emacs Spencer Baugh @ 2016-06-07 1:25 ` Spencer Baugh 2016-06-07 1:25 ` [PATCH 5/5] emacsclient: add extra-quiet mode Spencer Baugh ` (5 subsequent siblings) 9 siblings, 0 replies; 40+ messages in thread From: Spencer Baugh @ 2016-06-07 1:25 UTC (permalink / raw) To: emacs-devel; +Cc: Spencer Baugh Add some extra features: - The tap argument to server-pager; if non-nil, any input received is sent right back out. This allows inserting emacsclient pagers in the middle of a pipeline. - server-pager-show-active will display the buffers of all active emacsclients. --- lisp/server.el | 90 +++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 67 insertions(+), 23 deletions(-) diff --git a/lisp/server.el b/lisp/server.el index 894f8ac..446b475 100644 --- a/lisp/server.el +++ b/lisp/server.el @@ -1275,42 +1275,86 @@ server-emacsclient-proc this variable contains the process for communicating with that client.") +(defvar server-pager-active-list nil + "List of all active pager processes in order of creation.") + +;; TODO make a function that is basically "server-delete-client-maybe" +;; which will delete the client iff there are no more resources +;; (buffers, frames, pipelines) associated with it (defun server-pager-sentinel (proc event) (internal-default-process-sentinel proc event) - (when (equal event "finished\n") - (let ((emacsclient (process-get proc :emacsclient))) - (setf (process-get emacsclient :pipelines) - (delq proc (process-get emacsclient :pipelines))) + (let ((emacsclient (process-get proc :emacsclient))) + (setf (process-get emacsclient :pipelines) + (delq proc (process-get emacsclient :pipelines))) + (setf server-pager-active-list + (delq proc server-pager-active-list)) + (when (= 0 (let ((frame-num 0)) + (dolist (f (frame-list)) + (when (eq emacsclient (frame-parameter f 'client)) + (setq frame-num (1+ frame-num)))) + frame-num)) (server-delete-client emacsclient)))) -(defun server-pager () +(defun server-pager-tap-filter (proc text) + (internal-default-process-filter proc text) + (process-send-string proc text)) + +(defun server-pager (&optional name tap) "Start a process reading from FDs passed in by the current client. This function will start a process which will begin reading from the FDs passed in by the current client and copying their input to a -*pager* buffer. +buffer. + +NAME is the name of the buffer to copy input to; if nil, *pager* is +used. If NAME is an empty string, that is treated as equivalent to +nil, for ease of use from the command line. + +If TAP is non-nil, all input to the stdin of the client will be copied +also to the stdout of the client, allowing a client invoking +server-pager to be inserted in the middle of a pipeline. This function should only be run by passing --eval to an emacsclient that also has the -l or --pipeline option, like so: echo some data | emacsclient -l --eval '(server-pager)'" ;; we remove two fds from the emacsclient process, and add ourselves ;; in for later deletion when the emacsclient quits - (if (null server-emacsclient-proc) - (error "Cannot be run out of emacsclient --eval context") - (let ((buf (get-buffer "*pager*"))) - (when buf (kill-buffer buf))) - (let* ((infd (pop (process-get server-emacsclient-proc :fds))) - (outfd (pop (process-get server-emacsclient-proc :fds))) - (buffer (generate-new-buffer "*pager*")) - (proc (make-fd-process :name "pager-proc" - :buffer buffer - :noquery t - :sentinel #'server-pager-sentinel - :infd infd - :outfd outfd - :plist (list :emacsclient server-emacsclient-proc)))) - (push proc (process-get server-emacsclient-proc :pipelines)) - (pop-to-buffer buffer) - proc))) + (when (equal "" name) (setq name nil)) + (with-current-buffer (or (and name (get-buffer-create name)) + (generate-new-buffer "*pager*")) + (if (null server-emacsclient-proc) + (error "Cannot be run out of emacsclient --eval context") + (let* ((infd (pop (process-get server-emacsclient-proc :fds))) + (outfd (pop (process-get server-emacsclient-proc :fds))) + (proc (make-fd-process :name (if name (concat name "-proc") "pager-proc") + :buffer (current-buffer) + :noquery t + :sentinel #'server-pager-sentinel + :filter (if tap #'server-pager-tap-filter + #'internal-default-process-filter) + :infd infd + :outfd outfd + :plist (list :emacsclient server-emacsclient-proc)))) + (push proc (process-get server-emacsclient-proc :pipelines)) + (add-to-list 'server-pager-active-list proc 'append) + (pop-to-buffer (current-buffer) '(display-buffer-same-window . nil)) + proc)))) + +(defun server-pager-show-active (&optional _ frame) + "Displays all active pagers in windows on the current frame." + (interactive) + (delete-other-windows) + (let ((buffers (mapcar #'process-buffer server-pager-active-list)) + (window (frame-selected-window frame)) + (windows (list (selected-window)))) + (dotimes (_ (- (length buffers) 1)) + (setq window (split-window window nil 'right)) + (message "window: %s, windows: %s" window windows) + (push window windows) + (balance-windows)) + (setq windows (nreverse windows)) + (message "%s %s" windows buffers) + (cl-mapcar #'set-window-buffer windows buffers) + (redisplay))) (defun server-execute (proc files nowait commands dontkill frame tty-name) -- 2.8.2 ^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 5/5] emacsclient: add extra-quiet mode 2016-06-07 1:25 Teaching emacsclient to act as a pager, and more Spencer Baugh ` (3 preceding siblings ...) 2016-06-07 1:25 ` [PATCH 4/5] server: add pager tapping and show-active Spencer Baugh @ 2016-06-07 1:25 ` Spencer Baugh 2016-06-08 15:51 ` Teaching emacsclient to act as a pager, and more Tassilo Horn ` (4 subsequent siblings) 9 siblings, 0 replies; 40+ messages in thread From: Spencer Baugh @ 2016-06-07 1:25 UTC (permalink / raw) To: emacs-devel; +Cc: Spencer Baugh For emacsclient to write any output at all while acting as a pipeline can be very disruptive. As a quick hack, passing -qq or -l will now silence absolutely all output from emacsclient. Eventually we should probably just write to /dev/tty instead of to stdout, since POSIX 10.1 says about /dev/tty: It is useful for programs or shell procedures that wish to be sure of writing messages to or reading data from the terminal no matter how output has been redirected. --- lib-src/emacsclient.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c index 1b1f75a..e1f35cb 100644 --- a/lib-src/emacsclient.c +++ b/lib-src/emacsclient.c @@ -436,6 +436,7 @@ static void message (bool, const char *, ...) ATTRIBUTE_FORMAT_PRINTF (2, 3); static void message (bool is_error, const char *format, ...) { + if (quiet > 1) return; va_list args; va_start (args, format); @@ -503,6 +504,7 @@ decode_options (int argc, char **argv) case 'l': pipeline = 1; + quiet = 2; send_fds_once = 1; break; #endif @@ -528,7 +530,7 @@ decode_options (int argc, char **argv) break; case 'q': - quiet = 1; + quiet++; break; case 'V': @@ -643,6 +645,7 @@ The following OPTIONS are accepted:\n\ -e, --eval Evaluate the FILE arguments as ELisp expressions\n\ -n, --no-wait Don't wait for the server to return\n\ -q, --quiet Don't display messages on success\n\ + Pass twice to suppress absolutely all output\n\ -d DISPLAY, --display=DISPLAY\n\ Visit the file in the given display\n\ ", "\ @@ -1542,7 +1545,8 @@ start_daemon_and_retry_set_socket (void) } else if (dpid < 0) { - fprintf (stderr, "Error: Cannot fork!\n"); + if (quiet <= 1) + fprintf (stderr, "Error: Cannot fork!\n"); exit (EXIT_FAILURE); } else @@ -1899,6 +1903,7 @@ main (int argc, char **argv) } else if (strprefix ("-print ", p)) { + if (quiet > 1) continue; /* -print STRING: Print STRING on the terminal. */ str = unquote_argument (p + strlen ("-print ")); if (needlf) @@ -1908,6 +1913,7 @@ main (int argc, char **argv) } else if (strprefix ("-print-nonl ", p)) { + if (quiet > 1) continue; /* -print-nonl STRING: Print STRING on the terminal. Used to continue a preceding -print command. */ str = unquote_argument (p + strlen ("-print-nonl ")); @@ -1916,6 +1922,7 @@ main (int argc, char **argv) } else if (strprefix ("-error ", p)) { + if (quiet > 1) continue; /* -error DESCRIPTION: Signal an error on the terminal. */ str = unquote_argument (p + strlen ("-error ")); if (needlf) @@ -1947,7 +1954,8 @@ main (int argc, char **argv) if (needlf) printf ("\n"); - fflush (stdout); + if (quiet <= 1) + fflush (stdout); while (fdatasync (1) != 0 && errno == EINTR) continue; -- 2.8.2 ^ permalink raw reply related [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-06-07 1:25 Teaching emacsclient to act as a pager, and more Spencer Baugh ` (4 preceding siblings ...) 2016-06-07 1:25 ` [PATCH 5/5] emacsclient: add extra-quiet mode Spencer Baugh @ 2016-06-08 15:51 ` Tassilo Horn 2016-06-08 16:13 ` Anders Lindgren 2016-06-09 0:25 ` raman 2016-06-09 11:31 ` H. Dieter Wilhelm ` (3 subsequent siblings) 9 siblings, 2 replies; 40+ messages in thread From: Tassilo Horn @ 2016-06-08 15:51 UTC (permalink / raw) To: Spencer Baugh; +Cc: emacs-devel Spencer Baugh <sbaugh@catern.com> writes: Hi Spencer, > I am sure that these patches are terrible style, not in keeping with > the Emacs coding conventions, and totally unportable (I wrote this on > GNU/Linux) - I just wanted to get out a quick proof of concept. I can't comment on that but using emacsclient as a PAGER is one of the top entries in my emacs wishlist. Thanks for working on that! Bye, Tassilo ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-06-08 15:51 ` Teaching emacsclient to act as a pager, and more Tassilo Horn @ 2016-06-08 16:13 ` Anders Lindgren 2016-06-08 17:30 ` Tassilo Horn 2016-06-09 0:25 ` raman 1 sibling, 1 reply; 40+ messages in thread From: Anders Lindgren @ 2016-06-08 16:13 UTC (permalink / raw) To: Spencer Baugh, emacs-devel [-- Attachment #1: Type: text/plain, Size: 647 bytes --] > > > I am sure that these patches are terrible style, not in keeping with > > the Emacs coding conventions, and totally unportable (I wrote this on > > GNU/Linux) - I just wanted to get out a quick proof of concept. > > I can't comment on that but using emacsclient as a PAGER is one of the > top entries in my emacs wishlist. Thanks for working on that! > Another approach is to use the LESSOPEN feature of "less" to spawn an Emacs in batch mode to perform syntax highlighting, uncompressing files, access remote files via tramp etc. This is available today using the "e2ansi" package ( https://github.com/Lindydancer/e2ansi). -- Anders [-- Attachment #2: Type: text/html, Size: 1117 bytes --] ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-06-08 16:13 ` Anders Lindgren @ 2016-06-08 17:30 ` Tassilo Horn 0 siblings, 0 replies; 40+ messages in thread From: Tassilo Horn @ 2016-06-08 17:30 UTC (permalink / raw) To: Anders Lindgren; +Cc: Spencer Baugh, emacs-devel Anders Lindgren <andlind@gmail.com> writes: Hi Anders, >> I can't comment on that but using emacsclient as a PAGER is one of >> the top entries in my emacs wishlist. Thanks for working on that! > > Another approach is to use the LESSOPEN feature of "less" to spawn an > Emacs in batch mode to perform syntax highlighting, uncompressing > files, access remote files via tramp etc. I mainly want emacsclient as a pager in order to have isearch and the emacs navigation commands. Bye, Tassilo ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-06-08 15:51 ` Teaching emacsclient to act as a pager, and more Tassilo Horn 2016-06-08 16:13 ` Anders Lindgren @ 2016-06-09 0:25 ` raman 1 sibling, 0 replies; 40+ messages in thread From: raman @ 2016-06-09 0:25 UTC (permalink / raw) To: Spencer Baugh; +Cc: emacs-devel Tassilo Horn <tsdh@gnu.org> writes: 1+. For now, I do this with a perl script that I found via GitHub, but seeing this functionality in Emacs/emacsclient would be wonderful. My specific use case -- I define the pipe to emacsclient as my printer command in XTerm -- then I can grab content from ssh running in an XTerm over to my local emacs.> Spencer Baugh <sbaugh@catern.com> writes: > > Hi Spencer, > >> I am sure that these patches are terrible style, not in keeping with >> the Emacs coding conventions, and totally unportable (I wrote this on >> GNU/Linux) - I just wanted to get out a quick proof of concept. > > I can't comment on that but using emacsclient as a PAGER is one of the > top entries in my emacs wishlist. Thanks for working on that! > > Bye, > Tassilo > -- ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-06-07 1:25 Teaching emacsclient to act as a pager, and more Spencer Baugh ` (5 preceding siblings ...) 2016-06-08 15:51 ` Teaching emacsclient to act as a pager, and more Tassilo Horn @ 2016-06-09 11:31 ` H. Dieter Wilhelm 2016-06-27 22:42 ` Ole JørgenBrønner ` (2 subsequent siblings) 9 siblings, 0 replies; 40+ messages in thread From: H. Dieter Wilhelm @ 2016-06-09 11:31 UTC (permalink / raw) To: emacs-devel Spencer Baugh <sbaugh@catern.com> writes: > Hi emacs-devel, > > I've added the ability for emacsclient to act as a pager, such that > you can pipe data to emacsclient and page through it in the emacs > frame of your choice. The attached patches are on top of commit +1 thanks > 549470fdf234acb4da7941e3bb9b28ed63a51876 > > Here is a video demo: https://catern.com/files/emacspager.webm > > To do this, I've used the file descriptor passing feature of Unix > sockets, which allows a process to transmit a duplicate of any of its > open file descriptors over a Unix socket to another process. The other > process can then make full use of that file descriptor, including, of > course, reading and writing from it. > > In the attached patches, I taught emacsclient to (when a new option > --pipeline/-l is passed) send its stdin/stdout/stderr to the emacs > server, and I taught the emacs server to accept those file descriptors > and make an Elisp process out of them. Then the process machinery does > the rest of the work of reading data from the Elisp process (which is > actually data coming in on emacsclient's stdin) and putting it in a > buffer. > > This functionality is exposed to Elisp by simply directly passing > received file descriptor numbers to the process filter function (with > a new argument, to processes that have opted in with a new keyword > argument :ancillary). Those file descriptor numbers can be passed to a > new function make-fd-process. > > I've written a function in Elisp, server-pager, which should be run > with emacsclient -l --eval (server-pager). I added a new dynamic > variable server-emacsclient-proc which is non-nil if we are currently > evaluating Lisp for an emacsclient, and holds the Elisp process > corresponding to that Emacsclient. server-pager uses this variable to > retrieve the file descriptors for the current emacs-client, invoke > make-fd-process, and pop-to-buffer the output buffer. server-pager > stores the Elisp process it creates in the plist of the corresponding > emacsclient, so when the Elisp process is killed, emacsclient is told > to exit (if it didn't also open frames). Likewise if emacsclient is > killed, the server-pager Elisp process is killed. > > The primary issue here, it seems to me, is that this leads to huge > amounts of file descriptor leakage - if any passed-in file descriptor > is unused, it is eternally left open. I think a good way to fix this > is to add a native Elisp file descriptor type, so the file descriptor > can be closed on garbage collect. But perhaps there's a better > solution? In any case I would need guidance on how to create such a > new Elisp type - this is my first attempt to hack on the Emacs C > codebase. > > I am sure that these patches are terrible style, not in keeping with > the Emacs coding conventions, and totally unportable (I wrote this on > GNU/Linux) - I just wanted to get out a quick proof of concept. > > > -- Best wishes H. Dieter Wilhelm Kelkheim, Germany ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-06-07 1:25 Teaching emacsclient to act as a pager, and more Spencer Baugh ` (6 preceding siblings ...) 2016-06-09 11:31 ` H. Dieter Wilhelm @ 2016-06-27 22:42 ` Ole JørgenBrønner 2016-07-24 18:22 ` sbaugh 2016-09-09 13:27 ` Teaching emacsclient to act as a pager, and more sbaugh 9 siblings, 0 replies; 40+ messages in thread From: Ole JørgenBrønner @ 2016-06-27 22:42 UTC (permalink / raw) To: emacs-devel Spencer Baugh <sbaugh <at> catern.com> writes: > > Hi emacs-devel, > > I've added the ability for emacsclient to act as a pager, such that > you can pipe data to emacsclient and page through it in the emacs > frame of your choice. [snip] Hi, just wanted to say that this looks great. Really hope it's accepted :) When I started using emacs a while ago I was really surprised that this wasn't possible out-of-the-box (I know about some workarounds). I guess it's because most emacs users mostly live inside emacs? ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-06-07 1:25 Teaching emacsclient to act as a pager, and more Spencer Baugh ` (7 preceding siblings ...) 2016-06-27 22:42 ` Ole JørgenBrønner @ 2016-07-24 18:22 ` sbaugh 2016-09-09 13:42 ` Noam Postavsky 2016-09-09 13:27 ` Teaching emacsclient to act as a pager, and more sbaugh 9 siblings, 1 reply; 40+ messages in thread From: sbaugh @ 2016-07-24 18:22 UTC (permalink / raw) To: emacs-devel Hi emacs-devel, Just hoping again for some experienced comments on my approach/patches. I hope to get this feature into Emacs for Emacs 26... Thanks! ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-07-24 18:22 ` sbaugh @ 2016-09-09 13:42 ` Noam Postavsky 2016-09-09 14:14 ` sbaugh 0 siblings, 1 reply; 40+ messages in thread From: Noam Postavsky @ 2016-09-09 13:42 UTC (permalink / raw) To: sbaugh; +Cc: Emacs developers On Sun, Jul 24, 2016 at 2:22 PM, <sbaugh@catern.com> wrote: > > Just hoping again for some experienced comments on my > approach/patches. I hope to get this feature into Emacs for Emacs 26... > I don't think you ever attached your patches, at least I don't see them. ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-09-09 13:42 ` Noam Postavsky @ 2016-09-09 14:14 ` sbaugh 2016-09-09 14:59 ` Stefan Monnier 2016-09-09 15:53 ` Eli Zaretskii 0 siblings, 2 replies; 40+ messages in thread From: sbaugh @ 2016-09-09 14:14 UTC (permalink / raw) To: emacs-devel [-- Attachment #1: Type: text/plain, Size: 239 bytes --] Noam Postavsky <npostavs@users.sourceforge.net> writes: > I don't think you ever attached your patches, at least I don't see them. I previously sent them as mail, here they are again as attachments (in the order they should be applied): [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-process-add-features-for-direct-use-of-FDs.patch --] [-- Type: text/x-diff, Size: 12109 bytes --] From c2ff58ce764801101fb4c37fd3963f99cb8652a2 Mon Sep 17 00:00:00 2001 From: Spencer Baugh <sbaugh@catern.com> Date: Sun, 5 Jun 2016 00:40:28 -0400 Subject: [PATCH 1/5] process: add features for direct use of FDs - A new keyword argument for make-network-process, :ancillary. When non-nil, Emacs will check for ancillary data when reading from the network process. If any is found, it is passed as an additional argument to the process filter function. At the moment, this only supports reading passed file descriptors out of ancillary data. This is inherited by the children of server processes. - A new Lisp function make-fd-process, which accepts keyword arguments exactly like make-pipe-process, but also accepts :infd and :outfd, which take Lisp integers which should be file descriptors that can be read from and written to, respectively. --- src/process.c | 215 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/process.h | 2 + 2 files changed, 212 insertions(+), 5 deletions(-) diff --git a/src/process.c b/src/process.c index 9ca3e594..dc96166 100644 --- a/src/process.c +++ b/src/process.c @@ -21,6 +21,7 @@ along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */ #include <config.h> +#include <sys/socket.h> #include <stdio.h> #include <errno.h> #include <sys/types.h> /* Some typedefs are used in sys/file.h. */ @@ -2085,6 +2086,157 @@ create_pty (Lisp_Object process) p->pid = -2; } +DEFUN ("make-fd-process", Fmake_fd_process, Smake_fd_process, + 0, MANY, 0, + doc: /* Create a process from passed file descriptors. + +:infd FD + +:outfd FD + +usage: (make-fd-process &rest ARGS) */) + (ptrdiff_t nargs, Lisp_Object *args) +{ + Lisp_Object proc, contact; + struct Lisp_Process *p; + Lisp_Object name, buffer; + Lisp_Object tem; + int infd, outfd; + ptrdiff_t specpdl_count; + int inchannel, outchannel; + + if (nargs == 0) + return Qnil; + + contact = Flist (nargs, args); + + infd = XINT (Fplist_get (contact, QCinfd)); + outfd = XINT (Fplist_get (contact, QCoutfd)); + + name = Fplist_get (contact, QCname); + CHECK_STRING (name); + proc = make_process (name); + specpdl_count = SPECPDL_INDEX (); + record_unwind_protect (remove_process, proc); + p = XPROCESS (proc); + + outchannel = outfd; + inchannel = infd; + p->open_fd[WRITE_TO_SUBPROCESS] = outfd; + p->open_fd[READ_FROM_SUBPROCESS] = infd; + + fcntl (inchannel, F_SETFL, O_NONBLOCK); + fcntl (outchannel, F_SETFL, O_NONBLOCK); + +#ifdef WINDOWSNT + register_aux_fd (inchannel); +#endif + + /* Record this as an active process, with its channels. */ + chan_process[inchannel] = proc; + p->infd = inchannel; + p->outfd = outchannel; + + if (inchannel > max_process_desc) + max_process_desc = inchannel; + + buffer = Fplist_get (contact, QCbuffer); + if (NILP (buffer)) + buffer = name; + buffer = Fget_buffer_create (buffer); + pset_buffer (p, buffer); + + pset_childp (p, contact); + pset_plist (p, Fcopy_sequence (Fplist_get (contact, QCplist))); + pset_type (p, Qpipe); + pset_sentinel (p, Fplist_get (contact, QCsentinel)); + pset_filter (p, Fplist_get (contact, QCfilter)); + pset_log (p, Qnil); + if (tem = Fplist_get (contact, QCnoquery), !NILP (tem)) + p->kill_without_query = 1; + if (tem = Fplist_get (contact, QCstop), !NILP (tem)) + pset_command (p, Qt); + eassert (! p->pty_flag); + + if (!EQ (p->command, Qt)) + { + FD_SET (inchannel, &input_wait_mask); + FD_SET (inchannel, &non_keyboard_wait_mask); + } + p->adaptive_read_buffering + = (NILP (Vprocess_adaptive_read_buffering) ? 0 + : EQ (Vprocess_adaptive_read_buffering, Qt) ? 1 : 2); + + /* Make the process marker point into the process buffer (if any). */ + if (BUFFERP (buffer)) + set_marker_both (p->mark, buffer, + BUF_ZV (XBUFFER (buffer)), + BUF_ZV_BYTE (XBUFFER (buffer))); + + { + /* Setup coding systems for communicating with the network stream. */ + + /* Qt denotes we have not yet called Ffind_operation_coding_system. */ + Lisp_Object coding_systems = Qt; + Lisp_Object val; + + tem = Fplist_get (contact, QCcoding); + val = Qnil; + if (!NILP (tem)) + { + val = tem; + if (CONSP (val)) + val = XCAR (val); + } + else if (!NILP (Vcoding_system_for_read)) + val = Vcoding_system_for_read; + else if ((!NILP (buffer) && NILP (BVAR (XBUFFER (buffer), enable_multibyte_characters))) + || (NILP (buffer) && NILP (BVAR (&buffer_defaults, enable_multibyte_characters)))) + /* We dare not decode end-of-line format by setting VAL to + Qraw_text, because the existing Emacs Lisp libraries + assume that they receive bare code including a sequence of + CR LF. */ + val = Qnil; + else + { + if (CONSP (coding_systems)) + val = XCAR (coding_systems); + else if (CONSP (Vdefault_process_coding_system)) + val = XCAR (Vdefault_process_coding_system); + else + val = Qnil; + } + pset_decode_coding_system (p, val); + + if (!NILP (tem)) + { + val = tem; + if (CONSP (val)) + val = XCDR (val); + } + else if (!NILP (Vcoding_system_for_write)) + val = Vcoding_system_for_write; + else if (NILP (BVAR (current_buffer, enable_multibyte_characters))) + val = Qnil; + else + { + if (CONSP (coding_systems)) + val = XCDR (coding_systems); + else if (CONSP (Vdefault_process_coding_system)) + val = XCDR (Vdefault_process_coding_system); + else + val = Qnil; + } + pset_encode_coding_system (p, val); + } + /* This may signal an error. */ + setup_process_coding_systems (proc); + + specpdl_ptr = specpdl + specpdl_count; + + return proc; +} + DEFUN ("make-pipe-process", Fmake_pipe_process, Smake_pipe_process, 0, MANY, 0, doc: /* Create and return a bidirectional pipe process. @@ -3919,6 +4071,7 @@ usage: (make-network-process &rest ARGS) */) p->kill_without_query = 1; if ((tem = Fplist_get (contact, QCstop), !NILP (tem))) pset_command (p, Qt); + p->ancillary_data = !NILP (Fplist_get (contact, QCancillary_data)); p->pid = 0; p->backlog = 5; p->is_non_blocking_client = false; @@ -4574,6 +4727,11 @@ server_accept_connection (Lisp_Object server, int channel) p = XPROCESS (proc); + /* TODO: I think this is supposed to be done by checking for a + property in contact, not by just copying the field from the + server process, but I'm not sure exactly what is correct */ + p->ancillary_data = ps->ancillary_data; + /* Build new contact information for this setup. */ contact = Fcopy_sequence (ps->childp); contact = Fplist_put (contact, QCserver, Qnil); @@ -5587,6 +5745,7 @@ read_process_output_error_handler (Lisp_Object error_val) static void read_and_dispose_of_process_output (struct Lisp_Process *p, char *chars, ssize_t nbytes, + Lisp_Object *fds, size_t nfds, struct coding_system *coding); /* Read pending output from the process channel, @@ -5611,6 +5770,9 @@ read_process_output (Lisp_Object proc, int channel) ptrdiff_t count = SPECPDL_INDEX (); Lisp_Object odeactivate; char chars[sizeof coding->carryover + readmax]; + char cbuf[512] = {}; + int nfds = 0; + int *fd_data = NULL; if (carryover) /* See the comment above. */ @@ -5639,8 +5801,34 @@ read_process_output (Lisp_Object proc, int channel) readmax - buffered); else #endif - nbytes = emacs_read (channel, chars + carryover + buffered, - readmax - buffered); + { + struct stat statbuf; + fstat(channel, &statbuf); + if (S_ISSOCK(statbuf.st_mode) && p->ancillary_data) { + /* we declare cbuf outside of here since it is the backing + storage for the fd_data array */ + struct iovec iov = { .iov_base = chars + carryover + buffered, + .iov_len = readmax - buffered, }; + struct msghdr msgh = { .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cbuf, + .msg_controllen = sizeof cbuf, }; + nbytes = recvmsg(channel, &msgh, MSG_CMSG_CLOEXEC); + /* check for control messages */ + for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msgh, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET + && cmsg->cmsg_type == SCM_RIGHTS) { + nfds = cmsg->cmsg_len / (sizeof(int)); + fd_data = (int *) CMSG_DATA(cmsg); + } + } + } else { + nbytes = emacs_read (channel, chars + carryover + buffered, + readmax - buffered); + } + } + if (nbytes > 0 && p->adaptive_read_buffering) { int delay = p->read_output_delay; @@ -5669,6 +5857,12 @@ read_process_output (Lisp_Object proc, int channel) nbytes += buffered; nbytes += buffered && nbytes <= 0; } + /* if we saw any fds, put them in an array of Lisp_Objects; this is + a no-op if we saw no fds */ + Lisp_Object fds[nfds]; + for (int i = 0; i < nfds; i++) { + fds[i] = make_number(fd_data[i]); + } p->decoding_carryover = 0; @@ -5690,7 +5884,7 @@ read_process_output (Lisp_Object proc, int channel) friends don't expect current-buffer to be changed from under them. */ record_unwind_current_buffer (); - read_and_dispose_of_process_output (p, chars, nbytes, coding); + read_and_dispose_of_process_output (p, chars, nbytes, fds, nfds, coding); /* Handling the process output should not deactivate the mark. */ Vdeactivate_mark = odeactivate; @@ -5702,6 +5896,7 @@ read_process_output (Lisp_Object proc, int channel) static void read_and_dispose_of_process_output (struct Lisp_Process *p, char *chars, ssize_t nbytes, + Lisp_Object *fds, size_t nfds, struct coding_system *coding) { Lisp_Object outstream = p->filter; @@ -5775,14 +5970,20 @@ read_and_dispose_of_process_output (struct Lisp_Process *p, char *chars, coding->carryover_bytes); p->decoding_carryover = coding->carryover_bytes; } - if (SBYTES (text) > 0) + if (SBYTES (text) > 0) { /* FIXME: It's wrong to wrap or not based on debug-on-error, and sometimes it's simply wrong to wrap (e.g. when called from accept-process-output). */ + Lisp_Object form; + if (p->ancillary_data) + form = list4 (outstream, make_lisp_proc (p), text, Flist(nfds, fds)); + else + form = list3 (outstream, make_lisp_proc (p), text); internal_condition_case_1 (read_process_output_call, - list3 (outstream, make_lisp_proc (p), text), + form, !NILP (Vdebug_on_error) ? Qnil : Qerror, read_process_output_error_handler); + } /* If we saved the match data nonrecursively, restore it now. */ restore_search_regs (); @@ -7866,6 +8067,7 @@ syms_of_process (void) DEFSYM (QCnowait, ":nowait"); DEFSYM (QCsentinel, ":sentinel"); DEFSYM (QCuse_external_socket, ":use-external-socket"); + DEFSYM (QCancillary_data, ":ancillary-data"); DEFSYM (QCtls_parameters, ":tls-parameters"); DEFSYM (Qnsm_verify_connection, "nsm-verify-connection"); DEFSYM (QClog, ":log"); @@ -7877,6 +8079,8 @@ syms_of_process (void) DEFSYM (QCstderr, ":stderr"); DEFSYM (Qpty, "pty"); DEFSYM (Qpipe, "pipe"); + DEFSYM (QCinfd, ":infd"); + DEFSYM (QCoutfd, ":outfd"); DEFSYM (Qlast_nonmenu_event, "last-nonmenu-event"); @@ -7979,6 +8183,7 @@ The variable takes effect when `start-process' is called. */); defsubr (&Sprocess_list); defsubr (&Smake_process); defsubr (&Smake_pipe_process); + defsubr (&Smake_fd_process); defsubr (&Sserial_process_configure); defsubr (&Smake_serial_process); defsubr (&Sset_network_process_option); diff --git a/src/process.h b/src/process.h index a5f690d..72ba204 100644 --- a/src/process.h +++ b/src/process.h @@ -166,6 +166,8 @@ struct Lisp_Process bool_bf is_non_blocking_client : 1; /* Whether this is a server or a client socket. */ bool_bf is_server : 1; + /* Whether the filter should have ancillary data passed to it */ + bool_bf ancillary_data : 1; int raw_status; /* The length of the socket backlog. */ int backlog; -- 2.9.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0002-server.el-accept-FDs-from-emacsclient.patch --] [-- Type: text/x-diff, Size: 5493 bytes --] From 9c3ce51b19c0a6fb34fc597aa08e5140b6d6274e Mon Sep 17 00:00:00 2001 From: Spencer Baugh <sbaugh@catern.com> Date: Sun, 5 Jun 2016 00:40:11 -0400 Subject: [PATCH 2/5] server.el: accept FDs from emacsclient The emacs server passes :ancillary t to make-network-process, and stores any file descriptors it receives from a client. --- lisp/server.el | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/lisp/server.el b/lisp/server.el index e4cf431..894f8ac 100644 --- a/lisp/server.el +++ b/lisp/server.el @@ -352,6 +352,9 @@ server-delete-client (when (and terminal (eq (terminal-live-p terminal) t)) (delete-terminal terminal)))) + ;; Delete associated processes using this client's fds + (mapc #'delete-process (process-get proc :pipelines)) + ;; Delete the client's process. (if (eq (process-status proc) 'open) (delete-process proc)) @@ -670,6 +673,7 @@ server-start :plist '(:authenticated nil)) (list :family 'local :service server-file + :ancillary-data t :plist '(:authenticated t))))) (unless server-process (error "Could not start server process")) (process-put server-process :server-file server-file) @@ -915,7 +919,7 @@ server-execute-continuation (process-put proc 'continuation nil) (if continuation (ignore-errors (funcall continuation))))) -(cl-defun server-process-filter (proc string) +(cl-defun server-process-filter (proc string &optional ancillary) "Process a request from the server to edit some files. PROC is the server process. STRING consists of a sequence of commands prefixed by a dash. Some commands have arguments; @@ -1015,6 +1019,9 @@ server-execute-continuation Suspend this terminal, i.e., stop the client process. Sent when the user presses C-z." (server-log (concat "Received " string) proc) + (when ancillary + (server-log (format "Received fds %s" ancillary) proc) + (process-put proc :fds ancillary)) ;; First things first: let's check the authentication (unless (process-get proc :authenticated) (if (and (string-match "-auth \\([!-~]+\\)\n?" string) @@ -1262,6 +1269,50 @@ server-execute-continuation ;; condition-case (error (server-return-error proc err)))) +(defvar server-emacsclient-proc nil + "Non-nil if running commands for a client of our server. +If we are currently evaluating Lisp in response to client commands, +this variable contains the process for communicating with that +client.") + +(defun server-pager-sentinel (proc event) + (internal-default-process-sentinel proc event) + (when (equal event "finished\n") + (let ((emacsclient (process-get proc :emacsclient))) + (setf (process-get emacsclient :pipelines) + (delq proc (process-get emacsclient :pipelines))) + (server-delete-client emacsclient)))) + +(defun server-pager () + "Start a process reading from FDs passed in by the current client. +This function will start a process which will begin reading from the +FDs passed in by the current client and copying their input to a +*pager* buffer. + +This function should only be run by passing --eval to an emacsclient +that also has the -l or --pipeline option, like so: + echo some data | emacsclient -l --eval '(server-pager)'" + ;; we remove two fds from the emacsclient process, and add ourselves + ;; in for later deletion when the emacsclient quits + (if (null server-emacsclient-proc) + (error "Cannot be run out of emacsclient --eval context") + (let ((buf (get-buffer "*pager*"))) + (when buf (kill-buffer buf))) + (let* ((infd (pop (process-get server-emacsclient-proc :fds))) + (outfd (pop (process-get server-emacsclient-proc :fds))) + (buffer (generate-new-buffer "*pager*")) + (proc (make-fd-process :name "pager-proc" + :buffer buffer + :noquery t + :sentinel #'server-pager-sentinel + :infd infd + :outfd outfd + :plist (list :emacsclient server-emacsclient-proc)))) + (push proc (process-get server-emacsclient-proc :pipelines)) + (pop-to-buffer buffer) + proc))) + + (defun server-execute (proc files nowait commands dontkill frame tty-name) ;; This is run from timers and process-filters, i.e. "asynchronously". ;; But w.r.t the user, this is not really asynchronous since the timer @@ -1272,7 +1323,8 @@ server-execute ;; including code that needs to wait. (with-local-quit (condition-case err - (let ((buffers (server-visit-files files proc nowait))) + (let ((buffers (server-visit-files files proc nowait)) + (server-emacsclient-proc proc)) (mapc 'funcall (nreverse commands)) ;; If we were told only to open a new client, obey @@ -1294,7 +1346,7 @@ server-execute ;; Client requested nowait; return immediately. (server-log "Close nowait client" proc) (server-delete-client proc)) - ((and (not dontkill) (null buffers)) + ((and (not dontkill) (null buffers) (null (process-get proc :pipelines))) ;; This client is empty; get rid of it immediately. (server-log "Close empty client" proc) (server-delete-client proc))) -- 2.9.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #4: 0003-emacsclient-support-passing-stdin-out-err-to-emacs.patch --] [-- Type: text/x-diff, Size: 3570 bytes --] From 4c2b02dabd72920c28008f73ca305d0bfe4f32c9 Mon Sep 17 00:00:00 2001 From: Spencer Baugh <sbaugh@catern.com> Date: Sun, 5 Jun 2016 00:40:00 -0400 Subject: [PATCH 3/5] emacsclient: support passing stdin/out/err to emacs To make this more useful, the terminal name is now also determined by looking at stderr, not stdout. If stderr is redirected, we'll still have trouble, though... --- lib-src/emacsclient.c | 45 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c index aab9c4b..1b1f75a 100644 --- a/lib-src/emacsclient.c +++ b/lib-src/emacsclient.c @@ -76,6 +76,7 @@ char *w32_getenv (const char *); #include <stdio.h> #include <getopt.h> #include <unistd.h> +#include <fcntl.h> #include <pwd.h> #include <sys/stat.h> @@ -119,6 +120,12 @@ int quiet = 0; /* Nonzero means args are expressions to be evaluated. --eval. */ int eval = 0; +/* Nonzero means we will pass stdin/stdout/stderr to Emacs. --pipeline. */ +int pipeline = 0; + +/* Nonzero means pass stdin/stdout/stderr to Emacs on next write. */ +int send_fds_once = 0; + /* Nonzero means don't open a new frame. Inverse of --create-frame. */ int current_frame = 1; @@ -163,6 +170,7 @@ struct option longopts[] = { "version", no_argument, NULL, 'V' }, { "tty", no_argument, NULL, 't' }, { "nw", no_argument, NULL, 't' }, + { "pipeline", no_argument, NULL, 'l' }, { "create-frame", no_argument, NULL, 'c' }, { "alternate-editor", required_argument, NULL, 'a' }, { "frame-parameters", required_argument, NULL, 'F' }, @@ -468,7 +476,7 @@ decode_options (int argc, char **argv) { int opt = getopt_long_only (argc, argv, #ifndef NO_SOCKETS_IN_FILE_SYSTEM - "VHneqa:s:f:d:F:tc", + "VHneqla:s:f:d:F:tc", #else "VHneqa:f:d:F:tc", #endif @@ -492,6 +500,11 @@ decode_options (int argc, char **argv) case 's': socket_name = optarg; break; + + case 'l': + pipeline = 1; + send_fds_once = 1; + break; #endif case 'f': @@ -738,7 +751,33 @@ send_to_emacs (HSOCKET s, const char *data) if (sblen == SEND_BUFFER_SIZE || (sblen > 0 && send_buffer[sblen-1] == '\n')) { - int sent = send (s, send_buffer, sblen, 0); + int sent; + if (send_fds_once) { + struct iovec iov; + struct msghdr msgh; + char cbuf[512] = {}; + iov = (struct iovec) { .iov_base = send_buffer, + .iov_len = sblen, }; + msgh = (struct msghdr) { .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cbuf, + .msg_controllen = sizeof cbuf, }; + struct cmsghdr *cmsg; + int myfds[3] = { 0, 1, 2 }; + cmsg = CMSG_FIRSTHDR(&msgh); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof (myfds)); + /* Initialize the payload: */ + memcpy(CMSG_DATA(cmsg), myfds, sizeof (myfds)); + /* Sum of the length of all control messages in the buffer: */ + msgh.msg_controllen = cmsg->cmsg_len; + + sent = sendmsg (s, &msgh, 0); + send_fds_once = 0; + } else { + sent = send (s, send_buffer, sblen, 0); + } if (sent < 0) { message (true, "%s: failed to send %d bytes to socket: %s\n", @@ -1019,7 +1058,7 @@ static int find_tty (const char **tty_type, const char **tty_name, int noabort) { const char *type = egetenv ("TERM"); - const char *name = ttyname (fileno (stdout)); + const char *name = ttyname (fileno (stderr)); if (!name) { -- 2.9.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #5: 0004-server-add-pager-tapping-and-show-active.patch --] [-- Type: text/x-diff, Size: 6023 bytes --] From ce21d4c9e0585adbec28020e25a227bbcba7a59f Mon Sep 17 00:00:00 2001 From: Spencer Baugh <sbaugh@catern.com> Date: Sun, 5 Jun 2016 16:35:18 -0400 Subject: [PATCH 4/5] server: add pager tapping and show-active Add some extra features: - The tap argument to server-pager; if non-nil, any input received is sent right back out. This allows inserting emacsclient pagers in the middle of a pipeline. - server-pager-show-active will display the buffers of all active emacsclients. --- lisp/server.el | 90 +++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 67 insertions(+), 23 deletions(-) diff --git a/lisp/server.el b/lisp/server.el index 894f8ac..446b475 100644 --- a/lisp/server.el +++ b/lisp/server.el @@ -1275,42 +1275,86 @@ server-emacsclient-proc this variable contains the process for communicating with that client.") +(defvar server-pager-active-list nil + "List of all active pager processes in order of creation.") + +;; TODO make a function that is basically "server-delete-client-maybe" +;; which will delete the client iff there are no more resources +;; (buffers, frames, pipelines) associated with it (defun server-pager-sentinel (proc event) (internal-default-process-sentinel proc event) - (when (equal event "finished\n") - (let ((emacsclient (process-get proc :emacsclient))) - (setf (process-get emacsclient :pipelines) - (delq proc (process-get emacsclient :pipelines))) + (let ((emacsclient (process-get proc :emacsclient))) + (setf (process-get emacsclient :pipelines) + (delq proc (process-get emacsclient :pipelines))) + (setf server-pager-active-list + (delq proc server-pager-active-list)) + (when (= 0 (let ((frame-num 0)) + (dolist (f (frame-list)) + (when (eq emacsclient (frame-parameter f 'client)) + (setq frame-num (1+ frame-num)))) + frame-num)) (server-delete-client emacsclient)))) -(defun server-pager () +(defun server-pager-tap-filter (proc text) + (internal-default-process-filter proc text) + (process-send-string proc text)) + +(defun server-pager (&optional name tap) "Start a process reading from FDs passed in by the current client. This function will start a process which will begin reading from the FDs passed in by the current client and copying their input to a -*pager* buffer. +buffer. + +NAME is the name of the buffer to copy input to; if nil, *pager* is +used. If NAME is an empty string, that is treated as equivalent to +nil, for ease of use from the command line. + +If TAP is non-nil, all input to the stdin of the client will be copied +also to the stdout of the client, allowing a client invoking +server-pager to be inserted in the middle of a pipeline. This function should only be run by passing --eval to an emacsclient that also has the -l or --pipeline option, like so: echo some data | emacsclient -l --eval '(server-pager)'" ;; we remove two fds from the emacsclient process, and add ourselves ;; in for later deletion when the emacsclient quits - (if (null server-emacsclient-proc) - (error "Cannot be run out of emacsclient --eval context") - (let ((buf (get-buffer "*pager*"))) - (when buf (kill-buffer buf))) - (let* ((infd (pop (process-get server-emacsclient-proc :fds))) - (outfd (pop (process-get server-emacsclient-proc :fds))) - (buffer (generate-new-buffer "*pager*")) - (proc (make-fd-process :name "pager-proc" - :buffer buffer - :noquery t - :sentinel #'server-pager-sentinel - :infd infd - :outfd outfd - :plist (list :emacsclient server-emacsclient-proc)))) - (push proc (process-get server-emacsclient-proc :pipelines)) - (pop-to-buffer buffer) - proc))) + (when (equal "" name) (setq name nil)) + (with-current-buffer (or (and name (get-buffer-create name)) + (generate-new-buffer "*pager*")) + (if (null server-emacsclient-proc) + (error "Cannot be run out of emacsclient --eval context") + (let* ((infd (pop (process-get server-emacsclient-proc :fds))) + (outfd (pop (process-get server-emacsclient-proc :fds))) + (proc (make-fd-process :name (if name (concat name "-proc") "pager-proc") + :buffer (current-buffer) + :noquery t + :sentinel #'server-pager-sentinel + :filter (if tap #'server-pager-tap-filter + #'internal-default-process-filter) + :infd infd + :outfd outfd + :plist (list :emacsclient server-emacsclient-proc)))) + (push proc (process-get server-emacsclient-proc :pipelines)) + (add-to-list 'server-pager-active-list proc 'append) + (pop-to-buffer (current-buffer) '(display-buffer-same-window . nil)) + proc)))) + +(defun server-pager-show-active (&optional _ frame) + "Displays all active pagers in windows on the current frame." + (interactive) + (delete-other-windows) + (let ((buffers (mapcar #'process-buffer server-pager-active-list)) + (window (frame-selected-window frame)) + (windows (list (selected-window)))) + (dotimes (_ (- (length buffers) 1)) + (setq window (split-window window nil 'right)) + (message "window: %s, windows: %s" window windows) + (push window windows) + (balance-windows)) + (setq windows (nreverse windows)) + (message "%s %s" windows buffers) + (cl-mapcar #'set-window-buffer windows buffers) + (redisplay))) (defun server-execute (proc files nowait commands dontkill frame tty-name) -- 2.9.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #6: 0005-emacsclient-add-extra-quiet-mode.patch --] [-- Type: text/x-diff, Size: 3307 bytes --] From ac7b10cedf8f41dc94f2cfc8d213da06edaa8836 Mon Sep 17 00:00:00 2001 From: Spencer Baugh <sbaugh@catern.com> Date: Mon, 6 Jun 2016 14:21:37 -0400 Subject: [PATCH 5/5] emacsclient: add extra-quiet mode For emacsclient to write any output at all while acting as a pipeline can be very disruptive. As a quick hack, passing -qq or -l will now silence absolutely all output from emacsclient. Eventually we should probably just write to /dev/tty instead of to stdout, since POSIX 10.1 says about /dev/tty: It is useful for programs or shell procedures that wish to be sure of writing messages to or reading data from the terminal no matter how output has been redirected. --- lib-src/emacsclient.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c index 1b1f75a..e1f35cb 100644 --- a/lib-src/emacsclient.c +++ b/lib-src/emacsclient.c @@ -436,6 +436,7 @@ static void message (bool, const char *, ...) ATTRIBUTE_FORMAT_PRINTF (2, 3); static void message (bool is_error, const char *format, ...) { + if (quiet > 1) return; va_list args; va_start (args, format); @@ -503,6 +504,7 @@ decode_options (int argc, char **argv) case 'l': pipeline = 1; + quiet = 2; send_fds_once = 1; break; #endif @@ -528,7 +530,7 @@ decode_options (int argc, char **argv) break; case 'q': - quiet = 1; + quiet++; break; case 'V': @@ -643,6 +645,7 @@ The following OPTIONS are accepted:\n\ -e, --eval Evaluate the FILE arguments as ELisp expressions\n\ -n, --no-wait Don't wait for the server to return\n\ -q, --quiet Don't display messages on success\n\ + Pass twice to suppress absolutely all output\n\ -d DISPLAY, --display=DISPLAY\n\ Visit the file in the given display\n\ ", "\ @@ -1542,7 +1545,8 @@ start_daemon_and_retry_set_socket (void) } else if (dpid < 0) { - fprintf (stderr, "Error: Cannot fork!\n"); + if (quiet <= 1) + fprintf (stderr, "Error: Cannot fork!\n"); exit (EXIT_FAILURE); } else @@ -1899,6 +1903,7 @@ main (int argc, char **argv) } else if (strprefix ("-print ", p)) { + if (quiet > 1) continue; /* -print STRING: Print STRING on the terminal. */ str = unquote_argument (p + strlen ("-print ")); if (needlf) @@ -1908,6 +1913,7 @@ main (int argc, char **argv) } else if (strprefix ("-print-nonl ", p)) { + if (quiet > 1) continue; /* -print-nonl STRING: Print STRING on the terminal. Used to continue a preceding -print command. */ str = unquote_argument (p + strlen ("-print-nonl ")); @@ -1916,6 +1922,7 @@ main (int argc, char **argv) } else if (strprefix ("-error ", p)) { + if (quiet > 1) continue; /* -error DESCRIPTION: Signal an error on the terminal. */ str = unquote_argument (p + strlen ("-error ")); if (needlf) @@ -1947,7 +1954,8 @@ main (int argc, char **argv) if (needlf) printf ("\n"); - fflush (stdout); + if (quiet <= 1) + fflush (stdout); while (fdatasync (1) != 0 && errno == EINTR) continue; -- 2.9.3 ^ permalink raw reply related [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-09-09 14:14 ` sbaugh @ 2016-09-09 14:59 ` Stefan Monnier 2016-09-09 15:58 ` sbaugh 2016-09-09 15:53 ` Eli Zaretskii 1 sibling, 1 reply; 40+ messages in thread From: Stefan Monnier @ 2016-09-09 14:59 UTC (permalink / raw) To: emacs-devel > - A new keyword argument for make-network-process, :ancillary. When > non-nil, Emacs will check for ancillary data when reading from the > network process. If any is found, it is passed as an additional argument > to the process filter function. At the moment, this only supports > reading passed file descriptors out of ancillary data. > This is inherited by the children of server processes. I don't have time to really review the code, but this part isn't quite right. IIUC you intend "ancillary data" to include potentially various things, tho currently the only one it can hold is FDs that were passed over a Unix socket, right? Yet, the way you pass it to the process filter doesn't tag the data as being file-descriptors as opposed to other kinds of ancillary data, so your design intends to be generic/extensible, but the way the data is passed to the filter doesn't have the corresponding extensibility. I have a question: what happens with current Emacs if you send a file-descriptor over such a Unix socket. Are these file descriptors just silently ignored, or do they block subsequent transmission (because Emacs fails to read them), or...? The reason I ask is that if current Emacs misbehaves when receiving such file-descriptors, maybe we don't need the `ancillary_data' bit. We could simply call the process filter with something else than a string (e.g. with a list of the form (:file-descriptors . FDS)) when we receive file-descriptors. Stefan ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-09-09 14:59 ` Stefan Monnier @ 2016-09-09 15:58 ` sbaugh 2016-09-09 19:26 ` Stefan Monnier 0 siblings, 1 reply; 40+ messages in thread From: sbaugh @ 2016-09-09 15:58 UTC (permalink / raw) To: emacs-devel Stefan Monnier <monnier@iro.umontreal.ca> writes: > I don't have time to really review the code, but this part isn't > quite right. IIUC you intend "ancillary data" to include potentially > various things, tho currently the only one it can hold is FDs that were > passed over a Unix socket, right? > > Yet, the way you pass it to the process filter doesn't tag the data as > being file-descriptors as opposed to other kinds of ancillary data, so > your design intends to be generic/extensible, but the way the data > is passed to the filter doesn't have the corresponding extensibility. Yeah, I was just doing something quick and obviously wrong. > I have a question: what happens with current Emacs if you send > a file-descriptor over such a Unix socket. Are these file descriptors > just silently ignored, or do they block subsequent transmission (because > Emacs fails to read them), or...? I'm answering this question mostly from conjecture, but... Current Emacs should silently ignore passed file descriptors. That's what happens by default if you aren't using recvmsg. > The reason I ask is that if current Emacs misbehaves when receiving such > file-descriptors, maybe we don't need the `ancillary_data' bit. > We could simply call the process filter with something else than > a string (e.g. with a list of the form (:file-descriptors . FDS)) when > we receive file-descriptors. Couldn't we still do that anyway, even if current Emacs does not misbehave when getting FDs? As long as it's opt-in. ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-09-09 15:58 ` sbaugh @ 2016-09-09 19:26 ` Stefan Monnier 2016-09-09 19:42 ` Eli Zaretskii ` (2 more replies) 0 siblings, 3 replies; 40+ messages in thread From: Stefan Monnier @ 2016-09-09 19:26 UTC (permalink / raw) To: emacs-devel > Couldn't we still do that anyway, even if current Emacs does not > misbehave when getting FDs? As long as it's opt-in. Sure, but we'd still need a `ancillary_data' bit then. BTW, another solution to the overarching process would be: - in emacsclient, mknod a named pipe, and then pass all input to that pipe and similarly pass all its output to stdout. - pass that named pipe as plain text to the Emacs daemon. - extend make-pipe-process to also work on named pipes. Or simpler: - emacsclient connects to emacs daemon in pretty much the normal way. - then (after warning the emacs daemon appropriately), turn the emacsclient into a simple proxy which reads from its stdin and passes that straight to the daemon's socket (and vice versa for the daemon socket's output). This last strategy has the potential to be usable/useful even through TCP connections. Stefan ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-09-09 19:26 ` Stefan Monnier @ 2016-09-09 19:42 ` Eli Zaretskii 2016-09-09 21:13 ` sbaugh 2016-09-10 20:15 ` Teaching emacsclient to act as a pager, and more sbaugh 2 siblings, 0 replies; 40+ messages in thread From: Eli Zaretskii @ 2016-09-09 19:42 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel > From: Stefan Monnier <monnier@iro.umontreal.ca> > Date: Fri, 09 Sep 2016 15:26:48 -0400 > > Or simpler: > - emacsclient connects to emacs daemon in pretty much the normal way. > - then (after warning the emacs daemon appropriately), turn the > emacsclient into a simple proxy which reads from its stdin and passes > that straight to the daemon's socket (and vice versa for the daemon > socket's output). If this works for the use case at hand, IMO it's much simpler and more elegant than any alternative. Thanks. ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-09-09 19:26 ` Stefan Monnier 2016-09-09 19:42 ` Eli Zaretskii @ 2016-09-09 21:13 ` sbaugh 2016-09-10 6:37 ` Using file descriptors in Emacs (was: Teaching emacsclient to act as a pager, and more) Eli Zaretskii 2016-09-10 20:15 ` Teaching emacsclient to act as a pager, and more sbaugh 2 siblings, 1 reply; 40+ messages in thread From: sbaugh @ 2016-09-09 21:13 UTC (permalink / raw) To: emacs-devel Stefan Monnier <monnier@iro.umontreal.ca> writes: >> Couldn't we still do that anyway, even if current Emacs does not >> misbehave when getting FDs? As long as it's opt-in. > - emacsclient connects to emacs daemon in pretty much the normal way. > - then (after warning the emacs daemon appropriately), turn the > emacsclient into a simple proxy which reads from its stdin and passes > that straight to the daemon's socket (and vice versa for the daemon > socket's output). > > This last strategy has the potential to be usable/useful even through > TCP connections. That is indeed an elegant API for solving this specific problem, and I could implement it as a fallback for when fd-passing is not available. It does have the issue that it adds another hop for the data to go through, and needs more complicated polling/non-blocking logic in emacsclient. But besides those issues, I still think an fd-based approach is useful. First, I think (as I said in another mail) that Emacs should learn to talk about file descriptors as a native Elisp type, since that would open up a lot of capabilities which would be useful for (among other things) improving Eshell. So, assuming that Emacs knows how to talk about file descriptors, and Emacs can act as a real Unix shell, it would be useful and interesting to be able to pass in arbitrary file descriptors into a single Emacs process. My patches use this capability to make a pager, of course. You could also, say, pass in a file descriptor pointing to a file opened with escalated privileges, and then manipulate it without needing to go through sudo/su/TRAMP. Or the ability to pass around file descriptors in this way could unlock novel shell features, since this is definitely not something traditional Unix shells can do. Anyway, the easiest way to pass an arbitrary file descriptor into Emacs is by putting that file descriptor in emacsclient's stdin or stdout and using these patches. And as I mentioned, this makes it trivially easy to use emacsclient as a pager or filter. So, even if the pager use case ends up always done through emacsclient-as-a-proxy, I would still like some guidance on how (as a first step) an FD type could be added to Emacs. ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Using file descriptors in Emacs (was: Teaching emacsclient to act as a pager, and more) 2016-09-09 21:13 ` sbaugh @ 2016-09-10 6:37 ` Eli Zaretskii 0 siblings, 0 replies; 40+ messages in thread From: Eli Zaretskii @ 2016-09-10 6:37 UTC (permalink / raw) To: sbaugh; +Cc: emacs-devel > From: sbaugh@catern.com > Date: Fri, 09 Sep 2016 17:13:33 -0400 > > So, even if the pager use case ends up always done through > emacsclient-as-a-proxy, I would still like some guidance on how (as a > first step) an FD type could be added to Emacs. OK, let's talk about this broader issue. Let's start by describing the use cases for this feature, and please describe those use features in Emacs terms. IOW, instead of "act as a real Unix shell", please describe what you'd like Emacs to do with the file descriptors in terms of Emacs functionality, like display or edit text, send or receive text to/from a subprocess, etc. Also, what kind of descriptors (i.e. what kinds of files/devices are they open on) would be useful in this context? I'm asking for this because Emacs is not a shell, and its repertoire of features is quite different from that of a Unix shell. So looking at a shell as our model is not necessarily useful. Thanks. ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-09-09 19:26 ` Stefan Monnier 2016-09-09 19:42 ` Eli Zaretskii 2016-09-09 21:13 ` sbaugh @ 2016-09-10 20:15 ` sbaugh 2016-09-11 2:11 ` Leo Liu 2018-02-16 23:14 ` Kaushal Modi 2 siblings, 2 replies; 40+ messages in thread From: sbaugh @ 2016-09-10 20:15 UTC (permalink / raw) To: emacs-devel [-- Attachment #1: Type: text/plain, Size: 480 bytes --] Stefan Monnier <monnier@iro.umontreal.ca> writes: > Or simpler: > - emacsclient connects to emacs daemon in pretty much the normal way. > - then (after warning the emacs daemon appropriately), turn the > emacsclient into a simple proxy which reads from its stdin and passes > that straight to the daemon's socket (and vice versa for the daemon > socket's output). I produced some patches that use this approach. Even with this approach, still not sure about the best API. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-emacsclient-add-term_message-wrapper-around-printf.patch --] [-- Type: text/x-diff, Size: 3936 bytes --] From a5d53800a79330ab8a0e3abdaccc0091cd2d153b Mon Sep 17 00:00:00 2001 From: Spencer Baugh <sbaugh@catern.com> Date: Sat, 10 Sep 2016 12:55:41 -0400 Subject: [PATCH 1/5] emacsclient: add term_message wrapper around printf --- lib-src/emacsclient.c | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c index 1991aaa..c9b4578 100644 --- a/lib-src/emacsclient.c +++ b/lib-src/emacsclient.c @@ -455,9 +455,25 @@ message (bool is_error, const char *format, ...) va_end (args); } +/* Display a normal or error message, always on the terminal. */ +static void term_message (bool, const char *, ...) ATTRIBUTE_FORMAT_PRINTF (2, 3); +static void +term_message (bool is_error, const char *format, ...) +{ + va_list args; + + va_start (args, format); + + FILE *f = is_error ? stderr : stdout; + + vfprintf (f, format, args); + fflush (f); + + va_end (args); +} + /* Decode the options from argv and argc. The global variable `optind' will say how many arguments we used up. */ - static void decode_options (int argc, char **argv) { @@ -1502,7 +1518,7 @@ start_daemon_and_retry_set_socket (void) } else if (dpid < 0) { - fprintf (stderr, "Error: Cannot fork!\n"); + term_message (true, "Error: Cannot fork!\n"); exit (EXIT_FAILURE); } else @@ -1802,7 +1818,7 @@ main (int argc, char **argv) /* Wait for an answer. */ if (!eval && !tty && !nowait && !quiet) { - printf ("Waiting for Emacs..."); + term_message (false, "Waiting for Emacs..."); needlf = 2; } fflush (stdout); @@ -1862,8 +1878,8 @@ main (int argc, char **argv) /* -print STRING: Print STRING on the terminal. */ str = unquote_argument (p + strlen ("-print ")); if (needlf) - printf ("\n"); - printf ("%s", str); + term_message (false, "\n"); + term_message (false, "%s", str); needlf = str[0] == '\0' ? needlf : str[strlen (str) - 1] != '\n'; } else if (strprefix ("-print-nonl ", p)) @@ -1871,7 +1887,7 @@ main (int argc, char **argv) /* -print-nonl STRING: Print STRING on the terminal. Used to continue a preceding -print command. */ str = unquote_argument (p + strlen ("-print-nonl ")); - printf ("%s", str); + term_message (false, "%s", str); needlf = str[0] == '\0' ? needlf : str[strlen (str) - 1] != '\n'; } else if (strprefix ("-error ", p)) @@ -1879,8 +1895,8 @@ main (int argc, char **argv) /* -error DESCRIPTION: Signal an error on the terminal. */ str = unquote_argument (p + strlen ("-error ")); if (needlf) - printf ("\n"); - fprintf (stderr, "*ERROR*: %s", str); + term_message (false, "\n"); + term_message (true, "*ERROR*: %s", str); needlf = str[0] == '\0' ? needlf : str[strlen (str) - 1] != '\n'; exit_status = EXIT_FAILURE; } @@ -1889,7 +1905,7 @@ main (int argc, char **argv) { /* -suspend: Suspend this terminal, i.e., stop the process. */ if (needlf) - printf ("\n"); + term_message (false, "\n"); needlf = 0; kill (0, SIGSTOP); } @@ -1898,15 +1914,15 @@ main (int argc, char **argv) { /* Unknown command. */ if (needlf) - printf ("\n"); + term_message (false, "\n"); needlf = 0; - printf ("*ERROR*: Unknown message: %s\n", p); + term_message (true, "*ERROR*: Unknown message: %s\n", p); } } } if (needlf) - printf ("\n"); + term_message (false, "\n"); fflush (stdout); while (fdatasync (1) != 0 && errno == EINTR) continue; -- 2.9.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0002-emacsclient-add-force-stderr-argument.patch --] [-- Type: text/x-diff, Size: 1907 bytes --] From dca1b7a8c41f0f431a6c80ac0aded42c4d7e5ec1 Mon Sep 17 00:00:00 2001 From: Spencer Baugh <sbaugh@catern.com> Date: Sat, 10 Sep 2016 13:00:47 -0400 Subject: [PATCH 2/5] emacsclient: add --force-stderr argument --- lib-src/emacsclient.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c index c9b4578..dfa49c5 100644 --- a/lib-src/emacsclient.c +++ b/lib-src/emacsclient.c @@ -119,6 +119,9 @@ int quiet = 0; /* Nonzero means args are expressions to be evaluated. --eval. */ int eval = 0; +/* Nonzero means to force all output to stderr. --force-stderr. */ +int force_stderr = 0; + /* Nonzero means don't open a new frame. Inverse of --create-frame. */ int current_frame = 1; @@ -172,6 +175,7 @@ struct option longopts[] = { "server-file", required_argument, NULL, 'f' }, { "display", required_argument, NULL, 'd' }, { "parent-id", required_argument, NULL, 'p' }, + { "force-stderr", no_argument, NULL, 'r' }, { 0, 0, 0, 0 } }; @@ -464,7 +468,7 @@ term_message (bool is_error, const char *format, ...) va_start (args, format); - FILE *f = is_error ? stderr : stdout; + FILE *f = (is_error || force_stderr) ? stderr : stdout; vfprintf (f, format, args); fflush (f); @@ -483,9 +487,9 @@ decode_options (int argc, char **argv) { int opt = getopt_long_only (argc, argv, #ifndef NO_SOCKETS_IN_FILE_SYSTEM - "VHneqa:s:f:d:F:tc", + "VHneqa:s:f:d:F:tcr", #else - "VHneqa:f:d:F:tc", + "VHneqa:f:d:F:tcr", #endif longopts, 0); @@ -560,6 +564,10 @@ decode_options (int argc, char **argv) frame_parameters = optarg; break; + case 'r': + force_stderr = 1; + break; + default: message (true, "Try '%s --help' for more information\n", progname); exit (EXIT_FAILURE); -- 2.9.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #4: 0003-emacsclient-find-tty-by-looking-at-stderr.patch --] [-- Type: text/x-diff, Size: 883 bytes --] From 0f59db6be2d1d20a0c97997b3f6a77c928fe3119 Mon Sep 17 00:00:00 2001 From: Spencer Baugh <sbaugh@catern.com> Date: Sat, 10 Sep 2016 14:26:04 -0400 Subject: [PATCH 3/5] emacsclient: find tty by looking at stderr The POSIX standard suggests that applications which need to find the controlling tty should look at stderr, since it's less likely to be redirected. Let's do this. --- lib-src/emacsclient.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c index dfa49c5..aeef93d 100644 --- a/lib-src/emacsclient.c +++ b/lib-src/emacsclient.c @@ -1042,7 +1042,7 @@ static int find_tty (const char **tty_type, const char **tty_name, int noabort) { const char *type = egetenv ("TERM"); - const char *name = ttyname (fileno (stdout)); + const char *name = ttyname (fileno (stderr)); if (!name) { -- 2.9.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #5: 0004-server-add-defvar-server-emacsclient-proc.patch --] [-- Type: text/x-diff, Size: 1475 bytes --] From de2ad77b39412cd396901008722f59ab01e0a9c1 Mon Sep 17 00:00:00 2001 From: Spencer Baugh <sbaugh@catern.com> Date: Sat, 10 Sep 2016 14:38:41 -0400 Subject: [PATCH 4/5] server: add defvar server-emacsclient-proc This is useful for implementing emacsclient-based paging. --- lisp/server.el | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lisp/server.el b/lisp/server.el index 5300984..93b3ca4 100644 --- a/lisp/server.el +++ b/lisp/server.el @@ -1262,6 +1262,12 @@ server-execute-continuation ;; condition-case (error (server-return-error proc err)))) +(defvar server-emacsclient-proc nil + "Non-nil if running commands for a client of the server. +If we are currently evaluating Lisp in response to client commands, +this variable contains the process for communicating with that +client.") + (defun server-execute (proc files nowait commands dontkill frame tty-name) ;; This is run from timers and process-filters, i.e. "asynchronously". ;; But w.r.t the user, this is not really asynchronous since the timer @@ -1272,7 +1278,8 @@ server-execute ;; including code that needs to wait. (with-local-quit (condition-case err - (let ((buffers (server-visit-files files proc nowait))) + (let ((buffers (server-visit-files files proc nowait)) + (server-emacsclient-proc proc)) (mapc 'funcall (nreverse commands)) ;; If we were told only to open a new client, obey -- 2.9.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #6: 0005-emacsclient-server.el-add-proxy-stdio-and-use.patch --] [-- Type: text/x-diff, Size: 5034 bytes --] From af56249cadefef58ca628a566e70a8a71e306bb9 Mon Sep 17 00:00:00 2001 From: Spencer Baugh <sbaugh@catern.com> Date: Sat, 10 Sep 2016 15:49:33 -0400 Subject: [PATCH 5/5] emacsclient, server.el: add -proxy-stdio and use When the client gets -proxy-stdio it will stop listening for or sending commands and instead just blindly copy its stdin to Emacs and from Emacs to its stdout. This mode can be activated by running a function like server-pager through emacsclient --eval "(server-pager)". --- lib-src/emacsclient.c | 43 +++++++++++++++++++++++++++++++++++++++++++ lisp/server.el | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c index aeef93d..f1a2c71 100644 --- a/lib-src/emacsclient.c +++ b/lib-src/emacsclient.c @@ -1622,6 +1622,35 @@ start_daemon_and_retry_set_socket (void) #endif /* WINDOWSNT */ } +int write_all (int fd, char *buf, size_t size); +int +write_all (int fd, char *buf, size_t size) +{ + int ret; + while (size > 0) { + ret = write (fd, buf, size); + if (ret == -1) return -1; + size -= ret; + } + return 0; +} + +int start_proxying (char *buf, size_t size); +int +start_proxying (char *buf, size_t size) +{ + int ret; + + /* not actually proxying output from emacs to our stdout yet */ + for (;;) { + ret = read (fileno (stdin), buf, size); + if (ret == 0) return EXIT_SUCCESS; + if (ret == -1) return EXIT_FAILURE; + ret = write_all (emacs_socket, buf, ret); + if (ret == -1) return EXIT_FAILURE; + } +} + int main (int argc, char **argv) { @@ -1918,6 +1947,20 @@ main (int argc, char **argv) kill (0, SIGSTOP); } #endif + else if (strprefix ("-proxy-stdio ", p)) + { + /* -proxy-stdio: Begin proxying stdio */ + /* All further data sent to us by Emacs (including the + * rest of this message), we should write out verbatim + * to stdout. */ + /* And we should start reading stdin and sending it + * verbatim to Emacs. */ + if (write_all (fileno(stdout), end_p, (string + rl) - end_p) != 0) + return EXIT_FAILURE; + /* We'll do this forever and do nothing else. */ + return start_proxying(string, sizeof(string)); + } + else { /* Unknown command. */ diff --git a/lisp/server.el b/lisp/server.el index 93b3ca4..c47ef74 100644 --- a/lisp/server.el +++ b/lisp/server.el @@ -1279,9 +1279,11 @@ server-execute (with-local-quit (condition-case err (let ((buffers (server-visit-files files proc nowait)) - (server-emacsclient-proc proc)) + (server-emacsclient-proc proc) + pager-func) (mapc 'funcall (nreverse commands)) + (setq pager-func (process-get proc 'server-client-pager-func)) ;; If we were told only to open a new client, obey ;; `initial-buffer-choice' if it specifies a file ;; or a function. @@ -1301,10 +1303,17 @@ server-execute ;; Client requested nowait; return immediately. (server-log "Close nowait client" proc) (server-delete-client proc)) - ((and (not dontkill) (null buffers)) + ((and (not dontkill) (null buffers) (not pager-func)) ;; This client is empty; get rid of it immediately. (server-log "Close empty client" proc) - (server-delete-client proc))) + (server-delete-client proc)) + ((and (not dontkill) (null buffers) pager-func) + ;; We've been instructed to turn this emacsclient into a pager + ;; this passes it outside the control of server.el, so for us it's like killing it + (server-send-string proc "-proxy-stdio \n") + ;; pager-func should set up a new process-filter and return promptly + (funcall pager-func proc) + (setq server-clients (delq proc server-clients)))) (cond ((or isearch-mode (minibufferp)) nil) @@ -1324,6 +1333,24 @@ server-execute (message "Quit emacsclient request")) (server-return-error proc err))))) +(defun server-pager () + "Create a buffer holding the stdin of the current client. +This function will create a buffer *pager* which will receive +input from the stdin of the current emacsclient. + +This function should only be run by passing --eval to emacsclient, +like so: + echo some data | emacsclient --eval '(server-pager)'" + (when (null server-emacsclient-proc) + (error "Cannot be run out of emacsclient --eval context")) + (process-put server-emacsclient-proc 'server-client-pager-func + (lambda (proc) + (let ((buffer (generate-new-buffer "*pager*"))) + (set-process-buffer proc buffer) + (set-process-filter proc nil) + (pop-to-buffer buffer)))) + nil) + (defun server-return-error (proc err) (ignore-errors (server-send-string -- 2.9.3 ^ permalink raw reply related [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-09-10 20:15 ` Teaching emacsclient to act as a pager, and more sbaugh @ 2016-09-11 2:11 ` Leo Liu 2018-02-16 23:14 ` Kaushal Modi 1 sibling, 0 replies; 40+ messages in thread From: Leo Liu @ 2016-09-11 2:11 UTC (permalink / raw) To: emacs-devel On 2016-09-10 16:15 -0400, sbaugh@catern.com wrote: > I produced some patches that use this approach. Even with this approach, > still not sure about the best API. why don't people use the bug tracker any more? Leo ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-09-10 20:15 ` Teaching emacsclient to act as a pager, and more sbaugh 2016-09-11 2:11 ` Leo Liu @ 2018-02-16 23:14 ` Kaushal Modi 2018-02-17 15:46 ` Göktuğ Kayaalp 1 sibling, 1 reply; 40+ messages in thread From: Kaushal Modi @ 2018-02-16 23:14 UTC (permalink / raw) To: sbaugh; +Cc: emacs-devel [-- Attachment #1: Type: text/plain, Size: 385 bytes --] On Sat, Sep 10, 2016 at 4:16 PM <sbaugh@catern.com> wrote: > > I produced some patches that use this approach. Even with this approach, > still not sure about the best API. > Hello Spencer, I am curious what happened to this effort (Thread back in Sep 2016[1]). This looks very interesting. Thanks. [1]: https://lists.gnu.org/r/emacs-devel/2016-06/msg00051.html -- Kaushal Modi [-- Attachment #2: Type: text/html, Size: 866 bytes --] ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2018-02-16 23:14 ` Kaushal Modi @ 2018-02-17 15:46 ` Göktuğ Kayaalp 0 siblings, 0 replies; 40+ messages in thread From: Göktuğ Kayaalp @ 2018-02-17 15:46 UTC (permalink / raw) To: Kaushal Modi; +Cc: sbaugh, emacs-devel On 2018-02-16 23:14 +00, Kaushal Modi <kaushal.modi@gmail.com> wrote: > On Sat, Sep 10, 2016 at 4:16 PM <sbaugh@catern.com> wrote: > >> >> I produced some patches that use this approach. Even with this approach, >> still not sure about the best API. >> > > Hello Spencer, > > I am curious what happened to this effort (Thread back in Sep 2016[1]). > This looks very interesting. +1. Personally I'd definitely like some mainstream way to pipe data into Emacs through emacsclient, and paging command output is only one possible use of such a feature. > Thanks. > > [1]: https://lists.gnu.org/r/emacs-devel/2016-06/msg00051.html -- İ. Göktuğ Kayaalp <https://www.gkayaalp.com/> 024C 30DD 597D 142B 49AC 40EB 465C D949 B101 2427 ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-09-09 14:14 ` sbaugh 2016-09-09 14:59 ` Stefan Monnier @ 2016-09-09 15:53 ` Eli Zaretskii 2016-09-09 17:16 ` sbaugh 1 sibling, 1 reply; 40+ messages in thread From: Eli Zaretskii @ 2016-09-09 15:53 UTC (permalink / raw) To: sbaugh; +Cc: emacs-devel > From: sbaugh@catern.com > Date: Fri, 09 Sep 2016 10:14:33 -0400 > > >From c2ff58ce764801101fb4c37fd3963f99cb8652a2 Mon Sep 17 00:00:00 2001 > From: Spencer Baugh <sbaugh@catern.com> > Date: Sun, 5 Jun 2016 00:40:28 -0400 > Subject: [PATCH 1/5] process: add features for direct use of FDs > > - A new keyword argument for make-network-process, :ancillary. When > non-nil, Emacs will check for ancillary data when reading from the > network process. If any is found, it is passed as an additional argument > to the process filter function. At the moment, this only supports > reading passed file descriptors out of ancillary data. > This is inherited by the children of server processes. > > - A new Lisp function make-fd-process, which accepts keyword arguments > exactly like make-pipe-process, but also accepts :infd and :outfd, which > take Lisp integers which should be file descriptors that can be read > from and written to, respectively. Cannot this be handled by the existing make-pipe-process? If not, why not? Thanks. ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-09-09 15:53 ` Eli Zaretskii @ 2016-09-09 17:16 ` sbaugh 2016-09-09 18:50 ` Eli Zaretskii 0 siblings, 1 reply; 40+ messages in thread From: sbaugh @ 2016-09-09 17:16 UTC (permalink / raw) To: emacs-devel Eli Zaretskii <eliz@gnu.org> writes: >> From: sbaugh@catern.com >> Date: Fri, 09 Sep 2016 10:14:33 -0400 >> >> >From c2ff58ce764801101fb4c37fd3963f99cb8652a2 Mon Sep 17 00:00:00 2001 >> From: Spencer Baugh <sbaugh@catern.com> >> Date: Sun, 5 Jun 2016 00:40:28 -0400 >> Subject: [PATCH 1/5] process: add features for direct use of FDs >> >> - A new keyword argument for make-network-process, :ancillary. When >> non-nil, Emacs will check for ancillary data when reading from the >> network process. If any is found, it is passed as an additional argument >> to the process filter function. At the moment, this only supports >> reading passed file descriptors out of ancillary data. >> This is inherited by the children of server processes. >> >> - A new Lisp function make-fd-process, which accepts keyword arguments >> exactly like make-pipe-process, but also accepts :infd and :outfd, which >> take Lisp integers which should be file descriptors that can be read >> from and written to, respectively. > > Cannot this be handled by the existing make-pipe-process? If not, why > not? > > Thanks. First off, make-fd-process is only an exact clone of make-pipe-process at the moment because that was the fastest way to get something working. Still, they are quite similar. But make-pipe-process makes a process object backed by a pipe. make-fd-process makes a process object backed by arbitrary file descriptors. I don't think it would be a good API to add :infd and :outfd arguments to make-pipe-process. That would cause make-pipe-process to make process objects backed by arbitrary file descriptors and not just pipes. For one, the name would become confusing. If there was a file-descriptor Elisp type, then one solution would be to add a make-pipe Elisp function that is just a thin wrapper around the pipe system call, returning two FD objects. Then make-pipe-process can just call make-pipe followed by make-fd-process. ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-09-09 17:16 ` sbaugh @ 2016-09-09 18:50 ` Eli Zaretskii 2016-09-09 19:03 ` sbaugh 0 siblings, 1 reply; 40+ messages in thread From: Eli Zaretskii @ 2016-09-09 18:50 UTC (permalink / raw) To: sbaugh; +Cc: emacs-devel > From: sbaugh@catern.com > Date: Fri, 09 Sep 2016 13:16:18 -0400 > > > Cannot this be handled by the existing make-pipe-process? If not, why > > not? > > > > Thanks. > > First off, make-fd-process is only an exact clone of make-pipe-process > at the moment because that was the fastest way to get something > working. Still, they are quite similar. > > But make-pipe-process makes a process object backed by a > pipe. make-fd-process makes a process object backed by arbitrary file > descriptors. Looking at your patches, it seemed to me that you indeed need a pipe, no? > I don't think it would be a good API to add :infd and :outfd arguments > to make-pipe-process. Why do you need to add them, a pipe already has 2 descriptors available. ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-09-09 18:50 ` Eli Zaretskii @ 2016-09-09 19:03 ` sbaugh 2016-09-09 19:26 ` Eli Zaretskii 0 siblings, 1 reply; 40+ messages in thread From: sbaugh @ 2016-09-09 19:03 UTC (permalink / raw) To: emacs-devel Eli Zaretskii <eliz@gnu.org> writes: >> From: sbaugh@catern.com >> Date: Fri, 09 Sep 2016 13:16:18 -0400 >> >> > Cannot this be handled by the existing make-pipe-process? If not, why >> > not? >> > >> > Thanks. >> >> First off, make-fd-process is only an exact clone of make-pipe-process >> at the moment because that was the fastest way to get something >> working. Still, they are quite similar. >> >> But make-pipe-process makes a process object backed by a >> pipe. make-fd-process makes a process object backed by arbitrary file >> descriptors. > > Looking at your patches, it seemed to me that you indeed need a pipe, > no? Not as far as I know - what in my patches suggests that I need a pipe? I am just passing in the arbitrary file-descriptors that emacsclient has as its stdin/stdout. They are directly moved into the main emacs process, not tunneled over a pipe. Those file descriptors frequently would be pipes, but could be regular files or anything else. And given that I have these two arbitrary file descriptors, I need to manipulate them somehow - and I do that by passing them into make-fd-process to make a process object backed by those two arbitrary file descriptors. ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-09-09 19:03 ` sbaugh @ 2016-09-09 19:26 ` Eli Zaretskii 2016-09-09 20:38 ` sbaugh 0 siblings, 1 reply; 40+ messages in thread From: Eli Zaretskii @ 2016-09-09 19:26 UTC (permalink / raw) To: sbaugh; +Cc: emacs-devel > From: sbaugh@catern.com > Date: Fri, 09 Sep 2016 15:03:23 -0400 > > > Looking at your patches, it seemed to me that you indeed need a pipe, > > no? > > Not as far as I know - what in my patches suggests that I need a pipe? A pipe is a set of 2 file descriptors between 2 programs, one of the descriptors is used for reading by one program and writing by the other, the other descriptor used similarly in the opposite direction. Isn't that what you set up? If not, then I must be missing something, so please describe the data flow between emacsclient, emacs server, and the external program. > I am just passing in the arbitrary file-descriptors that emacsclient has > as its stdin/stdout. They are directly moved into the main emacs > process, not tunneled over a pipe. Those file descriptors frequently > would be pipes, but could be regular files or anything else. And given > that I have these two arbitrary file descriptors, I need to manipulate > them somehow - and I do that by passing them into make-fd-process to > make a process object backed by those two arbitrary file descriptors. I'm trying to see if a higher-level abstraction can be used here, because passing file descriptors is too low-level and therefore less portable. Thanks. ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-09-09 19:26 ` Eli Zaretskii @ 2016-09-09 20:38 ` sbaugh 2016-09-10 7:12 ` Using file descriptors in Emacs Eli Zaretskii 0 siblings, 1 reply; 40+ messages in thread From: sbaugh @ 2016-09-09 20:38 UTC (permalink / raw) To: emacs-devel Eli Zaretskii <eliz@gnu.org> writes: >> From: sbaugh@catern.com >> Date: Fri, 09 Sep 2016 15:03:23 -0400 >> >> > Looking at your patches, it seemed to me that you indeed need a pipe, >> > no? >> >> Not as far as I know - what in my patches suggests that I need a pipe? > > A pipe is a set of 2 file descriptors between 2 programs, one of the > descriptors is used for reading by one program and writing by the > other, the other descriptor used similarly in the opposite direction. > Isn't that what you set up? > > If not, then I must be missing something, so please describe the data > flow between emacsclient, emacs server, and the external program. There is not necessarily any external program. Even emacsclient can exit just fine once the file descriptors are passed in and a process object created from them. Once a process object P is created, the data flow is as follows: When emacs writes to P, emacs is directly writing to the file descriptor that emacsclient had as its stdout. It is literally calling "write" on the file descriptor that was/is emacsclient's stdout. When emacs reads from P, emacs is directly reading from the file descriptor that emacsclient had as its stdin. It is literally calling "read" on the file descriptor that was/is emacsclient's stdin. If I invoke emacsclient in my shell with "emacsclient <foo >bar", reads from P will read from the file "bar", and writes to P will read from the file "foo", with the data in both cases passing directly to emacs and not first going through any other process. > >> I am just passing in the arbitrary file-descriptors that emacsclient has >> as its stdin/stdout. They are directly moved into the main emacs >> process, not tunneled over a pipe. Those file descriptors frequently >> would be pipes, but could be regular files or anything else. And given >> that I have these two arbitrary file descriptors, I need to manipulate >> them somehow - and I do that by passing them into make-fd-process to >> make a process object backed by those two arbitrary file descriptors. > > I'm trying to see if a higher-level abstraction can be used here, > because passing file descriptors is too low-level and therefore less > portable. That is true, however I do want to eventually get an API for handling file descriptors in Emacs, even if it is not portable. I believe it would be useful for quite a lot. For example I would like to bring eshell up to par with other Unix shells, and the ability to manipulate file descriptors directly is pretty much required for that. ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Using file descriptors in Emacs 2016-09-09 20:38 ` sbaugh @ 2016-09-10 7:12 ` Eli Zaretskii 2016-09-10 14:28 ` sbaugh 0 siblings, 1 reply; 40+ messages in thread From: Eli Zaretskii @ 2016-09-10 7:12 UTC (permalink / raw) To: sbaugh; +Cc: emacs-devel > From: sbaugh@catern.com > Date: Fri, 09 Sep 2016 16:38:16 -0400 > > >> Not as far as I know - what in my patches suggests that I need a pipe? > > > > A pipe is a set of 2 file descriptors between 2 programs, one of the > > descriptors is used for reading by one program and writing by the > > other, the other descriptor used similarly in the opposite direction. > > Isn't that what you set up? > > > > If not, then I must be missing something, so please describe the data > > flow between emacsclient, emacs server, and the external program. > > There is not necessarily any external program. Even emacsclient can exit > just fine once the file descriptors are passed in and a process object > created from them. > > Once a process object P is created, the data flow is as follows: > > When emacs writes to P, emacs is directly writing to the file descriptor > that emacsclient had as its stdout. It is literally calling "write" on > the file descriptor that was/is emacsclient's stdout. > > When emacs reads from P, emacs is directly reading from the file > descriptor that emacsclient had as its stdin. It is literally calling > "read" on the file descriptor that was/is emacsclient's stdin. Emacs doesn't normally read or write from/to file descriptors. The only features that do such things are communications with subprocesses, which could be either local subprocesses (in which case Emacs talks to them via a pipe) or remote processes (in which case we use network or serial connections). One other kind of feature that uses file descriptors is external data sources, such as D-Bus and inotify, which are a kind of local process (the OS kernel or some other daemon) that doesn't need to be run by Emacs, only communicated with. As we already have a framework for all of the above, what other kinds of descriptors, which don't fit into the framework, would we like to have? > For example I would like to bring eshell up to par with other Unix > shells, and the ability to manipulate file descriptors directly is > pretty much required for that. Please elaborate: how are file descriptors required for that? A shell needs to pass file descriptors to the subprocesses it invokes, which AFAIK we already do in the primitives that invoke subprocesses. What else is needed, and how does the feature you propose fit into that? ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Using file descriptors in Emacs 2016-09-10 7:12 ` Using file descriptors in Emacs Eli Zaretskii @ 2016-09-10 14:28 ` sbaugh 2016-09-11 15:28 ` Eli Zaretskii 0 siblings, 1 reply; 40+ messages in thread From: sbaugh @ 2016-09-10 14:28 UTC (permalink / raw) To: emacs-devel Eli Zaretskii <eliz@gnu.org> writes: >> From: sbaugh@catern.com >> Date: Fri, 09 Sep 2016 16:38:16 -0400 >> >> >> Not as far as I know - what in my patches suggests that I need a pipe? >> > >> > A pipe is a set of 2 file descriptors between 2 programs, one of the >> > descriptors is used for reading by one program and writing by the >> > other, the other descriptor used similarly in the opposite direction. >> > Isn't that what you set up? >> > >> > If not, then I must be missing something, so please describe the data >> > flow between emacsclient, emacs server, and the external program. >> >> There is not necessarily any external program. Even emacsclient can exit >> just fine once the file descriptors are passed in and a process object >> created from them. >> >> Once a process object P is created, the data flow is as follows: >> >> When emacs writes to P, emacs is directly writing to the file descriptor >> that emacsclient had as its stdout. It is literally calling "write" on >> the file descriptor that was/is emacsclient's stdout. >> >> When emacs reads from P, emacs is directly reading from the file >> descriptor that emacsclient had as its stdin. It is literally calling >> "read" on the file descriptor that was/is emacsclient's stdin. > > Emacs doesn't normally read or write from/to file descriptors. The > only features that do such things are communications with > subprocesses, which could be either local subprocesses (in which case > Emacs talks to them via a pipe) or remote processes (in which case we > use network or serial connections). One other kind of feature that > uses file descriptors is external data sources, such as D-Bus and > inotify, which are a kind of local process (the OS kernel or some > other daemon) that doesn't need to be run by Emacs, only communicated > with. > > As we already have a framework for all of the above, what other kinds > of descriptors, which don't fit into the framework, would we like to > have? > >> For example I would like to bring eshell up to par with other Unix >> shells, and the ability to manipulate file descriptors directly is >> pretty much required for that. > > Please elaborate: how are file descriptors required for that? A shell > needs to pass file descriptors to the subprocesses it invokes, which > AFAIK we already do in the primitives that invoke subprocesses. What > else is needed, and how does the feature you propose fit into that? Two features that I would like to add to eshell (or some shell in Emacs) that I believe will require the ability to directly manipulate file descriptors: - Piping data to and from processes without that data round-tripping through Emacs, which is necessary if I want to write a pipeline processing any significant amount of data (which I frequently do in even casual shell use) - Redirecting higher file descriptors than 0/1/2 (that is, when creating a process, before forking, use dup2 to rearrange what file descriptors are in what slots) Of the two, the first is more important, but I think the second is also necessary. ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Using file descriptors in Emacs 2016-09-10 14:28 ` sbaugh @ 2016-09-11 15:28 ` Eli Zaretskii 2016-09-11 16:00 ` sbaugh 0 siblings, 1 reply; 40+ messages in thread From: Eli Zaretskii @ 2016-09-11 15:28 UTC (permalink / raw) To: sbaugh; +Cc: emacs-devel > From: sbaugh@catern.com > Date: Sat, 10 Sep 2016 10:28:44 -0400 > > > Please elaborate: how are file descriptors required for that? A shell > > needs to pass file descriptors to the subprocesses it invokes, which > > AFAIK we already do in the primitives that invoke subprocesses. What > > else is needed, and how does the feature you propose fit into that? > > Two features that I would like to add to eshell (or some shell in Emacs) > that I believe will require the ability to directly manipulate file > descriptors: > > - Piping data to and from processes without that data round-tripping through > Emacs, which is necessary if I want to write a pipeline processing any > significant amount of data (which I frequently do in even casual shell > use) > - Redirecting higher file descriptors than 0/1/2 (that is, when creating > a process, before forking, use dup2 to rearrange what file descriptors are > in what slots) > > Of the two, the first is more important, but I think the second is also > necessary. It sounds like none of these two features need file descriptors to be exposed to Lisp. Both of these sound like extensions of start-process, and the descriptors could be dealt with entirely on the C level, as applications shouldn't care about them. Am I missing something? ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Using file descriptors in Emacs 2016-09-11 15:28 ` Eli Zaretskii @ 2016-09-11 16:00 ` sbaugh 2016-09-11 16:39 ` Eli Zaretskii 0 siblings, 1 reply; 40+ messages in thread From: sbaugh @ 2016-09-11 16:00 UTC (permalink / raw) To: emacs-devel Eli Zaretskii <eliz@gnu.org> writes: >> From: sbaugh@catern.com >> Date: Sat, 10 Sep 2016 10:28:44 -0400 >> Two features that I would like to add to eshell (or some shell in Emacs) >> that I believe will require the ability to directly manipulate file >> descriptors: >> >> - Piping data to and from processes without that data round-tripping through >> Emacs, which is necessary if I want to write a pipeline processing any >> significant amount of data (which I frequently do in even casual shell >> use) >> - Redirecting higher file descriptors than 0/1/2 (that is, when creating >> a process, before forking, use dup2 to rearrange what file descriptors are >> in what slots) > > It sounds like none of these two features need file descriptors to be > exposed to Lisp. Both of these sound like extensions of > start-process, and the descriptors could be dealt with entirely on the > C level, as applications shouldn't care about them. Am I missing > something? What kind of API based on extending start-process would allow these features to be used in full generality? I suppose one could add the capability to pass in a process object as the source of input for a new process object. This could be implemented by forcing the passed-in process object to a state where it does not have (and cannot have) a process-filter function. Then the file descriptor used for input to the passed-in process object could be manipulated appropriately to make it the stdin of the new process object. The new process object in turn will be in a special state where it cannot accept input from Emacs. Likewise for the capability to pass in a process object as the destination for a program's output, instead of a process-filter, and the capability to redirect arbitrary file descriptor numbers to point at process objects (or other file descriptors). Then also it is necessary to handle redirecting to and from files without going through Emacs. That could be done by creating a new kind of process object which is created by opening a file, and then that can be passed in in the way I describe above. But to me that sounds somewhat unnatural for the process API. Are those the kind of extensions you were envisioning? Such extensions would work fine for my purposes. ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Using file descriptors in Emacs 2016-09-11 16:00 ` sbaugh @ 2016-09-11 16:39 ` Eli Zaretskii 2016-09-11 16:57 ` sbaugh 0 siblings, 1 reply; 40+ messages in thread From: Eli Zaretskii @ 2016-09-11 16:39 UTC (permalink / raw) To: sbaugh; +Cc: emacs-devel > From: sbaugh@catern.com > Date: Sun, 11 Sep 2016 12:00:21 -0400 > > I suppose one could add the capability to pass in a process object as > the source of input for a new process object. This could be implemented > by forcing the passed-in process object to a state where it does not > have (and cannot have) a process-filter function. Then the file > descriptor used for input to the passed-in process object could be > manipulated appropriately to make it the stdin of the new process > object. The new process object in turn will be in a special state where > it cannot accept input from Emacs. > > Likewise for the capability to pass in a process object as the > destination for a program's output, instead of a process-filter, and the > capability to redirect arbitrary file descriptor numbers to point at > process objects (or other file descriptors). > > Then also it is necessary to handle redirecting to and from files > without going through Emacs. That could be done by creating a new kind > of process object which is created by opening a file, and then that can > be passed in in the way I describe above. Yes, something like that. > But to me that sounds somewhat unnatural for the process API. To me it sounds most natural. All of our APIs in this area are like that. > Are those the kind of extensions you were envisioning? Yes. Btw, running a pipe of processes raises another issue, unrelated to file descriptors: we would need a way to make sure the processes do not start running until all the redirections are set up, otherwise some of them will die with SIGPIPE or somesuch. ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Using file descriptors in Emacs 2016-09-11 16:39 ` Eli Zaretskii @ 2016-09-11 16:57 ` sbaugh 2016-09-11 17:13 ` Eli Zaretskii 2016-09-12 15:40 ` Davis Herring 0 siblings, 2 replies; 40+ messages in thread From: sbaugh @ 2016-09-11 16:57 UTC (permalink / raw) To: emacs-devel Eli Zaretskii <eliz@gnu.org> writes: >> From: sbaugh@catern.com >> Date: Sun, 11 Sep 2016 12:00:21 -0400 >> >> I suppose one could add the capability to pass in a process object as >> the source of input for a new process object. This could be implemented >> by forcing the passed-in process object to a state where it does not >> have (and cannot have) a process-filter function. Then the file >> descriptor used for input to the passed-in process object could be >> manipulated appropriately to make it the stdin of the new process >> object. The new process object in turn will be in a special state where >> it cannot accept input from Emacs. >> >> Likewise for the capability to pass in a process object as the >> destination for a program's output, instead of a process-filter, and the >> capability to redirect arbitrary file descriptor numbers to point at >> process objects (or other file descriptors). >> >> Then also it is necessary to handle redirecting to and from files >> without going through Emacs. That could be done by creating a new kind >> of process object which is created by opening a file, and then that can >> be passed in in the way I describe above. > > Yes, something like that. > >> But to me that sounds somewhat unnatural for the process API. > > To me it sounds most natural. All of our APIs in this area are like > that. OK, that works for me. I guess it's not really different from the API I would have constructed for file descriptors. I can hack up some patches for this. Or maybe people have more thoughts on the appropriate API first? > Btw, running a pipe of processes raises another issue, unrelated to > file descriptors: we would need a way to make sure the processes do > not start running until all the redirections are set up, otherwise > some of them will die with SIGPIPE or somesuch. No, SIGPIPE will only happen when the read end of the pipe is no longer open anywhere. If a process is writing to a pipe for which the read end of the pipe has not yet been passed to a subprocess, that process will just block, since we will still have the read end of the pipe open in the Emacs process. We would only close the read end of the pipe after forking, and the fork will make a copy of the read end of the pipe so it will still be open even while the redirection is in process. ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Using file descriptors in Emacs 2016-09-11 16:57 ` sbaugh @ 2016-09-11 17:13 ` Eli Zaretskii 2016-09-12 15:40 ` Davis Herring 1 sibling, 0 replies; 40+ messages in thread From: Eli Zaretskii @ 2016-09-11 17:13 UTC (permalink / raw) To: sbaugh; +Cc: emacs-devel > From: sbaugh@catern.com > Date: Sun, 11 Sep 2016 12:57:25 -0400 > > > To me it sounds most natural. All of our APIs in this area are like > > that. > > OK, that works for me. I guess it's not really different from the API I > would have constructed for file descriptors. > > I can hack up some patches for this. Or maybe people have more thoughts > on the appropriate API first? Yes, I'd like to hear other opinions as well. The above sounds like a natural extension of what we've been doing in that department, but that doesn't mean there couldn't be alternative ideas. > > Btw, running a pipe of processes raises another issue, unrelated to > > file descriptors: we would need a way to make sure the processes do > > not start running until all the redirections are set up, otherwise > > some of them will die with SIGPIPE or somesuch. > > No, SIGPIPE will only happen when the read end of the pipe is no longer > open anywhere. If a process is writing to a pipe for which the read end > of the pipe has not yet been passed to a subprocess, that process will > just block, since we will still have the read end of the pipe open in > the Emacs process. We would only close the read end of the pipe after > forking, and the fork will make a copy of the read end of the pipe so it > will still be open even while the redirection is in process. That's not universally true, AFAIK. ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Using file descriptors in Emacs 2016-09-11 16:57 ` sbaugh 2016-09-11 17:13 ` Eli Zaretskii @ 2016-09-12 15:40 ` Davis Herring 1 sibling, 0 replies; 40+ messages in thread From: Davis Herring @ 2016-09-12 15:40 UTC (permalink / raw) To: sbaugh; +Cc: emacs-devel [All sbaugh@catern.com:] >>> I suppose one could add the capability to pass in a process object as >>> the source of input for a new process object. This could be implemented >>> by forcing the passed-in process object to a state where it does not >>> have (and cannot have) a process-filter function. Then the file >>> descriptor used for input to the passed-in process object could be >>> manipulated appropriately to make it the stdin of the new process >>> object. The new process object in turn will be in a special state where >>> it cannot accept input from Emacs. [...] >>> Then also it is necessary to handle redirecting to and from files >>> without going through Emacs. That could be done by creating a new kind >>> of process object which is created by opening a file, and then that can >>> be passed in in the way I describe above. [...] > OK, that works for me. I guess it's not really different from the API I > would have constructed for file descriptors. Of course it's not any different: the phrases "[cannot] have ... a process-filter function", "a special state where it cannot accept input from Emacs", and especially "a new kind of process object which is created by opening a file" are telling you that these "generalized process objects" _are_ file descriptors. Of course, this is already the case in Emacs: `make-network-process' makes a socket which (on POSIX at least) is a file descriptor. The terminological confusion requires hedges like > However, a network process has no process id, it cannot be signaled, > and the status codes are different from normal processes. from its documentation. Davis -- This product is sold by volume, not by mass. If it appears too dense or too sparse, it is because mass-energy conversion has occurred during shipping. ^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Teaching emacsclient to act as a pager, and more 2016-06-07 1:25 Teaching emacsclient to act as a pager, and more Spencer Baugh ` (8 preceding siblings ...) 2016-07-24 18:22 ` sbaugh @ 2016-09-09 13:27 ` sbaugh 9 siblings, 0 replies; 40+ messages in thread From: sbaugh @ 2016-09-09 13:27 UTC (permalink / raw) To: emacs-devel Hello, Just pinging again for any comment from a more experienced Emacs developer... Thanks! ^ permalink raw reply [flat|nested] 40+ messages in thread
end of thread, other threads:[~2018-02-17 15:46 UTC | newest] Thread overview: 40+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2016-06-07 1:25 Teaching emacsclient to act as a pager, and more Spencer Baugh 2016-06-07 1:25 ` [PATCH 1/5] process: add features for direct use of FDs Spencer Baugh 2016-06-07 1:25 ` [PATCH 2/5] server.el: accept FDs from emacsclient Spencer Baugh 2016-06-07 1:25 ` [PATCH 3/5] emacsclient: support passing stdin/out/err to emacs Spencer Baugh 2016-06-07 1:25 ` [PATCH 4/5] server: add pager tapping and show-active Spencer Baugh 2016-06-07 1:25 ` [PATCH 5/5] emacsclient: add extra-quiet mode Spencer Baugh 2016-06-08 15:51 ` Teaching emacsclient to act as a pager, and more Tassilo Horn 2016-06-08 16:13 ` Anders Lindgren 2016-06-08 17:30 ` Tassilo Horn 2016-06-09 0:25 ` raman 2016-06-09 11:31 ` H. Dieter Wilhelm 2016-06-27 22:42 ` Ole JørgenBrønner 2016-07-24 18:22 ` sbaugh 2016-09-09 13:42 ` Noam Postavsky 2016-09-09 14:14 ` sbaugh 2016-09-09 14:59 ` Stefan Monnier 2016-09-09 15:58 ` sbaugh 2016-09-09 19:26 ` Stefan Monnier 2016-09-09 19:42 ` Eli Zaretskii 2016-09-09 21:13 ` sbaugh 2016-09-10 6:37 ` Using file descriptors in Emacs (was: Teaching emacsclient to act as a pager, and more) Eli Zaretskii 2016-09-10 20:15 ` Teaching emacsclient to act as a pager, and more sbaugh 2016-09-11 2:11 ` Leo Liu 2018-02-16 23:14 ` Kaushal Modi 2018-02-17 15:46 ` Göktuğ Kayaalp 2016-09-09 15:53 ` Eli Zaretskii 2016-09-09 17:16 ` sbaugh 2016-09-09 18:50 ` Eli Zaretskii 2016-09-09 19:03 ` sbaugh 2016-09-09 19:26 ` Eli Zaretskii 2016-09-09 20:38 ` sbaugh 2016-09-10 7:12 ` Using file descriptors in Emacs Eli Zaretskii 2016-09-10 14:28 ` sbaugh 2016-09-11 15:28 ` Eli Zaretskii 2016-09-11 16:00 ` sbaugh 2016-09-11 16:39 ` Eli Zaretskii 2016-09-11 16:57 ` sbaugh 2016-09-11 17:13 ` Eli Zaretskii 2016-09-12 15:40 ` Davis Herring 2016-09-09 13:27 ` Teaching emacsclient to act as a pager, and more sbaugh
Code repositories for project(s) associated with this public inbox https://git.savannah.gnu.org/cgit/emacs.git This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).