X-Git-Url: http://www.git.cypherpunks.ru/?a=blobdiff_plain;f=run.go;h=366240557fff22680d401ae11b3d7de2ddb3ed01;hb=3c87984511180d7bf865574123195588ea8c044c;hp=ea14641a9304bd85adbee7c988cebdcc3059242c;hpb=d8abe40c66df8d79a025524c0d230959cacf9465;p=goredo.git diff --git a/run.go b/run.go index ea14641..3662405 100644 --- a/run.go +++ b/run.go @@ -1,6 +1,6 @@ /* goredo -- djb's redo implementation on pure Go -Copyright (C) 2020-2021 Sergey Matveev +Copyright (C) 2020-2022 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 @@ -51,6 +51,7 @@ const ( EnvStderrKeep = "REDO_LOGS" EnvStderrSilent = "REDO_SILENT" EnvNoSync = "REDO_NO_SYNC" + EnvStopIfMod = "REDO_STOP_IF_MODIFIED" RedoDir = ".redo" LockSuffix = ".lock" @@ -65,6 +66,7 @@ var ( StderrKeep = false StderrSilent = false StderrPrefix string + StopIfMod = false Jobs sync.WaitGroup flagTrace *bool @@ -126,24 +128,26 @@ func mkdirs(pth string) error { return os.MkdirAll(pth, os.FileMode(0777)) } -func isModified(cwd, redoDir, tgt string) (bool, *Inode, error) { +func isModified(cwd, redoDir, tgt string) (bool, *Inode, string, error) { fdDep, err := os.Open(path.Join(redoDir, tgt+DepSuffix)) if err != nil { if os.IsNotExist(err) { - return false, nil, nil + return false, nil, "", nil } - return false, nil, err + return false, nil, "", err } defer fdDep.Close() r := recfile.NewReader(fdDep) + var modified bool var ourInode *Inode + var hshPrev string for { m, err := r.NextMap() if err != nil { if errors.Is(err, io.EOF) { break } - return false, nil, err + return false, nil, "", err } if m["Type"] != DepTypeIfchange || m["Target"] != tgt { continue @@ -151,25 +155,24 @@ func isModified(cwd, redoDir, tgt string) (bool, *Inode, error) { fd, err := os.Open(path.Join(cwd, tgt)) if err != nil { if os.IsNotExist(err) { - return false, nil, nil + return false, nil, "", nil } - return false, nil, err + return false, nil, "", err } ourInode, err = inodeFromFile(fd) fd.Close() if err != nil { - return false, nil, err + return false, nil, "", err } theirInode, err := inodeFromRec(m) if err != nil { - return false, nil, err - } - if !ourInode.Equals(theirInode) { - return true, ourInode, nil + return false, nil, "", err } + hshPrev = m["Hash"] + modified = !ourInode.Equals(theirInode) break } - return false, ourInode, nil + return modified, ourInode, hshPrev, nil } func syncDir(dir string) error { @@ -198,9 +201,14 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { if err != nil { return TgtError{tgtOrig, err} } + flock := unix.Flock_t{ + Type: unix.F_WRLCK, + Whence: io.SeekStart, + } lockRelease := func() { tracef(CLock, "LOCK_UN: %s", fdLock.Name()) - if err := unix.Flock(int(fdLock.Fd()), unix.LOCK_UN); err != nil { + flock.Type = unix.F_UNLCK + if err := unix.FcntlFlock(fdLock.Fd(), unix.F_SETLK, &flock); err != nil { log.Fatalln(err) } fdLock.Close() @@ -208,13 +216,16 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { tracef(CLock, "LOCK_NB: %s", fdLock.Name()) // Waiting for job completion, already taken by someone else - if err = unix.Flock(int(fdLock.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil { - if uintptr(err.(syscall.Errno)) != uintptr(unix.EWOULDBLOCK) { + if err = unix.FcntlFlock(fdLock.Fd(), unix.F_SETLK, &flock); err != nil { + if uintptr(err.(syscall.Errno)) != uintptr(unix.EAGAIN) { fdLock.Close() return TgtError{tgtOrig, err} } Jobs.Add(1) - tracef(CDebug, "waiting: %s", tgtOrig) + if err = unix.FcntlFlock(fdLock.Fd(), unix.F_GETLK, &flock); err != nil { + log.Fatalln(err) + } + tracef(CDebug, "waiting: %s (pid=%d)", tgtOrig, flock.Pid) if FdStatus != nil { if _, err = FdStatus.Write([]byte{StatusWait}); err != nil { log.Fatalln(err) @@ -223,7 +234,7 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { go func() { defer Jobs.Done() tracef(CLock, "LOCK_EX: %s", fdLock.Name()) - if err := unix.Flock(int(fdLock.Fd()), unix.LOCK_EX); err != nil { + if err := unix.FcntlFlock(fdLock.Fd(), unix.F_SETLKW, &flock); err != nil { log.Fatalln(err) } lockRelease() @@ -259,14 +270,17 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { } // Check if target is not modified externally - modified, inodePrev, err := isModified(cwd, redoDir, tgt) + modified, inodePrev, hshPrev, err := isModified(cwd, redoDir, tgt) if err != nil { lockRelease() return TgtError{tgtOrig, err} } if modified { - tracef(CWarn, "%s externally modified: not redoing", tgtOrig) lockRelease() + if StopIfMod { + return fmt.Errorf("%s externally modified", tgtOrig) + } + tracef(CWarn, "%s externally modified: not redoing", tgtOrig) go func() { errs <- nil }() @@ -322,7 +336,7 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { runErr.DoFile = doFileRelPath } - if err = writeDep(fdDep, cwdOrig, doFileRelPath); err != nil { + if err = depWrite(fdDep, cwdOrig, doFileRelPath, ""); err != nil { cleanup() return TgtError{tgtOrig, err} } @@ -616,10 +630,9 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { // Was $1 touched? if fd, err := os.Open(path.Join(cwdOrig, tgt)); err == nil { - errTouched := Err1WasTouched if inodePrev == nil { fd.Close() - runErr.Err = errTouched + runErr.Err = Err1WasTouched errs <- runErr return } @@ -631,7 +644,7 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { return } if !inode.Equals(inodePrev) { - runErr.Err = errTouched + runErr.Err = Err1WasTouched errs <- runErr return } @@ -683,7 +696,7 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { fd = fdStdout } - // Do we need to ifcreate it, of ifchange with renaming? + // Do we need to ifcreate it, or ifchange with renaming? if fd == nil { os.Remove(path.Join(cwdOrig, tgt)) err = ifcreate(fdDep, tgt) @@ -691,6 +704,39 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { goto Finish } } else { + var hsh string + if hshPrev != "" { + _, err = fd.Seek(0, io.SeekStart) + if err != nil { + goto Finish + } + hsh, err = fileHash(fd) + if err != nil { + goto Finish + } + if hsh == hshPrev { + tracef(CDebug, "%s has same hash, not renaming", tgtOrig) + err = os.Remove(fd.Name()) + if err != nil { + goto Finish + } + err = os.Chtimes(path.Join(cwdOrig, tgt), finished, finished) + if err != nil { + goto Finish + } + if !NoSync { + err = syncDir(cwdOrig) + if err != nil { + goto Finish + } + } + err = depWrite(fdDep, cwdOrig, tgt, hshPrev) + if err != nil { + goto Finish + } + goto RecCommit + } + } if !NoSync { err = fd.Sync() if err != nil { @@ -707,12 +753,13 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { goto Finish } } - err = writeDep(fdDep, cwdOrig, tgt) + err = depWrite(fdDep, cwdOrig, tgt, hsh) if err != nil { goto Finish } } + RecCommit: // Commit .rec if !NoSync { err = fdDep.Sync() @@ -731,6 +778,26 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { goto Finish } } + + // Post-commit .rec sanitizing + fdDep.Close() + if fdDepR, err := os.Open(fdDepPath); err == nil { + depInfo, err := depRead(fdDepR) + fdDepR.Close() + if err != nil { + goto Finish + } + ifchangeSeen := make(map[string]struct{}, len(depInfo.ifchanges)) + for _, dep := range depInfo.ifchanges { + ifchangeSeen[dep["Target"]] = struct{}{} + } + for _, dep := range depInfo.ifcreates { + if _, exists := ifchangeSeen[dep]; exists { + tracef(CWarn, "simultaneous ifcreate and ifchange records: %s", tgt) + } + } + } + Finish: runErr.Err = err errs <- runErr