wg-quicker/vendor/github.com/getlantern/byteexec/byteexec.go

119 lines
3.2 KiB
Go

// Package byteexec provides a very basic facility for running executables
// supplied as byte arrays, which is handy when used with
// github.com/jteeuwen/go-bindata.
//
// byteexec works by storing the provided command in a file.
//
// Example Usage:
//
// programBytes := // read bytes from somewhere
// be, err := byteexec.New(programBytes, "new/path/to/executable")
// if err != nil {
// log.Fatalf("Uh oh: %s", err)
// }
// cmd := be.Command("arg1", "arg2")
// // cmd is an os/exec.Cmd
// err = cmd.Run()
//
// Note - byteexec.New is somewhat expensive, and Exec is safe for concurrent
// use, so it's advisable to create only one Exec for each executable.
package byteexec
import (
"fmt"
"os"
"os/exec"
"os/user"
"path/filepath"
"sync"
"github.com/getlantern/filepersist"
"github.com/getlantern/golog"
)
var (
log = golog.LoggerFor("Exec")
fileMode = os.FileMode(0744)
initMutex sync.Mutex
)
// Exec is a handle to an executable that can be used to create an exec.Cmd
// using the Command method. Exec is safe for concurrent use.
type Exec struct {
Filename string
}
// New creates a new Exec using the program stored in the provided data, at the
// provided filename (relative or absolute path allowed). If the path given is
// a relative path, the executable will be placed in one of the following
// locations:
//
// On Windows - %APPDATA%/byteexec
// On OSX - ~/Library/Application Support/byteexec
// All Others - ~/.byteexec
//
// Creating a new Exec can be somewhat expensive, so it's best to create only
// one Exec per executable and reuse that.
//
// WARNING - if a file already exists at this location and its contents differ
// from data, Exec will attempt to overwrite it.
func New(data []byte, filename string) (*Exec, error) {
log.Tracef("Creating new at %v", filename)
// Use initMutex to synchronize file operations by this process
initMutex.Lock()
defer initMutex.Unlock()
var err error
if !filepath.IsAbs(filename) {
filename, err = inStandardDir(filename)
if err != nil {
return nil, err
}
}
filename = renameExecutable(filename)
log.Tracef("Placing executable in %s", filename)
err = filepersist.Save(filename, data, fileMode)
if err != nil {
return nil, err
}
log.Trace("File saved, returning new Exec")
return newExec(filename)
}
// Command creates an exec.Cmd using the supplied args.
func (be *Exec) Command(args ...string) *exec.Cmd {
return exec.Command(be.Filename, args...)
}
func newExec(filename string) (*Exec, error) {
absolutePath, err := filepath.Abs(filename)
if err != nil {
return nil, err
}
return &Exec{Filename: absolutePath}, nil
}
func inStandardDir(filename string) (string, error) {
folder, err := pathForRelativeFiles()
if err != nil {
return "", err
}
err = os.MkdirAll(folder, fileMode)
if err != nil {
return "", fmt.Errorf("Unable to make folder %s: %s", folder, err)
}
return filepath.Join(folder, filename), nil
}
func inHomeDir(filename string) (string, error) {
log.Tracef("Determining user's home directory")
usr, err := user.Current()
if err != nil {
return "", fmt.Errorf("Unable to determine user's home directory: %s", err)
}
return filepath.Join(usr.HomeDir, filename), nil
}