package main // A note on the origin of the name. // http://en.wikipedia.org/wiki/Barak // TODO: Nonrepudiable command log import ( "errors" "flag" "fmt" "io" "net/http" "os" "reflect" "time" "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/cmd/barak/types" . "github.com/tendermint/tendermint/common" cfg "github.com/tendermint/tendermint/config" pcm "github.com/tendermint/tendermint/process" "github.com/tendermint/tendermint/rpc/server" ) var Routes map[string]*rpcserver.RPCFunc func init() { Routes = map[string]*rpcserver.RPCFunc{ "status": rpcserver.NewRPCFunc(Status, []string{}), "run": rpcserver.NewRPCFunc(Run, []string{"auth_command"}), // NOTE: also, two special non-JSONRPC routes called "download" and "upload" } } // Global instance var barak_ *Barak // Parse command-line options func parseFlags() (optionsFile string) { flag.StringVar(&optionsFile, "options-file", "", "Read options from file instead of stdin") flag.Parse() return } func main() { fmt.Printf("New Barak Process (PID: %d)\n", os.Getpid()) // Apply bare tendermint/* configuration. cfg.ApplyConfig(cfg.MapConfig(map[string]interface{}{"log_level": "info"})) // Read options optionsFile := parseFlags() options := ReadBarakOptions(optionsFile) // Init barak barak_ = NewBarakFromOptions(options) barak_.StartRegisterRoutine() barak_.WritePidFile() // This should be last, before TrapSignal(). TrapSignal(func() { fmt.Println("Barak shutting down") }) } //------------------------------------------------------------------------------ // RPC functions func Status() (*ResponseStatus, error) { barak_.mtx.Lock() pid := barak_.pid nonce := barak_.nonce validators := barak_.validators barak_.mtx.Unlock() return &ResponseStatus{ Pid: pid, Nonce: nonce, Validators: validators, }, nil } func Run(authCommand AuthCommand) (interface{}, error) { command, err := parseValidateCommand(authCommand) if err != nil { return nil, err } log.Info(Fmt("Run() received command %v:\n%v", reflect.TypeOf(command), command)) // Issue command switch c := command.(type) { case CommandStartProcess: return StartProcess(c.Wait, c.Label, c.ExecPath, c.Args, c.Input) case CommandStopProcess: return StopProcess(c.Label, c.Kill) case CommandListProcesses: return ListProcesses() case CommandOpenListener: return OpenListener(c.Addr) case CommandCloseListener: return CloseListener(c.Addr) default: return nil, errors.New("Invalid endpoint for command") } } func parseValidateCommandStr(authCommandStr string) (Command, error) { var err error authCommand := binary.ReadJSON(AuthCommand{}, []byte(authCommandStr), &err).(AuthCommand) if err != nil { fmt.Printf("Failed to parse auth_command") return nil, errors.New("AuthCommand parse error") } return parseValidateCommand(authCommand) } func parseValidateCommand(authCommand AuthCommand) (Command, error) { commandJSONStr := authCommand.CommandJSONStr signatures := authCommand.Signatures // Validate commandJSONStr if !validate([]byte(commandJSONStr), barak_.ListValidators(), signatures) { fmt.Printf("Failed validation attempt") return nil, errors.New("Validation error") } // Parse command var err error command := binary.ReadJSON(NoncedCommand{}, []byte(commandJSONStr), &err).(NoncedCommand) if err != nil { fmt.Printf("Failed to parse command") return nil, errors.New("Command parse error") } // Prevent replays barak_.CheckIncrNonce(command.Nonce) return command.Command, nil } //------------------------------------------------------------------------------ // RPC base commands // WARNING Not validated, do not export to routes. func StartProcess(wait bool, label string, execPath string, args []string, input string) (*ResponseStartProcess, error) { // First, see if there already is a process labeled 'label' existing := barak_.GetProcess(label) if existing != nil && existing.EndTime.IsZero() { return nil, fmt.Errorf("Process already exists: %v", label) } // Otherwise, create one. err := EnsureDir(barak_.RootDir() + "/outputs") if err != nil { return nil, fmt.Errorf("Failed to create outputs dir: %v", err) } outPath := Fmt("%v/outputs/%v_%v.out", barak_.RootDir(), label, time.Now().Format("2006_01_02_15_04_05_MST")) proc, err := pcm.Create(pcm.ProcessModeDaemon, label, execPath, args, input, outPath) if err != nil { return nil, err } barak_.AddProcess(label, proc) if wait { <-proc.WaitCh output := pcm.ReadOutput(proc) fmt.Println("Read output", output) if proc.ExitState == nil { return &ResponseStartProcess{ Success: true, Output: output, }, nil } else { return &ResponseStartProcess{ Success: proc.ExitState.Success(), // Would be always false? Output: output, }, nil } } else { return &ResponseStartProcess{ Success: true, Output: "", }, nil } } func StopProcess(label string, kill bool) (*ResponseStopProcess, error) { err := barak_.StopProcess(label, kill) return &ResponseStopProcess{}, err } func ListProcesses() (*ResponseListProcesses, error) { procs := barak_.ListProcesses() return &ResponseListProcesses{ Processes: procs, }, nil } func OpenListener(addr string) (*ResponseOpenListener, error) { listener, err := barak_.OpenListener(addr) if err != nil { return nil, err } return &ResponseOpenListener{ Addr: listener.Addr().String(), }, nil } func CloseListener(addr string) (*ResponseCloseListener, error) { barak_.CloseListener(addr) return &ResponseCloseListener{}, nil } //-------------------------------------------------------------------------------- // Another barak instance registering its external // address to a remote barak. func RegisterHandler(w http.ResponseWriter, req *http.Request) { registry, err := os.OpenFile(barak_.RootDir()+"/registry.log", os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600) if err != nil { http.Error(w, "Could not open registry file. Please contact the administrator", 500) return } // TODO: Also check the X-FORWARDED-FOR or whatever it's called. registry.Write([]byte(Fmt("++ %v\n", req.RemoteAddr))) registry.Close() w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) w.Write([]byte("Noted!")) } func ServeFileHandler(w http.ResponseWriter, req *http.Request) { authCommandStr := req.FormValue("auth_command") command, err := parseValidateCommandStr(authCommandStr) if err != nil { http.Error(w, Fmt("Invalid command: %v", err), 400) } serveCommand, ok := command.(CommandServeFile) if !ok { http.Error(w, "Invalid command", 400) } path := serveCommand.Path if path == "" { http.Error(w, "Must specify path", 400) return } if path[0] == '.' { // local paths must be explicitly local, e.g. "./xyz" } else if path[0] != '/' { // If not an absolute path, then is label proc := barak_.GetProcess(path) if proc == nil { http.Error(w, Fmt("Unknown process label: %v", path), 400) return } path = proc.OutputPath } file, err := os.Open(path) if err != nil { http.Error(w, Fmt("Error opening file: %v. %v", path, err), 400) return } _, err = io.Copy(w, file) if err != nil { fmt.Fprintf(os.Stderr, Fmt("Error serving file: %v. %v", path, err)) return } }