unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* 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-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

* 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: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 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: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 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               ` 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: 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: 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: 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: 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-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

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).