diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b3a4aa8..91b0450f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -124,6 +124,24 @@ This version includes a few minor updates to fzf's classic visual style: - Markers no longer use background colors. - The `--color base16` theme (alias: `16`) has been updated for better compatibility with both dark and light themes. +### `--listen` now supports Unix domain sockets + +If an argument to `--listen` ends with `.sock`, fzf will listen on a Unix +domain socket at the specified path. + +```sh +fzf --listen /tmp/fzf.sock --no-tmux + +# GET +curl --unix-socket /tmp/fzf.sock http + +# POST +curl --unix-socket /tmp/fzf.sock http -d up +``` + +Note that any existing file at the given path will be removed before creating +the socket, so avoid using an important file path. + ### Added options #### `--gutter CHAR` diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index 95f33fb6..b5135101 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -1133,19 +1133,25 @@ On Windows, the default value is \fBcmd /s/c\fR when \fB$SHELL\fR is not set. .TP -.B "\-\-listen[=[ADDR:]PORT]" "\-\-listen\-unsafe[=[ADDR:]PORT]" -Start HTTP server and listen on the given address. It allows external processes -to send actions to perform via POST method. +.B "\-\-listen[=SOCKET_PATH|[ADDR:]PORT]" "\-\-listen\-unsafe[=[ADDR:]PORT]" +Start HTTP server and listen on the given address or Unix socket. It allows +external processes to send actions to perform via POST method and query the +program state via GET method. For the argument to be recognized as a socket +path, it must have \fB.sock\fR extension. - If the port number is omitted or given as 0, fzf will automatically choose -a port and export it as \fBFZF_PORT\fR environment variable to the child processes +a port and export it as \fBFZF_PORT\fR environment variable to the child processes. + +- If a Unix socket path is given, fzf will create a Unix domain socket at the + given path. The existing file will be removed. The path to the socket file + is exported as \fBFZF_SOCK\fR environment variable. - If \fBFZF_API_KEY\fR environment variable is set, the server would require -sending an API key with the same value in the \fBx\-api\-key\fR HTTP header + sending an API key with the same value in the \fBx\-api\-key\fR HTTP header. -- \fBFZF_API_KEY\fR is required for a non-localhost listen address +- \fBFZF_API_KEY\fR is required for a non-localhost listen address. -- To allow remote process execution, use \fB\-\-listen\-unsafe\fR +- To allow remote process execution, use \fB\-\-listen\-unsafe\fR. e.g. \fB# Start HTTP server on port 6266 @@ -1184,6 +1190,18 @@ e.g. ' \fR +Here is an example script that uses a Unix socket instead of a TCP port. + + \fB + fzf --listen=/tmp/fzf.sock + + # GET + curl --unix-socket /tmp/fzf.sock http + + # POST + curl --unix-socket /tmp/fzf.sock http -d up + \fR + .SS DIRECTORY TRAVERSAL .TP .B "\-\-walker=[file][,dir][,follow][,hidden]" @@ -1373,6 +1391,8 @@ fzf exports the following environment variables to its child processes. .br .BR FZF_PORT " Port number when \-\-listen option is used" .br +.BR FZF_SOCK " Unix socket path when \-\-listen option is used" +.br .BR FZF_PREVIEW_TOP " Top position of the preview window" .br .BR FZF_PREVIEW_LEFT " Left position of the preview window" diff --git a/src/options.go b/src/options.go index a0b6f514..c1f75585 100644 --- a/src/options.go +++ b/src/options.go @@ -206,7 +206,9 @@ Usage: fzf [options] ADVANCED --with-shell=STR Shell command and flags to start child processes with - --listen[=[ADDR:]PORT] Start HTTP server to receive actions (POST /) + --listen[=SOCKET_PATH] Start HTTP server to receive actions via Unix domain socket + (Path should end with .sock) + --listen[=[ADDR:]PORT] Start HTTP server to receive actions via TCP (To allow remote process execution, use --listen-unsafe) DIRECTORY TRAVERSAL (Only used when $FZF_DEFAULT_COMMAND is not set) diff --git a/src/server.go b/src/server.go index 5757e160..9f1200da 100644 --- a/src/server.go +++ b/src/server.go @@ -46,15 +46,20 @@ type httpServer struct { type listenAddress struct { host string port int + sock string } func (addr listenAddress) IsLocal() bool { - return addr.host == "localhost" || addr.host == "127.0.0.1" + return addr.host == "localhost" || addr.host == "127.0.0.1" || len(addr.sock) > 0 } -var defaultListenAddr = listenAddress{"localhost", 0} +var defaultListenAddr = listenAddress{"localhost", 0, ""} func parseListenAddress(address string) (listenAddress, error) { + if strings.HasSuffix(address, ".sock") { + return listenAddress{"", 0, address}, nil + } + parts := strings.SplitN(address, ":", 3) if len(parts) == 1 { parts = []string{"localhost", parts[0]} @@ -70,7 +75,7 @@ func parseListenAddress(address string) (listenAddress, error) { if len(parts[0]) == 0 { parts[0] = "localhost" } - return listenAddress{parts[0], port}, nil + return listenAddress{parts[0], port, ""}, nil } func startHttpServer(address listenAddress, actionChannel chan []*action, getHandler func(getParams) string) (net.Listener, int, error) { @@ -80,21 +85,32 @@ func startHttpServer(address listenAddress, actionChannel chan []*action, getHan if !address.IsLocal() && len(apiKey) == 0 { return nil, port, errors.New("FZF_API_KEY is required to allow remote access") } - addrStr := fmt.Sprintf("%s:%d", host, port) - listener, err := net.Listen("tcp", addrStr) - if err != nil { - return nil, port, fmt.Errorf("failed to listen on %s", addrStr) - } - if port == 0 { - addr := listener.Addr().String() - parts := strings.Split(addr, ":") - if len(parts) < 2 { - return nil, port, fmt.Errorf("cannot extract port: %s", addr) - } - var err error - port, err = strconv.Atoi(parts[len(parts)-1]) + + var listener net.Listener + var err error + if len(address.sock) > 0 { + os.Remove(address.sock) + listener, err = net.Listen("unix", address.sock) if err != nil { - return nil, port, err + return nil, 0, fmt.Errorf("failed to listen on %s", address.sock) + } + } else { + addrStr := fmt.Sprintf("%s:%d", host, port) + listener, err = net.Listen("tcp", addrStr) + if err != nil { + return nil, port, fmt.Errorf("failed to listen on %s", addrStr) + } + if port == 0 { + addr := listener.Addr().String() + parts := strings.Split(addr, ":") + if len(parts) < 2 { + return nil, port, fmt.Errorf("cannot extract port: %s", addr) + } + var err error + port, err = strconv.Atoi(parts[len(parts)-1]) + if err != nil { + return nil, port, err + } } } diff --git a/src/terminal.go b/src/terminal.go index 9a014d14..cf95623c 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -1268,7 +1268,9 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor return nil, err } t.listener = listener - t.listenPort = &port + if port > 0 { + t.listenPort = &port + } } if t.hasStartActions { @@ -1292,6 +1294,9 @@ func (t *Terminal) environForPreview() []string { func (t *Terminal) environImpl(forPreview bool) []string { env := os.Environ() + if t.listenAddr != nil && len(t.listenAddr.sock) > 0 { + env = append(env, "FZF_SOCK="+t.listenAddr.sock) + } if t.listenPort != nil { env = append(env, fmt.Sprintf("FZF_PORT=%d", *t.listenPort)) }