From b8c00b6c73a23dfa8f8f8b6d7b1b3217a8e2b75f Mon Sep 17 00:00:00 2001 From: Simon Let Date: Fri, 4 Oct 2019 00:30:58 +0200 Subject: [PATCH] Split collect into collect and postcollect Realpaths for before fields are actually evaluated before. Hard prerequisite for any recall functionality --- Makefile | 2 +- VERSION | 2 +- cmd/collect/main.go | 171 +++++---------------------------------- cmd/daemon/histfile.go | 57 +++++++++++++ cmd/daemon/main.go | 27 +++---- cmd/event/main.go | 7 ++ cmd/postcollect/main.go | 147 +++++++++++++++++++++++++++++++++ cmd/sanitize/main.go | 2 +- pkg/collect/collect.go | 78 ++++++++++++++++++ pkg/histanal/histload.go | 2 +- pkg/records/records.go | 45 ++++++++++- scripts/shellrc.sh | 106 ++++++++++++++---------- 12 files changed, 429 insertions(+), 217 deletions(-) create mode 100644 cmd/daemon/histfile.go create mode 100644 cmd/event/main.go create mode 100644 cmd/postcollect/main.go create mode 100644 pkg/collect/collect.go diff --git a/Makefile b/Makefile index 30402fd..b11529a 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,7 @@ sanitize: # # -build: submodules bin/resh-collect bin/resh-daemon bin/resh-evaluate bin/resh-sanitize bin/resh-control +build: submodules bin/resh-collect bin/resh-postcollect bin/resh-daemon bin/resh-evaluate bin/resh-sanitize bin/resh-control test_go: # Running tests diff --git a/VERSION b/VERSION index 65087b4..e25d8d9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.4 +1.1.5 diff --git a/cmd/collect/main.go b/cmd/collect/main.go index c38055a..d2045c8 100644 --- a/cmd/collect/main.go +++ b/cmd/collect/main.go @@ -1,24 +1,20 @@ package main import ( - "bytes" - "encoding/json" "flag" "fmt" - "io/ioutil" "log" - "net/http" "os" "github.com/BurntSushi/toml" "github.com/curusarn/resh/pkg/cfg" + "github.com/curusarn/resh/pkg/collect" "github.com/curusarn/resh/pkg/records" // "os/exec" "os/user" "path/filepath" "strconv" - "strings" ) // Version from git set during build @@ -60,7 +56,6 @@ func main() { login := flag.String("login", "", "$LOGIN") // path := flag.String("path", "", "$PATH") pwd := flag.String("pwd", "", "$PWD - present working directory") - pwdAfter := flag.String("pwdAfter", "", "$PWD after command") shellEnv := flag.String("shellEnv", "", "$SHELL") term := flag.String("term", "", "$TERM") @@ -75,17 +70,12 @@ func main() { machtype := flag.String("machtype", "", "$MACHTYPE") gitCdup := flag.String("gitCdup", "", "git rev-parse --show-cdup") gitRemote := flag.String("gitRemote", "", "git remote get-url origin") - gitCdupAfter := flag.String("gitCdupAfter", "", "git rev-parse --show-cdup") - gitRemoteAfter := flag.String("gitRemoteAfter", "", "git remote get-url origin") gitCdupExitCode := flag.Int("gitCdupExitCode", -1, "... $?") gitRemoteExitCode := flag.Int("gitRemoteExitCode", -1, "... $?") - gitCdupExitCodeAfter := flag.Int("gitCdupExitCodeAfter", -1, "... $?") - gitRemoteExitCodeAfter := flag.Int("gitRemoteExitCodeAfter", -1, "... $?") // before after timezoneBefore := flag.String("timezoneBefore", "", "") - timezoneAfter := flag.String("timezoneAfter", "", "") osReleaseID := flag.String("osReleaseId", "", "/etc/os-release ID") osReleaseVersionID := flag.String("osReleaseVersionId", "", @@ -96,7 +86,6 @@ func main() { "/etc/os-release ID") rtb := flag.String("realtimeBefore", "-1", "before $EPOCHREALTIME") - rta := flag.String("realtimeAfter", "-1", "after $EPOCHREALTIME") rtsess := flag.String("realtimeSession", "-1", "on session start $EPOCHREALTIME") rtsessboot := flag.String("realtimeSessSinceBoot", "-1", @@ -125,10 +114,6 @@ func main() { ")") os.Exit(3) } - realtimeAfter, err := strconv.ParseFloat(*rta, 64) - if err != nil { - log.Fatal("Flag Parsing error (rta):", err) - } realtimeBefore, err := strconv.ParseFloat(*rtb, 64) if err != nil { log.Fatal("Flag Parsing error (rtb):", err) @@ -141,34 +126,22 @@ func main() { if err != nil { log.Fatal("Flag Parsing error (rt sess boot):", err) } - realtimeDuration := realtimeAfter - realtimeBefore realtimeSinceSessionStart := realtimeBefore - realtimeSessionStart realtimeSinceBoot := realtimeSessSinceBoot + realtimeSinceSessionStart - timezoneBeforeOffset := getTimezoneOffsetInSeconds(*timezoneBefore) - timezoneAfterOffset := getTimezoneOffsetInSeconds(*timezoneAfter) + timezoneBeforeOffset := collect.GetTimezoneOffsetInSeconds(*timezoneBefore) realtimeBeforeLocal := realtimeBefore + timezoneBeforeOffset - realtimeAfterLocal := realtimeAfter + timezoneAfterOffset realPwd, err := filepath.EvalSymlinks(*pwd) if err != nil { log.Println("err while handling pwd realpath:", err) realPwd = "" } - realPwdAfter, err := filepath.EvalSymlinks(*pwdAfter) - if err != nil { - log.Println("err while handling pwdAfter realpath:", err) - realPwd = "" - } - gitDir, gitRealDir := getGitDirs(*gitCdup, *gitCdupExitCode, *pwd) + gitDir, gitRealDir := collect.GetGitDirs(*gitCdup, *gitCdupExitCode, *pwd) if *gitRemoteExitCode != 0 { *gitRemote = "" } - gitDirAfter, gitRealDirAfter := getGitDirs(*gitCdupAfter, *gitCdupExitCodeAfter, *pwd) - if *gitRemoteExitCodeAfter != 0 { - *gitRemoteAfter = "" - } if *osReleaseID == "" { *osReleaseID = "linux" @@ -199,41 +172,32 @@ func main() { Login: *login, // Path: *path, Pwd: *pwd, - PwdAfter: *pwdAfter, ShellEnv: *shellEnv, Term: *term, // non-posix - RealPwd: realPwd, - RealPwdAfter: realPwdAfter, - Pid: *pid, - SessionPid: *sessionPid, - Host: *host, - Hosttype: *hosttype, - Ostype: *ostype, - Machtype: *machtype, - Shlvl: *shlvl, + RealPwd: realPwd, + Pid: *pid, + SessionPid: *sessionPid, + Host: *host, + Hosttype: *hosttype, + Ostype: *ostype, + Machtype: *machtype, + Shlvl: *shlvl, // before after TimezoneBefore: *timezoneBefore, - TimezoneAfter: *timezoneAfter, RealtimeBefore: realtimeBefore, - RealtimeAfter: realtimeAfter, RealtimeBeforeLocal: realtimeBeforeLocal, - RealtimeAfterLocal: realtimeAfterLocal, - RealtimeDuration: realtimeDuration, RealtimeSinceSessionStart: realtimeSinceSessionStart, RealtimeSinceBoot: realtimeSinceBoot, - GitDir: gitDir, - GitRealDir: gitRealDir, - GitOriginRemote: *gitRemote, - GitDirAfter: gitDirAfter, - GitRealDirAfter: gitRealDirAfter, - GitOriginRemoteAfter: *gitRemoteAfter, - MachineID: readFileContent(machineIDPath), + GitDir: gitDir, + GitRealDir: gitRealDir, + GitOriginRemote: *gitRemote, + MachineID: collect.ReadFileContent(machineIDPath), OsReleaseID: *osReleaseID, OsReleaseVersionID: *osReleaseVersionID, @@ -241,109 +205,12 @@ func main() { OsReleaseName: *osReleaseName, OsReleasePrettyName: *osReleasePrettyName, - ReshUUID: readFileContent(reshUUIDPath), + PartOne: true, + + ReshUUID: collect.ReadFileContent(reshUUIDPath), ReshVersion: Version, ReshRevision: Revision, }, } - sendRecord(rec, strconv.Itoa(config.Port)) -} - -func sendRecord(r records.Record, port string) { - recJSON, err := json.Marshal(r) - if err != nil { - log.Fatal("send err 1", err) - } - - req, err := http.NewRequest("POST", "http://localhost:"+port+"/record", - bytes.NewBuffer(recJSON)) - if err != nil { - log.Fatal("send err 2", err) - } - req.Header.Set("Content-Type", "application/json") - - client := &http.Client{} - _, err = client.Do(req) - if err != nil { - log.Fatal("resh-daemon is not running :(") - } + collect.SendRecord(rec, strconv.Itoa(config.Port)) } - -func readFileContent(path string) string { - dat, err := ioutil.ReadFile(path) - if err != nil { - return "" - //log.Fatal("failed to open " + path) - } - return strings.TrimSuffix(string(dat), "\n") -} - -func getGitDirs(cdup string, exitCode int, pwd string) (string, string) { - if exitCode != 0 { - return "", "" - } - abspath := filepath.Clean(filepath.Join(pwd, cdup)) - realpath, err := filepath.EvalSymlinks(abspath) - if err != nil { - log.Println("err while handling git dir paths:", err) - return "", "" - } - return abspath, realpath -} - -func getTimezoneOffsetInSeconds(zone string) float64 { - // date +%z -> "+0200" - hoursStr := zone[:3] - minsStr := zone[3:] - hours, err := strconv.Atoi(hoursStr) - if err != nil { - log.Println("err while parsing hours in timezone offset:", err) - return -1 - } - mins, err := strconv.Atoi(minsStr) - if err != nil { - log.Println("err while parsing mins in timezone offset:", err) - return -1 - } - secs := ((hours * 60) + mins) * 60 - return float64(secs) -} - -// func getGitRemote() string { -// out, err := exec.Command("git", "remote", "get-url", "origin").Output() -// if err != nil { -// if exitError, ok := err.(*exec.ExitError); ok { -// if exitError.ExitCode() == 128 { -// return "" -// } -// log.Fatal("git remote cmd failed") -// } else { -// log.Fatal("git remote cmd failed w/o exit code") -// } -// } -// return strings.TrimSuffix(string(out), "\n") -// } -// -// func getGitDir() string { -// // assume we are in pwd -// gitWorkTree := os.Getenv("GIT_WORK_TREE") -// -// if gitWorkTree != "" { -// return gitWorkTree -// } -// // we should look up the git directory ourselves -// // OR leave it to resh daemon to not slow down user -// out, err := exec.Command("git", "rev-parse", "--show-toplevel").Output() -// if err != nil { -// if exitError, ok := err.(*exec.ExitError); ok { -// if exitError.ExitCode() == 128 { -// return "" -// } -// log.Fatal("git rev-parse cmd failed") -// } else { -// log.Fatal("git rev-parse cmd failed w/o exit code") -// } -// } -// return strings.TrimSuffix(string(out), "\n") -// } -// } diff --git a/cmd/daemon/histfile.go b/cmd/daemon/histfile.go new file mode 100644 index 0000000..d608ca0 --- /dev/null +++ b/cmd/daemon/histfile.go @@ -0,0 +1,57 @@ +package main + +import ( + "encoding/json" + "log" + "os" + + "github.com/curusarn/resh/pkg/records" +) + +// HistfileWriter - reads records from channel, merges them and wrotes them to file +func HistfileWriter(input chan records.Record, outputPath string) { + sessions := map[string]records.Record{} + + for { + record := <-input + if record.PartOne { + if _, found := sessions[record.SessionID]; found { + log.Println("ERROR: Got another first part of the records before merging the previous one - overwriting!") + } + sessions[record.SessionID] = record + } else { + part1, found := sessions[record.SessionID] + if found == false { + log.Println("ERROR: Got second part of records and nothing to merge it with - ignoring!") + continue + } + delete(sessions, record.SessionID) + go mergeAndWriteRecord(part1, record, outputPath) + } + } +} + +func mergeAndWriteRecord(part1, part2 records.Record, outputPath string) { + err := part1.Merge(part2) + if err != nil { + log.Println("Error while merging", err) + return + } + recJSON, err := json.Marshal(part1) + if err != nil { + log.Println("Marshalling error", err) + return + } + f, err := os.OpenFile(outputPath, + os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + log.Println("Could not open file", err) + return + } + defer f.Close() + _, err = f.Write(append(recJSON, []byte("\n")...)) + if err != nil { + log.Printf("Error while writing: %v, %s\n", part1, err) + return + } +} diff --git a/cmd/daemon/main.go b/cmd/daemon/main.go index 91d1e28..69c031f 100644 --- a/cmd/daemon/main.go +++ b/cmd/daemon/main.go @@ -84,7 +84,7 @@ func statusHandler(w http.ResponseWriter, r *http.Request) { } type recordHandler struct { - OutputPath string + histfile chan records.Record } func (h *recordHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -103,19 +103,12 @@ func (h *recordHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { log.Println("Payload: ", jsn) return } - f, err := os.OpenFile(h.OutputPath, - os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - log.Println("Could not open file", err) - return + h.histfile <- record + part := "2" + if record.PartOne { + part = "1" } - defer f.Close() - _, err = f.Write(append(jsn, []byte("\n")...)) - if err != nil { - log.Printf("Error while writing: %v, %s\n", record, err) - return - } - log.Println("Received: ", record.CmdLine) + log.Println("Received:", record.CmdLine, " - part", part) // fmt.Println("cmd:", r.CmdLine) // fmt.Println("pwd:", r.Pwd) @@ -124,9 +117,13 @@ func (h *recordHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } func runServer(port int, outputPath string) { + histfile := make(chan records.Record) + go HistfileWriter(histfile, outputPath) + http.HandleFunc("/status", statusHandler) - http.Handle("/record", &recordHandler{OutputPath: outputPath}) - //http.Handle("/session_start", &recordHandler{OutputPath: outputPath}) + http.Handle("/record", &recordHandler{histfile: histfile}) + //http.Handle("/session_init", &sessionInitHandler{OutputPath: outputPath}) + //http.Handle("/recall", &recallHandler{OutputPath: outputPath}) http.ListenAndServe(":"+strconv.Itoa(port), nil) } diff --git a/cmd/event/main.go b/cmd/event/main.go new file mode 100644 index 0000000..fe3cb72 --- /dev/null +++ b/cmd/event/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("Hell world") +} diff --git a/cmd/postcollect/main.go b/cmd/postcollect/main.go new file mode 100644 index 0000000..fc02704 --- /dev/null +++ b/cmd/postcollect/main.go @@ -0,0 +1,147 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + + "github.com/BurntSushi/toml" + "github.com/curusarn/resh/pkg/cfg" + "github.com/curusarn/resh/pkg/collect" + "github.com/curusarn/resh/pkg/records" + + // "os/exec" + "os/user" + "path/filepath" + "strconv" +) + +// Version from git set during build +var Version string + +// Revision from git set during build +var Revision string + +func main() { + usr, _ := user.Current() + dir := usr.HomeDir + configPath := filepath.Join(dir, "/.config/resh.toml") + reshUUIDPath := filepath.Join(dir, "/.resh/resh-uuid") + + machineIDPath := "/etc/machine-id" + + var config cfg.Config + if _, err := toml.DecodeFile(configPath, &config); err != nil { + log.Fatal("Error reading config:", err) + } + showVersion := flag.Bool("version", false, "Show version and exit") + showRevision := flag.Bool("revision", false, "Show git revision and exit") + + requireVersion := flag.String("requireVersion", "", "abort if version doesn't match") + requireRevision := flag.String("requireRevision", "", "abort if revision doesn't match") + + cmdLine := flag.String("cmdLine", "", "command line") + exitCode := flag.Int("exitCode", -1, "exit code") + sessionID := flag.String("sessionId", "", "resh generated session id") + + // posix variables + pwdAfter := flag.String("pwdAfter", "", "$PWD after command") + + // non-posix + // sessionPid := flag.Int("sessionPid", -1, "$$ at session start") + + gitCdupAfter := flag.String("gitCdupAfter", "", "git rev-parse --show-cdup") + gitRemoteAfter := flag.String("gitRemoteAfter", "", "git remote get-url origin") + + gitCdupExitCodeAfter := flag.Int("gitCdupExitCodeAfter", -1, "... $?") + gitRemoteExitCodeAfter := flag.Int("gitRemoteExitCodeAfter", -1, "... $?") + + // before after + timezoneAfter := flag.String("timezoneAfter", "", "") + + rtb := flag.String("realtimeBefore", "-1", "before $EPOCHREALTIME") + rta := flag.String("realtimeAfter", "-1", "after $EPOCHREALTIME") + flag.Parse() + + if *showVersion == true { + fmt.Println(Version) + os.Exit(0) + } + if *showRevision == true { + fmt.Println(Revision) + os.Exit(0) + } + if *requireVersion != "" && *requireVersion != Version { + fmt.Println("Please restart/reload this terminal session " + + "(resh version: " + Version + + "; resh version of this terminal session: " + *requireVersion + + ")") + os.Exit(3) + } + if *requireRevision != "" && *requireRevision != Revision { + fmt.Println("Please restart/reload this terminal session " + + "(resh revision: " + Revision + + "; resh revision of this terminal session: " + *requireRevision + + ")") + os.Exit(3) + } + realtimeAfter, err := strconv.ParseFloat(*rta, 64) + if err != nil { + log.Fatal("Flag Parsing error (rta):", err) + } + realtimeBefore, err := strconv.ParseFloat(*rtb, 64) + if err != nil { + log.Fatal("Flag Parsing error (rtb):", err) + } + realtimeDuration := realtimeAfter - realtimeBefore + + timezoneAfterOffset := collect.GetTimezoneOffsetInSeconds(*timezoneAfter) + realtimeAfterLocal := realtimeAfter + timezoneAfterOffset + + realPwdAfter, err := filepath.EvalSymlinks(*pwdAfter) + if err != nil { + log.Println("err while handling pwdAfter realpath:", err) + realPwdAfter = "" + } + + gitDirAfter, gitRealDirAfter := collect.GetGitDirs(*gitCdupAfter, *gitCdupExitCodeAfter, *pwdAfter) + if *gitRemoteExitCodeAfter != 0 { + *gitRemoteAfter = "" + } + + rec := records.Record{ + // core + BaseRecord: records.BaseRecord{ + CmdLine: *cmdLine, + ExitCode: *exitCode, + SessionID: *sessionID, + + PwdAfter: *pwdAfter, + + // non-posix + RealPwdAfter: realPwdAfter, + + // before after + TimezoneAfter: *timezoneAfter, + + RealtimeBefore: realtimeBefore, + RealtimeAfter: realtimeAfter, + RealtimeAfterLocal: realtimeAfterLocal, + + RealtimeDuration: realtimeDuration, + + GitDirAfter: gitDirAfter, + GitRealDirAfter: gitRealDirAfter, + GitOriginRemoteAfter: *gitRemoteAfter, + MachineID: collect.ReadFileContent(machineIDPath), + + PartOne: false, + + ReshUUID: collect.ReadFileContent(reshUUIDPath), + ReshVersion: Version, + ReshRevision: Revision, + }, + } + collect.SendRecord(rec, strconv.Itoa(config.Port)) +} diff --git a/cmd/sanitize/main.go b/cmd/sanitize/main.go index 763f29a..a7415b1 100644 --- a/cmd/sanitize/main.go +++ b/cmd/sanitize/main.go @@ -89,7 +89,7 @@ func main() { log.Println("Line:", line) log.Fatal("Decoding error:", err) } - record = records.ConvertRecord(&fallbackRecord) + record = records.Convert(&fallbackRecord) } err = sanitizer.sanitizeRecord(&record) if err != nil { diff --git a/pkg/collect/collect.go b/pkg/collect/collect.go new file mode 100644 index 0000000..e53ef02 --- /dev/null +++ b/pkg/collect/collect.go @@ -0,0 +1,78 @@ +package collect + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "log" + "net/http" + "path/filepath" + "strconv" + "strings" + + "github.com/curusarn/resh/pkg/records" +) + +// SendRecord to daemon +func SendRecord(r records.Record, port string) { + recJSON, err := json.Marshal(r) + if err != nil { + log.Fatal("send err 1", err) + } + + req, err := http.NewRequest("POST", "http://localhost:"+port+"/record", + bytes.NewBuffer(recJSON)) + if err != nil { + log.Fatal("send err 2", err) + } + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + _, err = client.Do(req) + if err != nil { + log.Fatal("resh-daemon is not running :(") + } +} + +// ReadFileContent and return it as a string +func ReadFileContent(path string) string { + dat, err := ioutil.ReadFile(path) + if err != nil { + return "" + //log.Fatal("failed to open " + path) + } + return strings.TrimSuffix(string(dat), "\n") +} + +// GetGitDirs based on result of git "cdup" command +func GetGitDirs(cdup string, exitCode int, pwd string) (string, string) { + if exitCode != 0 { + return "", "" + } + abspath := filepath.Clean(filepath.Join(pwd, cdup)) + realpath, err := filepath.EvalSymlinks(abspath) + if err != nil { + log.Println("err while handling git dir paths:", err) + return "", "" + } + return abspath, realpath +} + +// GetTimezoneOffsetInSeconds based on zone returned by date command +func GetTimezoneOffsetInSeconds(zone string) float64 { + // date +%z -> "+0200" + hoursStr := zone[:3] + minsStr := zone[3:] + hours, err := strconv.Atoi(hoursStr) + if err != nil { + log.Println("err while parsing hours in timezone offset:", err) + return -1 + } + mins, err := strconv.Atoi(minsStr) + if err != nil { + log.Println("err while parsing mins in timezone offset:", err) + return -1 + } + secs := ((hours * 60) + mins) * 60 + return float64(secs) +} diff --git a/pkg/histanal/histload.go b/pkg/histanal/histload.go index 313c7ff..17497e5 100644 --- a/pkg/histanal/histload.go +++ b/pkg/histanal/histload.go @@ -162,7 +162,7 @@ func (e *HistLoad) loadHistoryRecords(fname string) []records.EnrichedRecord { log.Println("Line:", line) log.Fatal("Decoding error:", err) } - record = records.ConvertRecord(&fallbackRecord) + record = records.Convert(&fallbackRecord) } if e.sanitizedInput == false { if record.CmdLength != 0 { diff --git a/pkg/records/records.go b/pkg/records/records.go index 8a5216b..0ded51b 100644 --- a/pkg/records/records.go +++ b/pkg/records/records.go @@ -74,6 +74,18 @@ type BaseRecord struct { ReshVersion string `json:"reshVersion"` ReshRevision string `json:"reshRevision"` + // records come in two parts (collect and postcollect) + PartOne bool `json:"partOne,omitempty"` // false => part two + PartsMerged bool `json:"partsMerged"` + // special flag -> not an actual record but an session end + SessionExit bool `json:"sessionExit,omitempty"` + + // recall metadata + Recalled bool `json:"recalled"` + RecallHistno string `json:"recallHistno,omitempty"` + RecallStrategy string `json:"recallStrategy,omitempty"` + RecallActions []string `json:"recallActions,omitempty"` + // added by sanitizatizer Sanitized bool `json:"sanitized,omitempty"` CmdLength int `json:"cmdLength,omitempty"` @@ -111,8 +123,8 @@ type FallbackRecord struct { Lines int `json:"lines"` // notice the int type } -// ConvertRecord from FallbackRecord to Record -func ConvertRecord(r *FallbackRecord) Record { +// Convert from FallbackRecord to Record +func Convert(r *FallbackRecord) Record { return Record{ BaseRecord: r.BaseRecord, // these two lines are the only reason we are doing this @@ -154,6 +166,35 @@ func Enriched(r Record) EnrichedRecord { // TODO: Detect and mark simple commands r.Simple } +// Merge two records (part1 - collect + part2 - postcollect) +func (r *Record) Merge(r2 Record) error { + if r.PartOne == false || r2.PartOne { + return errors.New("Expected part1 and part2 of the same record - usage: part1.Merge(part2)") + } + if r.SessionID != r2.SessionID { + return errors.New("Records to merge are not from the same sesion - r1:" + r.SessionID + " r2:" + r2.SessionID) + } + if r.CmdLine != r2.CmdLine || r.RealtimeBefore != r2.RealtimeBefore { + return errors.New("Records to merge are not parts of the same records - r1:" + + r.CmdLine + "(" + strconv.FormatFloat(r.RealtimeBefore, 'f', -1, 64) + ") r2:" + + r2.CmdLine + "(" + strconv.FormatFloat(r2.RealtimeBefore, 'f', -1, 64) + ")") + } + r.ExitCode = r2.ExitCode + r.PwdAfter = r2.PwdAfter + r.RealPwdAfter = r2.RealPwdAfter + r.GitDirAfter = r2.GitDirAfter + r.GitRealDirAfter = r2.GitRealDirAfter + r.RealtimeAfter = r2.RealtimeAfter + r.GitOriginRemoteAfter = r2.GitOriginRemoteAfter + r.TimezoneAfter = r2.TimezoneAfter + r.RealtimeAfterLocal = r2.RealtimeAfterLocal + r.RealtimeDuration = r2.RealtimeDuration + + r.PartsMerged = true + r.PartOne = false + return nil +} + // Validate - returns error if the record is invalid func (r *Record) Validate() error { if r.CmdLine == "" { diff --git a/scripts/shellrc.sh b/scripts/shellrc.sh index c9804fe..83db854 100644 --- a/scripts/shellrc.sh +++ b/scripts/shellrc.sh @@ -168,14 +168,59 @@ __resh_preexec() { # __RESH_RT_BEFORE="$EPOCHREALTIME" __RESH_RT_BEFORE=$(__resh_get_epochrealtime) - # TODO: we should evaluate symlinks in preexec - # -> maybe create resh-precollect that could handle most of preexec - # maybe even move resh-collect here and send data to daemon and - # send rest of the data ($?, timeAfter) to daemon in precmd - # daemon will combine the data and save the record - # and save the unfinnished record even if it never finishes - # detect if the command died with the parent ps and save it then - + if [ "$__RESH_VERSION" != "$(resh-collect -version)" ]; then + # shellcheck source=shellrc.sh + source ~/.resh/shellrc + if [ "$__RESH_VERSION" != "$(resh-collect -version)" ]; then + echo "RESH WARNING: You probably just updated RESH - PLEASE RESTART OR RELOAD THIS TERMINAL SESSION (resh version: $(resh-collect -version); resh version of this terminal session: ${__RESH_VERSION})" + else + echo "RESH INFO: New RESH shellrc script was loaded - if you encounter any issues please restart this terminal session." + fi + elif [ "$__RESH_REVISION" != "$(resh-collect -revision)" ]; then + # shellcheck source=shellrc.sh + source ~/.resh/shellrc + if [ "$__RESH_REVISION" != "$(resh-collect -revision)" ]; then + echo "RESH WARNING: You probably just updated RESH - PLEASE RESTART OR RELOAD THIS TERMINAL SESSION (resh revision: $(resh-collect -revision); resh revision of this terminal session: ${__RESH_REVISION})" + fi + fi + if [ "$__RESH_VERSION" = "$(resh-collect -version)" ] && [ "$__RESH_REVISION" = "$(resh-collect -revision)" ]; then + resh-collect -requireVersion "$__RESH_VERSION" \ + -requireRevision "$__RESH_REVISION" \ + -cmdLine "$__RESH_CMDLINE" \ + -shell "$__RESH_SHELL" \ + -uname "$__RESH_UNAME" \ + -sessionId "$__RESH_SESSION_ID" \ + -cols "$__RESH_COLS" \ + -home "$__RESH_HOME" \ + -lang "$__RESH_LANG" \ + -lcAll "$__RESH_LC_ALL" \ + -lines "$__RESH_LINES" \ + -login "$__RESH_LOGIN" \ + -pwd "$__RESH_PWD" \ + -shellEnv "$__RESH_SHELL_ENV" \ + -term "$__RESH_TERM" \ + -pid "$__RESH_PID" \ + -sessionPid "$__RESH_SESSION_PID" \ + -host "$__RESH_HOST" \ + -hosttype "$__RESH_HOSTTYPE" \ + -ostype "$__RESH_OSTYPE" \ + -machtype "$__RESH_MACHTYPE" \ + -shlvl "$__RESH_SHLVL" \ + -gitCdup "$__RESH_GIT_CDUP" \ + -gitCdupExitCode "$__RESH_GIT_CDUP_EXIT_CODE" \ + -gitRemote "$__RESH_GIT_REMOTE" \ + -gitRemoteExitCode "$__RESH_GIT_REMOTE_EXIT_CODE" \ + -realtimeBefore "$__RESH_RT_BEFORE" \ + -realtimeSession "$__RESH_RT_SESSION" \ + -realtimeSessSinceBoot "$__RESH_RT_SESS_SINCE_BOOT" \ + -timezoneBefore "$__RESH_TZ_BEFORE" \ + -osReleaseId "$__RESH_OS_RELEASE_ID" \ + -osReleaseVersionId "$__RESH_OS_RELEASE_VERSION_ID" \ + -osReleaseIdLike "$__RESH_OS_RELEASE_ID_LIKE" \ + -osReleaseName "$__RESH_OS_RELEASE_NAME" \ + -osReleasePrettyName "$__RESH_OS_RELEASE_PRETTY_NAME" \ + &>~/.resh/client_last_run_out.txt || echo "resh-collect ERROR: $(head -n 1 ~/.resh/client_last_run_out.txt)" + fi } __resh_precmd() { @@ -204,55 +249,28 @@ __resh_precmd() { fi fi if [ "$__RESH_VERSION" = "$(resh-collect -version)" ] && [ "$__RESH_REVISION" = "$(resh-collect -revision)" ]; then - resh-collect -requireVersion "$__RESH_VERSION" \ + resh-postcollect -requireVersion "$__RESH_VERSION" \ -requireRevision "$__RESH_REVISION" \ -cmdLine "$__RESH_CMDLINE" \ + -realtimeBefore "$__RESH_RT_BEFORE" \ -exitCode "$__RESH_EXIT_CODE" \ - -shell "$__RESH_SHELL" \ - -uname "$__RESH_UNAME" \ -sessionId "$__RESH_SESSION_ID" \ - -cols "$__RESH_COLS" \ - -home "$__RESH_HOME" \ - -lang "$__RESH_LANG" \ - -lcAll "$__RESH_LC_ALL" \ - -lines "$__RESH_LINES" \ - -login "$__RESH_LOGIN" \ - -pwd "$__RESH_PWD" \ -pwdAfter "$__RESH_PWD_AFTER" \ - -shellEnv "$__RESH_SHELL_ENV" \ - -term "$__RESH_TERM" \ - -pid "$__RESH_PID" \ - -sessionPid "$__RESH_SESSION_PID" \ - -host "$__RESH_HOST" \ - -hosttype "$__RESH_HOSTTYPE" \ - -ostype "$__RESH_OSTYPE" \ - -machtype "$__RESH_MACHTYPE" \ - -shlvl "$__RESH_SHLVL" \ - -gitCdup "$__RESH_GIT_CDUP" \ - -gitCdupExitCode "$__RESH_GIT_CDUP_EXIT_CODE" \ - -gitRemote "$__RESH_GIT_REMOTE" \ - -gitRemoteExitCode "$__RESH_GIT_REMOTE_EXIT_CODE" \ -gitCdupAfter "$__RESH_GIT_CDUP_AFTER" \ -gitCdupExitCodeAfter "$__RESH_GIT_CDUP_EXIT_CODE_AFTER" \ -gitRemoteAfter "$__RESH_GIT_REMOTE_AFTER" \ -gitRemoteExitCodeAfter "$__RESH_GIT_REMOTE_EXIT_CODE_AFTER" \ - -realtimeBefore "$__RESH_RT_BEFORE" \ -realtimeAfter "$__RESH_RT_AFTER" \ - -realtimeSession "$__RESH_RT_SESSION" \ - -realtimeSessSinceBoot "$__RESH_RT_SESS_SINCE_BOOT" \ - -timezoneBefore "$__RESH_TZ_BEFORE" \ -timezoneAfter "$__RESH_TZ_AFTER" \ - -osReleaseId "$__RESH_OS_RELEASE_ID" \ - -osReleaseVersionId "$__RESH_OS_RELEASE_VERSION_ID" \ - -osReleaseIdLike "$__RESH_OS_RELEASE_ID_LIKE" \ - -osReleaseName "$__RESH_OS_RELEASE_NAME" \ - -osReleasePrettyName "$__RESH_OS_RELEASE_PRETTY_NAME" \ - &>~/.resh/client_last_run_out.txt || echo "resh ERROR: $(head -n 1 ~/.resh/client_last_run_out.txt)" - # -path "$__RESH_PATH" \ + &>~/.resh/client_last_run_out.txt || echo "resh-postcollect ERROR: $(head -n 1 ~/.resh/client_last_run_out.txt)" fi fi unset __RESH_COLLECT } -preexec_functions+=(__resh_preexec) -precmd_functions+=(__resh_precmd) +# do not add more hooks when shellrc is sourced again +if [ -z "${__RESH_PREEXEC_PRECMD_HOOKS_ADDED+x}" ]; then + preexec_functions+=(__resh_preexec) + precmd_functions+=(__resh_precmd) + __RESH_PREEXEC_PRECMD_HOOKS_ADDED=1 +fi