package sync import ( "os" "path/filepath" "strings" "time" "git.csclub.uwaterloo.ca/public/merlin/config" ) func cscTraceArchLinux(repo *config.Repo, newTime string) []string { args := []string{ "curl", "--interface", config.Conf.IPv4Address, "-s", repo.TraceHost, "-o", newTime, } return args } // return false iff both files exist, are non-empty, and have the same contents func cscTraceArchLinuxDiff(repo *config.Repo, newTime string) bool { readFile := func(file string) string { f, err := os.ReadFile(file) if err != nil { repo.Logger.Debug(err.Error()) return "" } return strings.TrimSpace(string(f)) } file1 := readFile(newTime) file2 := readFile(filepath.Join(buildDownloadDir(repo), "lastupdate")) if file1 == "" || file2 == "" { return true } return file1 != file2 } // update the lastsync file func cscTraceArchLinuxUpdate(repo *config.Repo) (args []string) { rsyncDir := repo.RsyncDir localDir := repo.LocalDir repo.RsyncDir = repo.RsyncDir + "/lastsync" repo.LocalDir = repo.LocalDir + "/lastsync" args = cscSyncArchLinux(repo) repo.RsyncDir = rsyncDir repo.LocalDir = localDir return args } func cscTraceDebian(repo *config.Repo, newTime string) []string { args := []string{ "nice", "rsync", "-tv", "-4", "--address=" + config.Conf.IPv4Address, "--quiet", repo.RsyncHost + "::" + filepath.Join(repo.RsyncDir, "project/trace", repo.TraceHost), newTime, } return args } // return false iff both files exist and were modified at the same time func cscTraceDebianDiff(repo *config.Repo, newTime string) bool { statFile := func(file string) int64 { f, err := os.Stat(file) if err != nil { repo.Logger.Debug("Error while trying to stat file: " + file) return 0 } return f.ModTime().Unix() } file1 := statFile(newTime) file2 := statFile(filepath.Join(buildDownloadDir(repo), "project/trace", repo.TraceHost)) if file1 == 0 || file2 == 0 { return true } return file1 != file2 } // update our trace file's modification date by writing the current time func cscTraceDebianUpdate(repo *config.Repo) { target := filepath.Join(buildDownloadDir(repo), "project/trace", config.Conf.Hostname) os.MkdirAll(filepath.Join(buildDownloadDir(repo), "project/trace"), 0755) f, err := os.OpenFile(target, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { repo.Logger.Error("Unable to open trace file: " + target) return } if _, err = f.WriteString(time.Now().UTC().Format(time.RFC1123)); err != nil { repo.Logger.Error("Unable to write to trace file: " + target) return } } // some repos want us to check a last modification time file before performing a full sync // returns a status to update the original sync's status along with if the sync should continue func checkIfSyncNeeded(repo *config.Repo) (status int, continueSync bool) { // default return values are the default status and to continue the sync status = config.FAILURE continueSync = true if repo.DryRun { return } runCommand := func(args []string) int { ch := spawnProcess(repo, args) if ch == nil { // spawnSyncProcess will have already logged error return 1 } cmd := <-ch return cmd.ProcessState.ExitCode() } // create a temp file temp, err := os.CreateTemp("/tmp", "merlin-"+repo.Name+"-trace-*") if err != nil { repo.Logger.Error(err.Error()) return } temp.Close() defer os.Remove(temp.Name()) // get the trace command to copy a last updated file var args []string switch repo.SyncType { case "csc-sync-archlinux": args = cscTraceArchLinux(repo, temp.Name()) case "csc-sync-debian": args = cscTraceDebian(repo, temp.Name()) default: repo.Logger.Error("Trace files are not implemented for sync type '" + repo.SyncType + "'") return } exit := runCommand(args) if exit != 0 { // if the process was terminated then don't continue to sync if exit == -1 { return config.TERMINATED, false } return } filesDiffer := true switch repo.SyncType { case "csc-sync-archlinux": filesDiffer = cscTraceArchLinuxDiff(repo, temp.Name()) case "csc-sync-debian": filesDiffer = cscTraceDebianDiff(repo, temp.Name()) } // debian still syncs even if the trace file is the same if !filesDiffer && repo.SyncType == "csc-sync-debian" { repo.Logger.Error("trace file for " + repo.RsyncHost + " unchanged") return } if filesDiffer { // continue sync if files differ return } repo.Logger.Debug("No changes found; full sync will not be made") // archlinux wants to sync a "lastsync" file if lastupdate prevents sync switch repo.SyncType { case "csc-sync-archlinux": args = cscTraceArchLinuxUpdate(repo) default: return config.SUCCESS, false } exit = runCommand(args) if exit != 0 { // if the process was terminated then don't continue to sync if exit == -1 { return config.TERMINATED, false } return } return config.SUCCESS, false } // csc-sync-debian wants to update a trace file after the repo is done syncing func postSyncTraceUpdate(repo *config.Repo) { if repo.SyncType == "csc-sync-debian" { cscTraceDebianUpdate(repo) } }