]> Cypherpunks.ru repositories - goredo.git/blob - main.go
Do not shadow variables
[goredo.git] / main.go
1 /*
2 goredo -- djb's redo implementation on pure Go
3 Copyright (C) 2020-2023 Sergey Matveev <stargrave@stargrave.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, version 3 of the License.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 package main
19
20 import (
21         "bufio"
22         "bytes"
23         "crypto/rand"
24         "errors"
25         "flag"
26         "fmt"
27         "io"
28         "log"
29         "os"
30         "os/signal"
31         "path"
32         "runtime"
33         "sort"
34         "strconv"
35         "syscall"
36
37         "go.cypherpunks.ru/recfile"
38         "golang.org/x/sys/unix"
39 )
40
41 const (
42         CmdNameGoredo       = "goredo"
43         CmdNameRedo         = "redo"
44         CmdNameRedoAffects  = "redo-affects"
45         CmdNameRedoAlways   = "redo-always"
46         CmdNameRedoCleanup  = "redo-cleanup"
47         CmdNameRedoDepFix   = "redo-depfix"
48         CmdNameRedoDot      = "redo-dot"
49         CmdNameRedoIfchange = "redo-ifchange"
50         CmdNameRedoIfcreate = "redo-ifcreate"
51         CmdNameRedoInode    = "redo-inode"
52         CmdNameRedoLog      = "redo-log"
53         CmdNameRedoOOD      = "redo-ood"
54         CmdNameRedoSources  = "redo-sources"
55         CmdNameRedoStamp    = "redo-stamp"
56         CmdNameRedoTargets  = "redo-targets"
57         CmdNameRedoWhichdo  = "redo-whichdo"
58 )
59
60 var (
61         Cwd       string
62         BuildUUID string
63         IsTopRedo bool // is it the top redo instance
64         UmaskCur  int
65 )
66
67 func mustSetenv(key string) {
68         if err := os.Setenv(key, "1"); err != nil {
69                 panic(err)
70         }
71 }
72
73 func mustParseFd(v, name string) *os.File {
74         ptr, err := strconv.ParseUint(v, 10, 64)
75         if err != nil {
76                 panic(err)
77         }
78         fd := os.NewFile(uintptr(ptr), name)
79         if fd == nil {
80                 panic("can not parse fd: " + name)
81         }
82         return fd
83 }
84
85 func CmdName() string {
86         return path.Base(os.Args[0])
87 }
88
89 func main() {
90         version := flag.Bool("version", false, "print version")
91         warranty := flag.Bool("warranty", false, "print warranty information")
92
93         var symlinks *bool
94         cmdName := CmdName()
95         if cmdName == "goredo" {
96                 symlinks = flag.Bool("symlinks", false, "create necessary symlinks in current directory")
97         }
98
99         flag.Usage = func() { usage(os.Args[0]) }
100         flag.Parse()
101         if *warranty {
102                 fmt.Println(Warranty)
103                 return
104         }
105         if *version {
106                 fmt.Println("goredo", Version, "built with", runtime.Version())
107                 return
108         }
109         if cmdName == CmdNameGoredo && *symlinks {
110                 rc := 0
111                 for _, cmdName := range []string{
112                         CmdNameRedo,
113                         CmdNameRedoAffects,
114                         CmdNameRedoAlways,
115                         CmdNameRedoCleanup,
116                         CmdNameRedoDepFix,
117                         CmdNameRedoDot,
118                         CmdNameRedoIfchange,
119                         CmdNameRedoIfcreate,
120                         CmdNameRedoInode,
121                         CmdNameRedoLog,
122                         CmdNameRedoOOD,
123                         CmdNameRedoSources,
124                         CmdNameRedoStamp,
125                         CmdNameRedoTargets,
126                         CmdNameRedoWhichdo,
127                 } {
128                         fmt.Println(os.Args[0], "<-", cmdName)
129                         if err := os.Symlink(os.Args[0], cmdName); err != nil {
130                                 rc = 1
131                                 log.Print(err)
132                         }
133                 }
134                 os.Exit(rc)
135         }
136         log.SetFlags(log.Lshortfile)
137
138         UmaskCur = syscall.Umask(0)
139         syscall.Umask(UmaskCur)
140
141         var err error
142         Cwd, err = os.Getwd()
143         if err != nil {
144                 log.Fatal(err)
145         }
146
147         TopDir = os.Getenv(EnvTopDir)
148         if TopDir == "" {
149                 TopDir = "/"
150         } else {
151                 TopDir = mustAbs(TopDir)
152         }
153         DirPrefix = os.Getenv(EnvDirPrefix)
154         DepCwd = os.Getenv(EnvDepCwd)
155
156         if flagStderrKeep != nil && *flagStderrKeep {
157                 mustSetenv(EnvStderrKeep)
158         }
159         if flagStderrSilent != nil && *flagStderrSilent {
160                 mustSetenv(EnvStderrSilent)
161         }
162         if flagNoProgress != nil && *flagNoProgress {
163                 mustSetenv(EnvNoProgress)
164         }
165         if flagDebug != nil && *flagDebug {
166                 mustSetenv(EnvDebug)
167         }
168         if flagLogWait != nil && *flagLogWait {
169                 mustSetenv(EnvLogWait)
170         }
171         if flagLogLock != nil && *flagLogLock {
172                 mustSetenv(EnvLogLock)
173         }
174         if flagLogPid != nil && *flagLogPid {
175                 mustSetenv(EnvLogPid)
176         }
177         if flagLogJS != nil && *flagLogJS {
178                 mustSetenv(EnvLogJS)
179         }
180         StderrKeep = os.Getenv(EnvStderrKeep) == "1"
181         StderrSilent = os.Getenv(EnvStderrSilent) == "1"
182         NoProgress = os.Getenv(EnvNoProgress) == "1"
183         Debug = os.Getenv(EnvDebug) == "1"
184         LogWait = os.Getenv(EnvLogWait) == "1"
185         LogLock = os.Getenv(EnvLogLock) == "1"
186         LogJS = os.Getenv(EnvLogJS) == "1"
187         if Debug || os.Getenv(EnvLogPid) == "1" {
188                 MyPid = os.Getpid()
189         }
190         var traced bool
191         if flagTraceAll != nil && *flagTraceAll {
192                 mustSetenv(EnvTrace)
193         }
194         if os.Getenv(EnvTrace) == "1" {
195                 TracedAll = true
196                 traced = true
197         } else if flagTrace != nil {
198                 traced = *flagTrace
199         }
200         NoColor = os.Getenv(EnvNoColor) != ""
201         NoSync = os.Getenv(EnvNoSync) == "1"
202         StopIfMod = os.Getenv(EnvStopIfMod) == "1"
203         switch s := os.Getenv(EnvInodeTrust); s {
204         case "none":
205                 InodeTrust = InodeTrustNone
206         case "", "ctime":
207                 InodeTrust = InodeTrustCtime
208         case "mtime":
209                 InodeTrust = InodeTrustMtime
210         default:
211                 log.Fatalln("unknown", EnvInodeTrust, "value")
212         }
213
214         // Those are internal envs
215         FdOODTgts, err = os.CreateTemp("", "ood-tgts")
216         if err != nil {
217                 log.Fatal(err)
218         }
219         if err = os.Remove(FdOODTgts.Name()); err != nil {
220                 log.Fatal(err)
221         }
222         FdOODTgtsLock, err = os.CreateTemp("", "ood-tgts.lock")
223         if err != nil {
224                 log.Fatal(err)
225         }
226         if err = os.Remove(FdOODTgtsLock.Name()); err != nil {
227                 log.Fatal(err)
228         }
229
230         var fdLock *os.File
231         if v := os.Getenv(EnvOODTgtsFd); v != "" {
232                 fd := mustParseFd(v, EnvOODTgtsFd)
233                 fdLock = mustParseFd(v, EnvOODTgtsLockFd)
234                 defer fdLock.Close()
235                 flock := unix.Flock_t{
236                         Type:   unix.F_WRLCK,
237                         Whence: io.SeekStart,
238                 }
239                 if err = unix.FcntlFlock(fdLock.Fd(), unix.F_SETLKW, &flock); err != nil {
240                         log.Fatal(err)
241                 }
242                 if _, err = fd.Seek(0, io.SeekStart); err != nil {
243                         log.Fatal(err)
244                 }
245                 tgtsRaw, err := io.ReadAll(bufio.NewReader(fd))
246                 if err != nil {
247                         log.Fatal(err)
248                 }
249                 flock.Type = unix.F_UNLCK
250                 if err = unix.FcntlFlock(fdLock.Fd(), unix.F_SETLK, &flock); err != nil {
251                         log.Fatal(err)
252                 }
253                 OODTgts = make(map[string]struct{})
254                 for _, tgtRaw := range bytes.Split(tgtsRaw, []byte{0}) {
255                         t := string(tgtRaw)
256                         if t == "" {
257                                 continue
258                         }
259                         OODTgts[t] = struct{}{}
260                         tracef(CDebug, "ood: known to be: %s", t)
261                 }
262         }
263
264         StderrPrefix = os.Getenv(EnvStderrPrefix)
265         if v := os.Getenv(EnvLevel); v != "" {
266                 Level, err = strconv.Atoi(v)
267                 if err != nil {
268                         panic(err)
269                 }
270                 if Level < 0 {
271                         panic("negative " + EnvLevel)
272                 }
273         }
274
275         var fdDep *os.File
276         if v := os.Getenv(EnvDepFd); v != "" {
277                 fdDep = mustParseFd(v, EnvDepFd)
278         }
279
280         tgts := make([]*Tgt, 0, len(flag.Args()))
281         for _, arg := range flag.Args() {
282                 tgts = append(tgts, NewTgt(arg))
283         }
284         BuildUUID = os.Getenv(EnvBuildUUID)
285         tgtsWasEmpty := len(tgts) == 0
286         if BuildUUID == "" {
287                 IsTopRedo = true
288                 raw := new([16]byte)
289                 if _, err = io.ReadFull(rand.Reader, raw[:]); err != nil {
290                         log.Fatal(err)
291                 }
292                 raw[6] = (raw[6] & 0x0F) | uint8(4<<4) // version 4
293                 BuildUUID = fmt.Sprintf(
294                         "%x-%x-%x-%x-%x",
295                         raw[0:4], raw[4:6], raw[6:8], raw[8:10], raw[10:],
296                 )
297                 if tgtsWasEmpty {
298                         tgts = append(tgts, NewTgt("all"))
299                 }
300                 tracef(CDebug, "inode-trust: %s", InodeTrust)
301         }
302
303         if cmdName == CmdNameRedo {
304                 statusInit()
305         }
306
307         killed := make(chan os.Signal, 1)
308         signal.Notify(killed, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
309         go func() {
310                 <-killed
311                 tracef(CDebug, "[%s] killed", BuildUUID)
312                 jsReleaseAll()
313                 RunningProcsM.Lock()
314                 for pid, proc := range RunningProcs {
315                         tracef(CDebug, "[%s] killing child %d", BuildUUID, pid)
316                         _ = proc.Signal(syscall.SIGTERM)
317                 }
318                 os.Exit(1)
319         }()
320
321         ok := true
322         err = nil
323         tracef(
324                 CDebug, "[%s] run: %s %s cwd:%s dirprefix:%s",
325                 BuildUUID, cmdName, tgts, Cwd, DirPrefix,
326         )
327
328 CmdSwitch:
329         switch cmdName {
330         case CmdNameRedo:
331                 for _, tgt := range tgts {
332                         ok, err = ifchange([]*Tgt{tgt}, true, traced)
333                         if err != nil || !ok {
334                                 break
335                         }
336                 }
337         case CmdNameRedoIfchange:
338                 ok, err = ifchange(tgts, *flagForcedIfchange, traced)
339                 if err == nil {
340                         err = depsWrite(fdDep, tgts)
341                 }
342         case CmdNameRedoIfcreate:
343                 if fdDep == nil {
344                         log.Fatalln("no", EnvDepFd)
345                 }
346                 for _, tgt := range tgts {
347                         err = ifcreate(fdDep, tgt.RelTo(path.Join(Cwd, DirPrefix)))
348                         if err != nil {
349                                 break
350                         }
351                 }
352         case CmdNameRedoAlways:
353                 if fdDep == nil {
354                         log.Fatalln("no", EnvDepFd)
355                 }
356                 err = always(fdDep)
357         case CmdNameRedoCleanup:
358                 for _, what := range tgts {
359                         err = cleanupWalker(Cwd, what.t)
360                         if err != nil {
361                                 break
362                         }
363                 }
364         case CmdNameRedoDot:
365                 err = dotPrint(tgts)
366         case CmdNameRedoStamp:
367                 if fdDep == nil {
368                         log.Fatalln("no", EnvDepFd)
369                 }
370                 err = stamp(fdDep, os.Stdin)
371         case CmdNameRedoLog:
372                 if len(tgts) != 1 {
373                         log.Fatal("single target expected")
374                 }
375                 err = showBuildLog(tgts[0], nil, 0)
376         case CmdNameRedoWhichdo:
377                 if len(tgts) != 1 {
378                         log.Fatal("single target expected")
379                 }
380                 var fdTmp *os.File
381                 fdTmp, err = os.CreateTemp("", "whichdo")
382                 if err != nil {
383                         err = ErrLine(err)
384                         break
385                 }
386                 err = ErrLine(os.Remove(fdTmp.Name()))
387                 if err != nil {
388                         break
389                 }
390                 tgt := tgts[0]
391                 var doFile string
392                 var upLevels int
393                 doFile, upLevels, err = findDo(fdTmp, tgt.h, tgt.t)
394                 if err != nil {
395                         err = ErrLine(err)
396                         break
397                 }
398                 _, err = fdTmp.Seek(0, io.SeekStart)
399                 if err != nil {
400                         err = ErrLine(err)
401                         break
402                 }
403                 r := recfile.NewReader(fdTmp)
404                 for {
405                         m, err := r.NextMap()
406                         if err != nil {
407                                 if errors.Is(err, io.EOF) {
408                                         break
409                                 }
410                                 err = ErrLine(err)
411                                 break CmdSwitch
412                         }
413                         fmt.Println(cwdMustRel(tgt.h, m["Target"]))
414                 }
415                 if doFile == "" {
416                         ok = false
417                 } else {
418                         p := make([]string, 0, upLevels+2)
419                         p = append(p, tgt.h)
420                         for i := 0; i < upLevels; i++ {
421                                 p = append(p, "..")
422                         }
423                         p = append(p, doFile)
424                         rel := mustRel(Cwd, path.Join(p...))
425                         fmt.Println(rel)
426                 }
427         case CmdNameRedoTargets:
428                 raws := make([]string, 0, len(tgts))
429                 for _, tgt := range tgts {
430                         raws = append(raws, tgt.rel)
431                 }
432                 if tgtsWasEmpty {
433                         raws = []string{Cwd}
434                 }
435                 raws, err = targetsWalker(raws)
436                 if err != nil {
437                         err = ErrLine(err)
438                         break
439                 }
440                 sort.Strings(raws)
441                 for _, tgt := range raws {
442                         fmt.Println(tgt)
443                 }
444         case CmdNameRedoAffects:
445                 if tgtsWasEmpty {
446                         log.Fatal("no targets specified")
447                 }
448                 var res []string
449                 {
450                         var tgtsKnown []string
451                         tgtsKnown, err = targetsWalker([]string{Cwd})
452                         if err != nil {
453                                 err = ErrLine(err)
454                                 break
455                         }
456                         deps := make(map[string]map[string]*Tgt)
457                         for _, tgt := range tgtsKnown {
458                                 collectDeps(NewTgt(tgt), 0, deps, true, make(map[string]struct{}))
459                         }
460                         seen := make(map[string]*Tgt)
461                         for _, tgt := range tgts {
462                                 collectWholeDeps(deps[tgt.rel], deps, seen)
463                         }
464                         res = make([]string, 0, len(seen))
465                         for _, dep := range seen {
466                                 res = append(res, dep.rel)
467                         }
468                 }
469                 sort.Strings(res)
470                 for _, dep := range res {
471                         fmt.Println(dep)
472                 }
473         case CmdNameRedoOOD:
474                 raws := make([]string, 0, len(tgts))
475                 for _, tgt := range tgts {
476                         raws = append(raws, tgt.rel)
477                 }
478                 if tgtsWasEmpty {
479                         raws, err = targetsWalker([]string{Cwd})
480                         if err != nil {
481                                 break
482                         }
483                 }
484                 sort.Strings(raws)
485                 var ood bool
486                 for _, tgt := range raws {
487                         ood, err = isOOD(NewTgt(tgt), 0, nil)
488                         if err != nil {
489                                 err = ErrLine(err)
490                                 break
491                         }
492                         if ood {
493                                 fmt.Println(tgt)
494                         }
495                 }
496         case CmdNameRedoSources:
497                 srcs := make(map[string]*Tgt)
498                 {
499                         raws := make([]string, 0, len(tgts))
500                         for _, tgt := range tgts {
501                                 raws = append(raws, tgt.rel)
502                         }
503                         if tgtsWasEmpty {
504                                 raws, err = targetsWalker([]string{Cwd})
505                                 if err != nil {
506                                         err = ErrLine(err)
507                                         break
508                                 }
509                         }
510                         sort.Strings(raws)
511                         tgts = tgts[:0]
512                         for _, raw := range raws {
513                                 tgts = append(tgts, NewTgt(raw))
514                         }
515                         seen := make(map[string]struct{})
516                         seenDeps := make(map[string]struct{})
517                         err = ErrLine(sourcesWalker(tgts, seen, seenDeps, srcs))
518                 }
519                 if err != nil {
520                         break
521                 }
522                 res := make([]string, 0, len(srcs))
523                 for _, tgt := range srcs {
524                         res = append(res, tgt.rel)
525                 }
526                 srcs = nil
527                 sort.Strings(res)
528                 for _, src := range res {
529                         fmt.Println(src)
530                 }
531         case CmdNameRedoDepFix:
532                 err = depFix(Cwd)
533         case CmdNameRedoInode:
534                 var inode *Inode
535                 for _, tgt := range tgts {
536                         inode, err = inodeFromFileByPath(tgt.a)
537                         if err != nil {
538                                 err = ErrLine(err)
539                                 break
540                         }
541                         err = recfileWrite(os.Stdout, append(
542                                 []recfile.Field{{Name: "Target", Value: tgt.String()}},
543                                 inode.RecfileFields()...)...)
544                         if err != nil {
545                                 err = ErrLine(err)
546                                 break
547                         }
548                 }
549         default:
550                 log.Fatalln("unknown command", cmdName)
551         }
552         if err != nil {
553                 log.Print(err)
554         }
555         rc := 0
556         if !ok || err != nil {
557                 rc = 1
558         }
559         tracef(CDebug, "[%s] finished: %s %s", BuildUUID, cmdName, tgts)
560         os.Exit(rc)
561 }