/* goredo -- djb's redo implementation on pure Go Copyright (C) 2020-2021 Sergey Matveev This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package main import ( "crypto/rand" "flag" "fmt" "io" "io/ioutil" "log" "os" "path" "path/filepath" "strconv" "go.cypherpunks.ru/recfile" ) var ( Cwd string BuildUUID string ) func mustSetenv(key, value string) { if err := os.Setenv(key, value); err != nil { panic(err) } } func mustParseFd(v, name string) *os.File { ptr, err := strconv.ParseUint(v, 10, 64) if err != nil { panic(err) } fd := os.NewFile(uintptr(ptr), name) if fd == nil { panic("can not parse fd: " + name) } return fd } func main() { version := flag.Bool("version", false, "print version") warranty := flag.Bool("warranty", false, "print warranty information") symlinks := flag.Bool("symlinks", false, "create necessary symlinks in current directory") flag.Usage = usage flag.Parse() if *warranty { fmt.Println(Warranty) return } if *version { fmt.Println(versionGet()) return } if *symlinks { rc := 0 for _, cmdName := range []string{ "redo", "redo-always", "redo-cleanup", "redo-dot", "redo-ifchange", "redo-ifcreate", "redo-log", "redo-stamp", "redo-whichdo", } { fmt.Println(os.Args[0], "<-", cmdName) if err := os.Symlink(os.Args[0], cmdName); err != nil { rc = 1 log.Println(err) } } os.Exit(rc) } log.SetFlags(0) var err error Cwd, err = os.Getwd() if err != nil { log.Fatalln(err) } NoColor = os.Getenv(EnvNoColor) != "" NoSync = os.Getenv(EnvNoSync) == "1" InodeTrust = os.Getenv(EnvInodeNoTrust) == "" TopDir = os.Getenv(EnvTopDir) if TopDir == "" { TopDir = "/" } else { TopDir, err = filepath.Abs(TopDir) if err != nil { panic(err) } } DirPrefix = os.Getenv(EnvDirPrefix) if *flagStderrKeep { mustSetenv(EnvStderrKeep, "1") } if *flagStderrSilent { mustSetenv(EnvStderrSilent, "1") } if *flagNoProgress { mustSetenv(EnvNoProgress, "1") } if *flagDebug { mustSetenv(EnvDebug, "1") } if *flagLogWait { mustSetenv(EnvLogWait, "1") } if *flagLogLock { mustSetenv(EnvLogLock, "1") } if *flagLogPid { mustSetenv(EnvLogPid, "1") } if *flagLogJS { mustSetenv(EnvLogJS, "1") } StderrKeep = os.Getenv(EnvStderrKeep) == "1" StderrSilent = os.Getenv(EnvStderrSilent) == "1" NoProgress = os.Getenv(EnvNoProgress) == "1" Debug = os.Getenv(EnvDebug) == "1" LogWait = os.Getenv(EnvLogWait) == "1" LogLock = os.Getenv(EnvLogLock) == "1" LogJS = os.Getenv(EnvLogJS) == "1" if Debug || os.Getenv(EnvLogPid) == "1" { MyPid = os.Getpid() } var traced bool if *flagTraceAll { mustSetenv(EnvTrace, "1") } if *flagTrace { traced = true } else { traced = os.Getenv(EnvTrace) == "1" } // Those are internal envs StderrPrefix = os.Getenv(EnvStderrPrefix) if v := os.Getenv(EnvLevel); v != "" { Level, err = strconv.Atoi(v) if err != nil { panic(err) } if Level < 0 { panic("negative " + EnvLevel) } } var fdDep *os.File if v := os.Getenv(EnvDepFd); v != "" { fdDep = mustParseFd(v, EnvDepFd) } BuildUUID = os.Getenv(EnvBuildUUID) tgts := flag.Args() if BuildUUID == "" { raw := new([16]byte) if _, err = io.ReadFull(rand.Reader, raw[:]); err != nil { log.Fatalln(err) } raw[6] = (raw[6] & 0x0F) | uint8(4<<4) // version 4 BuildUUID = fmt.Sprintf( "%x-%x-%x-%x-%x", raw[0:4], raw[4:6], raw[6:8], raw[8:10], raw[10:], ) if len(tgts) == 0 { tgts = []string{"all"} } } statusInit() for i, tgt := range tgts { if path.IsAbs(tgt) { tgts[i] = cwdMustRel(tgt) } } ok := true err = nil cmdName := path.Base(os.Args[0]) trace( CDebug, "[%s] run: %s %s cwd:%s dirprefix:%s", BuildUUID, cmdName, tgts, Cwd, DirPrefix, ) CmdSwitch: switch cmdName { case "redo": for _, tgt := range tgts { ok, err = ifchange([]string{tgt}, true, traced) if err != nil || !ok { break } } case "redo-ifchange": ok, err = ifchange(tgts, false, traced) if err == nil { err = writeDeps(fdDep, tgts) } case "redo-ifcreate": if fdDep == nil { log.Fatalln("no", EnvDepFd) } for _, tgt := range tgts { err = ifcreate(fdDep, tgt) if err != nil { break } } case "redo-always": if fdDep == nil { log.Fatalln("no", EnvDepFd) } err = always(fdDep) case "redo-cleanup": for _, what := range tgts { err = cleanupWalker(Cwd, what) if err != nil { break } } case "redo-dot": err = dotPrint(tgts) case "redo-stamp": if fdDep == nil { log.Fatalln("no", EnvDepFd) } err = stamp(fdDep, os.Stdin) case "redo-log": if len(tgts) != 1 { log.Fatalln("single target expected") } d, t := cwdAndTgt(tgts[0]) var fd *os.File fd, err = os.Open(path.Join(d, RedoDir, t+LogSuffix)) if err != nil { break } _, err = io.Copy(os.Stdout, fd) case "redo-whichdo": if len(tgts) != 1 { log.Fatalln("single target expected") } var fdTmp *os.File fdTmp, err = ioutil.TempFile("", "") if err != nil { break } os.Remove(fdTmp.Name()) cwd, tgt := cwdAndTgt(tgts[0]) doFile, upLevels, err := findDo(fdTmp, cwd, tgt) if err != nil { break } _, err = fdTmp.Seek(0, 0) if err != nil { break } r := recfile.NewReader(fdTmp) for { m, err := r.NextMap() if err != nil { if err == io.EOF { break } break CmdSwitch } fmt.Println(cwdMustRel(cwd, m["Target"])) } if doFile == "" { ok = false } else { p := make([]string, 0, upLevels+2) p = append(p, cwd) for i := 0; i < upLevels; i++ { p = append(p, "..") } p = append(p, doFile) rel, err := filepath.Rel(Cwd, path.Join(p...)) if err != nil { panic(err) } fmt.Println(rel) } default: log.Fatalln("unknown command", cmdName) } if err != nil { log.Println(err) } rc := 0 if !ok || err != nil { rc = 1 } trace(CDebug, "[%s] finished: %s %s", BuildUUID, cmdName, tgts) os.Exit(rc) }