From 6ca95ae1956b215f73a5d214bea2b2ef7b6a94c8 Mon Sep 17 00:00:00 2001 From: ivcosla Date: Mon, 10 Jun 2019 23:07:27 +0200 Subject: [PATCH 1/6] added pid files. permission denied error --- pkg/node/node.go | 69 +++++++++++++++++++++++++++++++++++- pkg/router/app_manager.go | 3 +- pkg/util/pathutil/homedir.go | 62 ++++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 3 deletions(-) diff --git a/pkg/node/node.go b/pkg/node/node.go index 4e5c4775d5..8c64c304e6 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -2,15 +2,19 @@ package node import ( + "bufio" "context" "errors" "fmt" + "github.com/skycoin/skywire/pkg/util/pathutil" "io" "net" "net/rpc" "os" "os/exec" "path/filepath" + "strconv" + "strings" "sync" "syscall" "time" @@ -151,6 +155,7 @@ func NewNode(config *Config) (*Node, error) { RoutingTable: node.rt, RouteFinder: routeFinder.NewHTTP(config.Routing.RouteFinder, time.Duration(config.Routing.RouteFinderTimeout)), SetupNodes: config.Routing.SetupNodes, + } r := router.New(rConfig) node.router = r @@ -203,6 +208,8 @@ func (node *Node) Start() error { } node.logger.Info("Connected to messaging servers") + pathutil.EnsureDir(node.dir()) + node.closePreviousApps() for _, ac := range node.appsConf { if !ac.AutoStart { continue @@ -239,6 +246,58 @@ func (node *Node) Start() error { return nil } +func (node *Node) dir() string { + return pathutil.NodeDir(node.config.Node.StaticPubKey) +} + +func (node *Node) pidFile() *os.File { + f, err := os.OpenFile(filepath.Join(node.dir(),"apps.pid"), os.O_RDWR|os.O_CREATE, 0755) + if err != nil { + panic(err) + } + + return f +} + +func (node *Node) closePreviousApps() { + pids := node.pidFile() + defer pids.Close() // nocheck: err + + scanner := bufio.NewScanner(pids) + for scanner.Scan() { + appInfo := strings.Split(scanner.Text(), " ") + if len(appInfo) != 2 { + node.logger.Fatal("error parsing %s. Err: %s", pids.Name(), errors.New("line should be: [app name] [pid]")) + } + + pid, err := strconv.Atoi(appInfo[1]) + if err != nil { + node.logger.Fatal("error parsing %s. Err: %s", pids.Name(), err) + } + + node.stopUnhandledApp(appInfo[0], pid) + } + + // empty file + pathutil.AtomicWriteFile(pids.Name(), []byte{}) +} + +func (node *Node) stopUnhandledApp(name string, pid int) { + p, err := os.FindProcess(pid) + if err != nil { + node.logger.Infof("Previous app %s ran by this node with pid: %d not found", name, pid) + return + } + + err = p.Signal(syscall.SIGKILL) + if err != nil { + node.logger.Warnf("Found hanged app %s with pid %d previously ran by this node, but unable to kill it: %s", name, pid, err) + return + } + + node.logger.Infof("Found and killed hanged app %s with pid %d previously ran by this node", name, pid) +} + // Close safely stops spawned Apps and messaging Node. func (node *Node) Close() (err error) { if node.rpcListener != nil { @@ -357,6 +416,8 @@ func (node *Node) SpawnApp(config *AppConfig, startCh chan<- struct{}) error { bind.pid = pid node.startedMu.Unlock() appCh <- node.executer.Wait(cmd) + + node.persistPID(config.App, pid) }() srvCh := make(chan error) @@ -389,6 +450,11 @@ func (node *Node) SpawnApp(config *AppConfig, startCh chan<- struct{}) error { return appErr } +func (node *Node) persistPID(name string, pid int) { + pidF := node.pidFile() + pathutil.AtomicAppendToFile(pidF.Name(), []byte(fmt.Sprintf("%s %d\n", name, pid))) +} + // StopApp stops running App. func (node *Node) StopApp(appName string) error { node.startedMu.Lock() @@ -445,6 +511,7 @@ func (exc *osExecuter) Start(cmd *exec.Cmd) (int, error) { exc.mu.Lock() exc.processes = append(exc.processes, cmd.Process) exc.mu.Unlock() + return cmd.Process.Pid, nil } @@ -457,7 +524,7 @@ func (exc *osExecuter) Stop(pid int) (err error) { continue } - if sigErr := process.Signal(syscall.SIGTERM); sigErr != nil && err == nil { + if sigErr := process.Signal(syscall.SIGKILL); sigErr != nil && err == nil { err = sigErr } } diff --git a/pkg/router/app_manager.go b/pkg/router/app_manager.go index 49565f85c4..b7acc32ce3 100644 --- a/pkg/router/app_manager.go +++ b/pkg/router/app_manager.go @@ -3,9 +3,7 @@ package router import ( "encoding/json" "errors" - "github.com/skycoin/skycoin/src/util/logging" - "github.com/skycoin/skywire/pkg/app" ) @@ -97,3 +95,4 @@ func (am *appManager) forwardAppPacket(payload []byte) error { return am.callbacks.Forward(am.proto, packet) } + diff --git a/pkg/util/pathutil/homedir.go b/pkg/util/pathutil/homedir.go index 442a989938..6aa2fe5730 100644 --- a/pkg/util/pathutil/homedir.go +++ b/pkg/util/pathutil/homedir.go @@ -1,7 +1,12 @@ package pathutil import ( + "fmt" + "github.com/skycoin/skywire/pkg/cipher" + "io/ioutil" "os" + "path" + "path/filepath" "runtime" ) @@ -17,3 +22,60 @@ func HomeDir() string { } return os.Getenv("HOME") } + +// NodeDir returns a path to a directory used to store specific node configuration. Such dir is ~/.skywire/{PK} +func NodeDir(pk cipher.PubKey) string { + return filepath.Join(HomeDir(),".skycoin","skywire",pk.String()) +} + +// EnsureDir attempts to create given directory, panics if it fails to do so +func EnsureDir(path string) { + if _, err := os.Stat(path); os.IsNotExist(err) { + err := os.MkdirAll(path, 0644) + if err != nil { + panic(err) + } + } +} + +// AtomicWriteFile creates a temp file in which to write data, then calls syscall.Rename to swap it and write it on +// filename for an atomic write. On failure temp file is removed and panics. +func AtomicWriteFile(filename string, data []byte) { + fmt.Println("got filename: ", filename) + dir, name := path.Split(filename) + f, err := ioutil.TempFile(dir, name) + if err != nil { + panic(err) + } + + _, err = f.Write(data) + if err == nil { + err = f.Sync() + } + if closeErr := f.Close(); err == nil { + err = closeErr + } + if permErr := os.Chmod(f.Name(), 0644); err == nil { + err = permErr + } + if err == nil { + err = os.Rename(f.Name(), filename) + } + + if err != nil { + os.Remove(f.Name()) + } + panic(err) +} + +// AtomicAppendToFile calls AtomicWriteFile but appends new data to destiny file +func AtomicAppendToFile(filename string, data []byte) { + fmt.Println("got filename: ", filename) + oldFile, err := ioutil.ReadFile(filename) + if err != nil { + panic(err) + } + + AtomicWriteFile(filename, append(oldFile, data...)) +} + From 14b725c6d0f49ee97de82dd1ef00d1aeaa4df7a3 Mon Sep 17 00:00:00 2001 From: ivcosla Date: Tue, 11 Jun 2019 18:36:37 +0200 Subject: [PATCH 2/6] working, but make test not passing --- pkg/node/node.go | 26 +++++++++++++++++++------- pkg/node/node_test.go | 8 +++++++- pkg/node/rpc_test.go | 10 +++++++++- pkg/util/pathutil/homedir.go | 15 ++++++--------- 4 files changed, 41 insertions(+), 18 deletions(-) diff --git a/pkg/node/node.go b/pkg/node/node.go index 8c64c304e6..a3c4f9b776 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -6,19 +6,21 @@ import ( "context" "errors" "fmt" - "github.com/skycoin/skywire/pkg/util/pathutil" "io" "net" "net/rpc" "os" "os/exec" "path/filepath" + "runtime" "strconv" "strings" "sync" "syscall" "time" + "github.com/skycoin/skywire/pkg/util/pathutil" + "github.com/skycoin/skycoin/src/util/logging" "github.com/skycoin/skywire/internal/noise" @@ -98,6 +100,8 @@ type Node struct { startedMu sync.RWMutex startedApps map[string]*appBind + pidMu sync.Mutex + rpcListener net.Listener rpcDialers []*noise.RPCClientDialer } @@ -155,7 +159,6 @@ func NewNode(config *Config) (*Node, error) { RoutingTable: node.rt, RouteFinder: routeFinder.NewHTTP(config.Routing.RouteFinder, time.Duration(config.Routing.RouteFinderTimeout)), SetupNodes: config.Routing.SetupNodes, - } r := router.New(rConfig) node.router = r @@ -251,7 +254,7 @@ func (node *Node) dir() string { } func (node *Node) pidFile() *os.File { - f, err := os.OpenFile(filepath.Join(node.dir(),"apps.pid"), os.O_RDWR|os.O_CREATE, 0755) + f, err := os.OpenFile(filepath.Join(node.dir(), "apps.pid"), os.O_RDWR|os.O_CREATE, 0755) if err != nil { panic(err) } @@ -260,6 +263,8 @@ func (node *Node) pidFile() *os.File { } func (node *Node) closePreviousApps() { + node.logger.Info("killing previously ran apps if any...") + pids := node.pidFile() defer pids.Close() // nocheck: err @@ -285,13 +290,14 @@ func (node *Node) closePreviousApps() { func (node *Node) stopUnhandledApp(name string, pid int) { p, err := os.FindProcess(pid) if err != nil { - node.logger.Infof("Previous app %s ran by this node with pid: %d not found", name, pid) + if runtime.GOOS != "windows" { + node.logger.Infof("Previous app %s ran by this node with pid: %d not found", name, pid) + } return } err = p.Signal(syscall.SIGKILL) if err != nil { - node.logger.Warnf("Found hanged app %s with pid %d previously ran by this node, but unable to kill it: %s", name, pid, err) return } @@ -415,9 +421,12 @@ func (node *Node) SpawnApp(config *AppConfig, startCh chan<- struct{}) error { node.startedMu.Lock() bind.pid = pid node.startedMu.Unlock() - appCh <- node.executer.Wait(cmd) + node.pidMu.Lock() + node.logger.Infof("storing app %s pid %d", config.App, pid) node.persistPID(config.App, pid) + node.pidMu.Unlock() + appCh <- node.executer.Wait(cmd) }() srvCh := make(chan error) @@ -452,7 +461,10 @@ func (node *Node) SpawnApp(config *AppConfig, startCh chan<- struct{}) error { func (node *Node) persistPID(name string, pid int) { pidF := node.pidFile() - pathutil.AtomicAppendToFile(pidF.Name(), []byte(fmt.Sprintf("%s %d\n", name, pid))) + pidFName := pidF.Name() + pidF.Close() + + pathutil.AtomicAppendToFile(pidFName, []byte(fmt.Sprintf("%s %d\n", name, pid))) } // StopApp stops running App. diff --git a/pkg/node/node_test.go b/pkg/node/node_test.go index 99903b5be7..6db88af95c 100644 --- a/pkg/node/node_test.go +++ b/pkg/node/node_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "github.com/skycoin/skywire/pkg/util/pathutil" "net" "os" "os/exec" @@ -97,11 +98,16 @@ func TestNodeStartClose(t *testing.T) { } func TestNodeSpawnApp(t *testing.T) { + pk, _ := cipher.GenerateKeyPair() r := new(mockRouter) executer := &MockExecuter{} defer os.RemoveAll("skychat") apps := []AppConfig{{App: "skychat", Version: "1.0", AutoStart: false, Port: 10, Args: []string{"foo"}}} - node := &Node{router: r, executer: executer, appsConf: apps, startedApps: map[string]*appBind{}, logger: logging.MustGetLogger("test")} + node := &Node{router: r, executer: executer, appsConf: apps, startedApps: map[string]*appBind{}, logger: logging.MustGetLogger("test"), + config: &Config{}} + node.config.Node.StaticPubKey = pk + pathutil.EnsureDir(node.dir()) + defer os.RemoveAll(node.dir()) require.NoError(t, node.StartApp("skychat")) time.Sleep(100 * time.Millisecond) diff --git a/pkg/node/rpc_test.go b/pkg/node/rpc_test.go index bd03e1259e..ac0f0bd322 100644 --- a/pkg/node/rpc_test.go +++ b/pkg/node/rpc_test.go @@ -3,6 +3,8 @@ package node import ( "context" "encoding/json" + "github.com/skycoin/skywire/pkg/cipher" + "github.com/skycoin/skywire/pkg/util/pathutil" "net" "net/rpc" "os" @@ -47,12 +49,16 @@ func TestListApps(t *testing.T) { } func TestStartStopApp(t *testing.T) { + pk, _ := cipher.GenerateKeyPair() router := new(mockRouter) executer := new(MockExecuter) defer os.RemoveAll("skychat") apps := []AppConfig{{App: "foo", Version: "1.0", AutoStart: false, Port: 10}} - node := &Node{router: router, executer: executer, appsConf: apps, startedApps: map[string]*appBind{}, logger: logging.MustGetLogger("test")} + node := &Node{router: router, executer: executer, appsConf: apps, startedApps: map[string]*appBind{}, logger: logging.MustGetLogger("test"), config: &Config{}} + node.config.Node.StaticPubKey = pk + pathutil.EnsureDir(node.dir()) + defer os.RemoveAll(node.dir()) rpc := &RPC{node: node} unknownApp := "bar" @@ -119,6 +125,8 @@ func TestRPC(t *testing.T) { startedApps: map[string]*appBind{}, logger: logging.MustGetLogger("test"), } + pathutil.EnsureDir(node.dir()) + defer os.RemoveAll(node.dir()) require.NoError(t, node.StartApp("foo")) require.NoError(t, node.StartApp("bar")) diff --git a/pkg/util/pathutil/homedir.go b/pkg/util/pathutil/homedir.go index 6aa2fe5730..e9cae64523 100644 --- a/pkg/util/pathutil/homedir.go +++ b/pkg/util/pathutil/homedir.go @@ -1,13 +1,13 @@ package pathutil import ( - "fmt" - "github.com/skycoin/skywire/pkg/cipher" "io/ioutil" "os" "path" "path/filepath" "runtime" + + "github.com/skycoin/skywire/pkg/cipher" ) // HomeDir obtains the path to the user's home directory via ENVs. @@ -25,13 +25,13 @@ func HomeDir() string { // NodeDir returns a path to a directory used to store specific node configuration. Such dir is ~/.skywire/{PK} func NodeDir(pk cipher.PubKey) string { - return filepath.Join(HomeDir(),".skycoin","skywire",pk.String()) + return filepath.Join(HomeDir(), ".skycoin", "skywire", pk.String()) } // EnsureDir attempts to create given directory, panics if it fails to do so func EnsureDir(path string) { if _, err := os.Stat(path); os.IsNotExist(err) { - err := os.MkdirAll(path, 0644) + err := os.MkdirAll(path, 0755) if err != nil { panic(err) } @@ -41,7 +41,6 @@ func EnsureDir(path string) { // AtomicWriteFile creates a temp file in which to write data, then calls syscall.Rename to swap it and write it on // filename for an atomic write. On failure temp file is removed and panics. func AtomicWriteFile(filename string, data []byte) { - fmt.Println("got filename: ", filename) dir, name := path.Split(filename) f, err := ioutil.TempFile(dir, name) if err != nil { @@ -63,14 +62,13 @@ func AtomicWriteFile(filename string, data []byte) { } if err != nil { - os.Remove(f.Name()) + os.Remove(f.Name()) // nolint: errcheck + panic(err) } - panic(err) } // AtomicAppendToFile calls AtomicWriteFile but appends new data to destiny file func AtomicAppendToFile(filename string, data []byte) { - fmt.Println("got filename: ", filename) oldFile, err := ioutil.ReadFile(filename) if err != nil { panic(err) @@ -78,4 +76,3 @@ func AtomicAppendToFile(filename string, data []byte) { AtomicWriteFile(filename, append(oldFile, data...)) } - From d5b029aec2f309b79c51b2a50423e93d1e4fc4f5 Mon Sep 17 00:00:00 2001 From: ivcosla Date: Wed, 12 Jun 2019 11:57:06 +0200 Subject: [PATCH 3/6] added time sleep to test --- pkg/node/rpc_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/node/rpc_test.go b/pkg/node/rpc_test.go index ac0f0bd322..42340338df 100644 --- a/pkg/node/rpc_test.go +++ b/pkg/node/rpc_test.go @@ -131,6 +131,7 @@ func TestRPC(t *testing.T) { require.NoError(t, node.StartApp("foo")) require.NoError(t, node.StartApp("bar")) + time.Sleep(time.Second) gateway := &RPC{node: node} sConn, cConn := net.Pipe() From 230c1972152b855b2b6da872172689cc198dbdd1 Mon Sep 17 00:00:00 2001 From: ivcosla Date: Wed, 12 Jun 2019 12:08:37 +0200 Subject: [PATCH 4/6] changed permissions --- pkg/node/node.go | 2 +- pkg/node/node_test.go | 3 ++- pkg/node/rpc_test.go | 5 +++-- pkg/util/pathutil/homedir.go | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg/node/node.go b/pkg/node/node.go index a3c4f9b776..6eb16bc2e8 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -254,7 +254,7 @@ func (node *Node) dir() string { } func (node *Node) pidFile() *os.File { - f, err := os.OpenFile(filepath.Join(node.dir(), "apps.pid"), os.O_RDWR|os.O_CREATE, 0755) + f, err := os.OpenFile(filepath.Join(node.dir(), "apps.pid"), os.O_RDWR|os.O_CREATE, 0600) if err != nil { panic(err) } diff --git a/pkg/node/node_test.go b/pkg/node/node_test.go index 6db88af95c..f6022b0d8d 100644 --- a/pkg/node/node_test.go +++ b/pkg/node/node_test.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "errors" - "github.com/skycoin/skywire/pkg/util/pathutil" "net" "os" "os/exec" @@ -12,6 +11,8 @@ import ( "testing" "time" + "github.com/skycoin/skywire/pkg/util/pathutil" + "net/http" "net/http/httptest" diff --git a/pkg/node/rpc_test.go b/pkg/node/rpc_test.go index 42340338df..85534b7ea6 100644 --- a/pkg/node/rpc_test.go +++ b/pkg/node/rpc_test.go @@ -3,14 +3,15 @@ package node import ( "context" "encoding/json" - "github.com/skycoin/skywire/pkg/cipher" - "github.com/skycoin/skywire/pkg/util/pathutil" "net" "net/rpc" "os" "testing" "time" + "github.com/skycoin/skywire/pkg/cipher" + "github.com/skycoin/skywire/pkg/util/pathutil" + "github.com/google/uuid" "github.com/skycoin/skycoin/src/util/logging" "github.com/stretchr/testify/assert" diff --git a/pkg/util/pathutil/homedir.go b/pkg/util/pathutil/homedir.go index e9cae64523..12f205d3b8 100644 --- a/pkg/util/pathutil/homedir.go +++ b/pkg/util/pathutil/homedir.go @@ -31,7 +31,7 @@ func NodeDir(pk cipher.PubKey) string { // EnsureDir attempts to create given directory, panics if it fails to do so func EnsureDir(path string) { if _, err := os.Stat(path); os.IsNotExist(err) { - err := os.MkdirAll(path, 0755) + err := os.MkdirAll(path, 0750) if err != nil { panic(err) } @@ -54,7 +54,7 @@ func AtomicWriteFile(filename string, data []byte) { if closeErr := f.Close(); err == nil { err = closeErr } - if permErr := os.Chmod(f.Name(), 0644); err == nil { + if permErr := os.Chmod(f.Name(), 0600); err == nil { err = permErr } if err == nil { From bef39a88237058ccdd56b34690b16164b542df65 Mon Sep 17 00:00:00 2001 From: ivcosla Date: Wed, 12 Jun 2019 12:54:48 +0200 Subject: [PATCH 5/6] format --- pkg/router/app_manager.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/router/app_manager.go b/pkg/router/app_manager.go index b7acc32ce3..49565f85c4 100644 --- a/pkg/router/app_manager.go +++ b/pkg/router/app_manager.go @@ -3,7 +3,9 @@ package router import ( "encoding/json" "errors" + "github.com/skycoin/skycoin/src/util/logging" + "github.com/skycoin/skywire/pkg/app" ) @@ -95,4 +97,3 @@ func (am *appManager) forwardAppPacket(payload []byte) error { return am.callbacks.Forward(am.proto, packet) } - From fffb0b335293eae5afdb955fcf0e02049873d6cc Mon Sep 17 00:00:00 2001 From: ivcosla Date: Thu, 13 Jun 2019 11:23:18 +0200 Subject: [PATCH 6/6] renamed pid file --- pkg/node/node.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/node/node.go b/pkg/node/node.go index 6eb16bc2e8..562b23a042 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -254,7 +254,7 @@ func (node *Node) dir() string { } func (node *Node) pidFile() *os.File { - f, err := os.OpenFile(filepath.Join(node.dir(), "apps.pid"), os.O_RDWR|os.O_CREATE, 0600) + f, err := os.OpenFile(filepath.Join(node.dir(), "apps-pid.txt"), os.O_RDWR|os.O_CREATE, 0600) if err != nil { panic(err) }