github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/client/snapctl.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package client
    21  
    22  import (
    23  	"bytes"
    24  	"encoding/json"
    25  	"fmt"
    26  	"io"
    27  	"io/ioutil"
    28  )
    29  
    30  // InternalSnapctlCmdNeedsStdin returns true if the given snapctl command
    31  // needs data from stdin
    32  func InternalSnapctlCmdNeedsStdin(name string) bool {
    33  	switch name {
    34  	case "fde-setup-result":
    35  		return true
    36  	default:
    37  		return false
    38  	}
    39  }
    40  
    41  // SnapCtlOptions holds the various options with which snapctl is invoked.
    42  type SnapCtlOptions struct {
    43  	// ContextID is a string used to determine the context of this call (e.g.
    44  	// which context and handler should be used, etc.)
    45  	ContextID string `json:"context-id"`
    46  
    47  	// Args contains a list of parameters to use for this invocation.
    48  	Args []string `json:"args"`
    49  }
    50  
    51  // SnapCtlPostData is the data posted to the daemon /v2/snapctl endpoint
    52  // TODO: this can be removed again once we no longer need to pass stdin data
    53  //       but instead use a real stdin stream
    54  type SnapCtlPostData struct {
    55  	SnapCtlOptions
    56  
    57  	Stdin []byte `json:"stdin,omitempty"`
    58  }
    59  
    60  type snapctlOutput struct {
    61  	Stdout string `json:"stdout"`
    62  	Stderr string `json:"stderr"`
    63  }
    64  
    65  // protect against too much data via stdin
    66  var stdinReadLimit = int64(4 * 1000 * 1000)
    67  
    68  // RunSnapctl requests a snapctl run for the given options.
    69  func (client *Client) RunSnapctl(options *SnapCtlOptions, stdin io.Reader) (stdout, stderr []byte, err error) {
    70  	// TODO: instead of reading all of stdin here we need to forward it to
    71  	//       the daemon eventually
    72  	var stdinData []byte
    73  	if stdin != nil {
    74  		limitedStdin := &io.LimitedReader{R: stdin, N: stdinReadLimit + 1}
    75  		stdinData, err = ioutil.ReadAll(limitedStdin)
    76  		if err != nil {
    77  			return nil, nil, fmt.Errorf("cannot read stdin: %v", err)
    78  		}
    79  		if limitedStdin.N <= 0 {
    80  			return nil, nil, fmt.Errorf("cannot read more than %v bytes of data from stdin", stdinReadLimit)
    81  		}
    82  	}
    83  
    84  	b, err := json.Marshal(SnapCtlPostData{
    85  		SnapCtlOptions: *options,
    86  		Stdin:          stdinData,
    87  	})
    88  	if err != nil {
    89  		return nil, nil, fmt.Errorf("cannot marshal options: %s", err)
    90  	}
    91  
    92  	var output snapctlOutput
    93  	_, err = client.doSync("POST", "/v2/snapctl", nil, nil, bytes.NewReader(b), &output)
    94  	if err != nil {
    95  		return nil, nil, err
    96  	}
    97  
    98  	return []byte(output.Stdout), []byte(output.Stderr), nil
    99  }