go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/logdog/client/butler/streamserver/namedPipe_posix.go (about)

     1  // Copyright 2015 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //go:build unix
    16  // +build unix
    17  
    18  package streamserver
    19  
    20  import (
    21  	"context"
    22  	"io/ioutil"
    23  	"net"
    24  	"os"
    25  	"path/filepath"
    26  
    27  	"go.chromium.org/luci/common/errors"
    28  	"go.chromium.org/luci/common/logging"
    29  	log "go.chromium.org/luci/common/logging"
    30  )
    31  
    32  // maxPOSIXNamedSocketLength is the maximum length of a UNIX domain socket.
    33  //
    34  // This is defined by the UNIX_PATH_MAX constant, and is usually this value.
    35  const maxPOSIXNamedSocketLength = 104
    36  
    37  // newStreamServer instantiates a new POSIX domain socket server
    38  // instance.
    39  //
    40  // No resources are actually created until methods are called on the returned
    41  // server.
    42  func newStreamServer(ctx context.Context, path string) (*StreamServer, error) {
    43  	if path == "" {
    44  		tFile, err := ioutil.TempFile("", "ld")
    45  		if err != nil {
    46  			return nil, err
    47  		}
    48  		path = tFile.Name()
    49  		if err := tFile.Close(); err != nil {
    50  			return nil, errors.Annotate(err, "closing tempfile %q", path).Err()
    51  		}
    52  	} else {
    53  		abs, err := filepath.Abs(path)
    54  		if err != nil {
    55  			return nil, errors.Annotate(err, "could not get absolute path of %q", path).Err()
    56  		}
    57  		path = abs
    58  	}
    59  
    60  	if len(path) > maxPOSIXNamedSocketLength {
    61  		return nil, errors.Reason("path exceeds maximum length %d", maxPOSIXNamedSocketLength).Err()
    62  	}
    63  
    64  	ctx = log.SetField(ctx, "namedPipePath", path)
    65  	return &StreamServer{
    66  		log:     logging.Get(ctx),
    67  		address: "unix:" + path,
    68  		gen: func() (listener, error) {
    69  			log.Infof(ctx, "Creating POSIX server socket Listener.")
    70  
    71  			// Cleanup any previous named pipe. We don't bother checking for the file
    72  			// first since the remove is atomic. We also ignore any error here, since
    73  			// it's probably related to the file not being found.
    74  			//
    75  			// If there was an actual error removing the file, we'll catch it shortly
    76  			// when we try to create it.
    77  			os.Remove(path)
    78  
    79  			// Create a UNIX listener
    80  			l, err := net.Listen("unix", path)
    81  			if err != nil {
    82  				return nil, err
    83  			}
    84  
    85  			ul := selfCleaningUNIXListener{
    86  				Context:  ctx,
    87  				Listener: l,
    88  				path:     path,
    89  			}
    90  			return mkListener(&ul), nil
    91  		},
    92  	}, nil
    93  }
    94  
    95  // selfCleaningUNIXListener is a wrapper around the "unix"-type Listener that
    96  // cleans up the named pipe on creation and Close().
    97  //
    98  // The standard Go Listener will unlink the file when Closed. However, it
    99  // doesn't do it in a deferred, so this will clean up if a panic is encountered
   100  // during close.
   101  type selfCleaningUNIXListener struct {
   102  	context.Context
   103  	net.Listener
   104  
   105  	path string
   106  }
   107  
   108  func (l *selfCleaningUNIXListener) Close() error {
   109  	defer func() {
   110  		if err := os.Remove(l.path); err != nil {
   111  			log.Fields{
   112  				log.ErrorKey: err,
   113  			}.Debugf(l, "Failed to remove named pipe file on Close().")
   114  		}
   115  	}()
   116  
   117  	if err := l.Listener.Close(); err != nil {
   118  		return err
   119  	}
   120  	return nil
   121  }