github.com/google/fleetspeak@v0.1.15-0.20240426164851-4f31f62c1aea/fleetspeak/src/client/daemonservice/command/command.go (about)

     1  // Copyright 2017 Google Inc.
     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  //     https://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  // Package command provides a relatively thin wrapper around exec.Cmd, adding support
    16  // for communicating with the dependent process using a channel.Channel.
    17  package command
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"os/exec"
    23  
    24  	"github.com/google/fleetspeak/fleetspeak/src/client/channel"
    25  )
    26  
    27  // Command is a wrapper around exec.Cmd which adds needed operations which are
    28  // os specific. Notably, it knows how to attach a channel.Channel.
    29  type Command struct {
    30  	exec.Cmd
    31  
    32  	filesToClose []*os.File // Files to close after starting cmd.
    33  }
    34  
    35  // Start refines exec.Cmd.Start.
    36  func (cmd *Command) Start() error {
    37  	// Delegate to the wrapped struct.
    38  	if err := cmd.Cmd.Start(); err != nil {
    39  		return err
    40  	}
    41  
    42  	// Close our copy of the passed pipe file descrptors.
    43  	for _, f := range cmd.filesToClose {
    44  		f.Close()
    45  	}
    46  
    47  	return nil
    48  }
    49  
    50  // SoftKill sends SIGINT to cmd.
    51  func (cmd *Command) SoftKill() error {
    52  	return cmd.softKill()
    53  }
    54  
    55  // Kill sends SIGKILL to cmd.
    56  func (cmd *Command) Kill() error {
    57  	return cmd.kill()
    58  }
    59  
    60  // AddEnvVar passes the given environment variable to the process.
    61  //
    62  // The environment variables are passed when the process is spawned, so this
    63  // method should only be called before .Start is called.
    64  //
    65  // The argument kv is a key value pair in the form "key=value".
    66  func (cmd *Command) AddEnvVar(kv string) {
    67  	if cmd.Env == nil {
    68  		// Append to current environment instead of overriding it. That's also how
    69  		// os/exec handles .Env == nil - see golang.org/src/os/exec/exec.go
    70  		cmd.Env = os.Environ()
    71  	}
    72  
    73  	cmd.Env = append(cmd.Env, kv)
    74  }
    75  
    76  // addInPipeFD creates a pipe passed to the process as a file descriptor. The
    77  // process receives the read end of the pipe.
    78  //
    79  // The file descriptor is passed when the process is spawned, so this method
    80  // should only be called before .Start is called.
    81  //
    82  // Returns the write end of the pipe and the file descriptor number that the
    83  // process will receive.
    84  func (cmd *Command) addInPipeFD() (*os.File, int, error) {
    85  	return cmd.addInPipeFDImpl()
    86  }
    87  
    88  // addOutPipeFD creates a pipe passed to the process as a file descriptor. The
    89  // process receives the write end of the pipe.
    90  //
    91  // The file descriptor is passed when the process is spawned, so this method
    92  // should only be called before .Start is called.
    93  //
    94  // Returns the read end of the pipe and the file descriptor number that the
    95  // process will receive.
    96  func (cmd *Command) addOutPipeFD() (*os.File, int, error) {
    97  	return cmd.addOutPipeFDImpl()
    98  }
    99  
   100  // SetupCommsChannel prepares a daemonServiceCommand to communicate with a
   101  // DaemonService locally over an interprocess pipe.
   102  func (cmd *Command) SetupCommsChannel() (*channel.Channel, error) {
   103  	pw, inFd, err := cmd.addInPipeFD()
   104  	if err != nil {
   105  		return nil, fmt.Errorf("failed to create an input pipe: %v", err)
   106  	}
   107  
   108  	pr, outFd, err := cmd.addOutPipeFD()
   109  	if err != nil {
   110  		return nil, fmt.Errorf("failed to create an output pipe: %v", err)
   111  	}
   112  
   113  	// Note the FD numbers in cmd's environment.
   114  	cmd.AddEnvVar(fmt.Sprintf("FLEETSPEAK_COMMS_CHANNEL_INFD=%d", inFd))
   115  	cmd.AddEnvVar(fmt.Sprintf("FLEETSPEAK_COMMS_CHANNEL_OUTFD=%d", outFd))
   116  
   117  	return channel.New(pr, pw), nil
   118  }