// Save persists a Siegfried struct to disk (path) func (s *Siegfried) Save(path string) error { ls := persist.NewLoadSaver(nil) ls.SaveTime(s.C) s.em.Save(ls) s.cm.Save(ls) s.bm.Save(ls) s.tm.Save(ls) ls.SaveTinyUInt(len(s.ids)) for _, i := range s.ids { i.Save(ls) } if ls.Err != nil { return ls.Err } f, err := os.Create(path) if err != nil { return err } defer f.Close() _, err = f.Write(append(config.Magic(), byte(config.Version()[0]), byte(config.Version()[1]))) if err != nil { return err } z, err := flate.NewWriter(f, 1) if err != nil { return err } _, err = z.Write(ls.Bytes()) z.Close() return err }
// YAML representation of a Siegfried struct. // This is the provenace block at the beginning of sf results and includes descriptions for each identifier. func (s *Siegfried) YAML() string { version := config.Version() str := fmt.Sprintf( "---\nsiegfried : %d.%d.%d\nscandate : %v\nsignature : %s\ncreated : %v\nidentifiers : \n", version[0], version[1], version[2], time.Now().Format(time.RFC3339), config.SignatureBase(), s.C.Format(time.RFC3339)) for _, id := range s.ids { d := id.Describe() str += fmt.Sprintf(" - name : '%v'\n details : '%v'\n", d[0], d[1]) } return str }
func updateSigs() (string, error) { url, _, _ := config.UpdateOptions() if url == "" { return "Update is not available for this distribution of Siegfried", nil } response, err := getHttp(url) if err != nil { return "", err } var u Update if err := json.Unmarshal(response, &u); err != nil { return "", err } version := config.Version() if version[0] < u.SfVersion[0] || (u.SfVersion[0] == version[0] && version[1] < u.SfVersion[1]) { return "Your version of Siegfried is out of date; please install latest from http://www.itforarchivists.com/siegfried before continuing.", nil } s, err := siegfried.Load(config.Signature()) if err == nil { if !s.Update(u.SigCreated) { return "You are already up to date!", nil } } else { // this hairy bit of golang exception handling is thanks to Ross! :) if _, err = os.Stat(config.Home()); err != nil { if os.IsNotExist(err) { err = os.MkdirAll(config.Home(), os.ModePerm) if err != nil { return "", fmt.Errorf("Siegfried: cannot create home directory %s, %v", config.Home(), err) } } else { return "", fmt.Errorf("Siegfried: error opening directory %s, %v", config.Home(), err) } } } fmt.Println("... downloading latest signature file ...") response, err = getHttp(u.LatestURL) if err != nil { return "", fmt.Errorf("Siegfried: error retrieving pronom.gob.\nThis may be a network or firewall issue. See https://github.com/richardlehane/siegfried/wiki/Getting-started for manual instructions.\nSystem error: %v", err) } if len(response) != u.GobSize { return "", fmt.Errorf("Siegfried: error retrieving pronom.gob; expecting %d bytes, got %d bytes", u.GobSize, len(response)) } err = ioutil.WriteFile(config.Signature(), response, os.ModePerm) if err != nil { return "", fmt.Errorf("Siegfried: error writing to directory, %v", err) } fmt.Printf("... writing %s ...\n", config.Signature()) return "Your signature file has been updated", nil }
// JSON representation of a Siegfried struct. // This is the provenace block at the beginning of sf results and includes descriptions for each identifier. func (s *Siegfried) JSON() string { version := config.Version() str := fmt.Sprintf( "{\"siegfried\":\"%d.%d.%d\",\"scandate\":\"%v\",\"signature\":\"%s\",\"created\":\"%v\",\"identifiers\":[", version[0], version[1], version[2], time.Now().Format(time.RFC3339), config.SignatureBase(), s.C.Format(time.RFC3339)) for i, id := range s.ids { if i > 0 { str += "," } d := id.Describe() str += fmt.Sprintf("{\"name\":\"%s\",\"details\":\"%s\"}", d[0], d[1]) } str += "]," return str }
// Load creates a Siegfried struct and loads content from path func Load(path string) (*Siegfried, error) { errOpening := "siegfried: error opening signature file, got %v; try running `sf -update`" errNotSig := "siegfried: not a siegfried signature file; try running `sf -update`" errUpdateSig := "siegfried: signature file is incompatible with this version of sf; try running `sf -update`" fbuf, err := ioutil.ReadFile(path) if err != nil { return nil, fmt.Errorf(errOpening, err) } if len(fbuf) < len(config.Magic())+2 { return nil, fmt.Errorf(errNotSig) } if string(fbuf[:len(config.Magic())]) != string(config.Magic()) { return nil, fmt.Errorf(errNotSig) } if major, minor := fbuf[len(config.Magic())], fbuf[len(config.Magic())+1]; major < byte(config.Version()[0]) || (major == byte(config.Version()[0]) && minor < byte(config.Version()[1])) { return nil, fmt.Errorf(errUpdateSig) } r := bytes.NewBuffer(fbuf[len(config.Magic())+2:]) rc := flate.NewReader(r) defer rc.Close() buf, err := ioutil.ReadAll(rc) if err != nil { return nil, fmt.Errorf(errOpening, err) } ls := persist.NewLoadSaver(buf) return &Siegfried{ C: ls.LoadTime(), em: extensionmatcher.Load(ls), cm: containermatcher.Load(ls), bm: bytematcher.Load(ls), tm: textmatcher.Load(ls), ids: func() []core.Identifier { ids := make([]core.Identifier, ls.LoadTinyUInt()) for i := range ids { ids[i] = core.LoadIdentifier(ls) } return ids }(), buffers: siegreader.New(), }, ls.Err }
func main() { flag.Parse() /*//UNCOMMENT TO RUN PROFILER go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }()*/ if *home != config.Home() { config.SetHome(*home) } if *sig != config.SignatureBase() { config.SetSignature(*sig) } if *version { version := config.Version() fmt.Printf("siegfried version: %d.%d.%d\n", version[0], version[1], version[2]) return } if *update { msg, err := updateSigs() if err != nil { log.Fatalf("Error: failed to update signature file, %v", err) } fmt.Println(msg) return } // during parallel scanning or in server mode, unsafe to access the last read buffer - so can't unzip or hash if *multi > 1 || *serve != "" { if *archive { log.Fatalln("Error: cannot scan archive formats when running in parallel mode") } if *hashf != "" { log.Fatalln("Error: cannot calculate file checksum when running in parallel mode") } } if err := setHash(); err != nil { log.Fatal(err) } if *serve != "" || *fprflag { s, err := siegfried.Load(config.Signature()) if err != nil { log.Fatalf("Error: error loading signature file, got: %v", err) } if *serve != "" { log.Printf("Starting server at %s. Use CTRL-C to quit.\n", *serve) listen(*serve, s) return } log.Printf("FPR server started at %s. Use CTRL-C to quit.\n", config.Fpr()) serveFpr(config.Fpr(), s) return } if flag.NArg() != 1 { log.Fatalln("Error: expecting a single file or directory argument") } s, err := siegfried.Load(config.Signature()) if err != nil { log.Fatalf("Error: error loading signature file, got: %v", err) } var w writer switch { case *debug: config.SetDebug() w = debugWriter{} case *slow: config.SetSlow() w = &slowWriter{os.Stdout} case *csvo: w = newCSV(os.Stdout) case *jsono: w = newJSON(os.Stdout) case *droido: w = newDroid(os.Stdout) case *knowno: w = &knownWriter{true, os.Stdout} case *unknowno: w = &knownWriter{false, os.Stdout} default: w = newYAML(os.Stdout) } // support reading list files from stdin if flag.Arg(0) == "-" { w.writeHead(s) scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { info, err := os.Stat(scanner.Text()) if err != nil || info.IsDir() { w.writeFile(scanner.Text(), 0, "", nil, fmt.Errorf("failed to identify %s (in scanning mode, inputs must all be files and not directories), got: %v", scanner.Text(), err), nil) } else { identifyFile(w, s, scanner.Text(), info.Size(), info.ModTime().Format(time.RFC3339)) } } w.writeTail() os.Exit(0) } info, err := os.Stat(flag.Arg(0)) if err != nil { log.Fatalf("Error: error getting info for %v, got: %v", flag.Arg(0), err) } if info.IsDir() { if config.Debug() { log.Fatalln("Error: when scanning in debug mode, give a file rather than a directory argument") } w.writeHead(s) if *multi > 16 { *multi = 16 } if *multi > 1 { multiIdentifyP(w, s, flag.Arg(0), *nr) } else { multiIdentifyS(w, s, flag.Arg(0), *nr) } w.writeTail() os.Exit(0) } w.writeHead(s) identifyFile(w, s, flag.Arg(0), info.Size(), info.ModTime().Format(time.RFC3339)) w.writeTail() os.Exit(0) }
func main() { flag.Parse() /*//UNCOMMENT TO RUN PROFILER go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }()*/ if *version { version := config.Version() fmt.Printf("siegfried %d.%d.%d\n", version[0], version[1], version[2]) s, err := siegfried.Load(config.Signature()) if err != nil { fmt.Println(err) return } fmt.Print(s) return } if *home != config.Home() { config.SetHome(*home) } if *sig != config.SignatureBase() { config.SetSignature(*sig) } if *update { msg, err := updateSigs() if err != nil { log.Fatalf("[FATAL] failed to update signature file, %v", err) } fmt.Println(msg) return } // during parallel scanning or in server mode, unsafe to access the last read buffer - so can't unzip or hash if *multi > 1 || *serve != "" { if *archive { log.Fatalln("[FATAL] cannot scan archive formats when running in parallel or server mode") } if *hashf != "" { log.Fatalln("[FATAL] cannot calculate file checksum when running in parallel or server mode") } } if *logf != "" { if *multi > 1 && *logf != "error" { log.Fatalln("[FATAL] cannot log in parallel mode") } if err := newLogger(*logf); err != nil { log.Fatalln(err) } } if err := setHash(); err != nil { log.Fatal(err) } if *serve != "" || *fprflag { s, err := siegfried.Load(config.Signature()) if err != nil { log.Fatalf("[FATAL] error loading signature file, got: %v", err) } if *serve != "" { log.Printf("Starting server at %s. Use CTRL-C to quit.\n", *serve) listen(*serve, s) return } log.Printf("FPR server started at %s. Use CTRL-C to quit.\n", config.Fpr()) serveFpr(config.Fpr(), s) return } if flag.NArg() != 1 { log.Fatalln("[FATAL] expecting a single file or directory argument") } s, err := siegfried.Load(config.Signature()) if err != nil { log.Fatalf("[FATAL] error loading signature file, got: %v", err) } var w writer switch { case *csvo: w = newCSV(os.Stdout) case *jsono: w = newJSON(os.Stdout) case *droido: w = newDroid(os.Stdout) default: w = newYAML(os.Stdout) } if lg != nil && lg.w == os.Stdout { w = logWriter{} } // support reading list files from stdin if flag.Arg(0) == "-" { w.writeHead(s) scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { info, err := os.Stat(scanner.Text()) if err != nil { info, err = retryStat(scanner.Text(), err) } if err != nil || info.IsDir() { writeError(w, scanner.Text(), 0, "", fmt.Errorf("failed to identify %s (in scanning mode, inputs must all be files and not directories), got: %v", scanner.Text(), err)) } else { identifyFile(w, s, scanner.Text(), info.Size(), info.ModTime().Format(time.RFC3339)) } } w.writeTail() lg.printElapsed() os.Exit(0) } info, err := os.Stat(flag.Arg(0)) if err != nil { info, err = retryStat(flag.Arg(0), err) if err != nil { log.Fatalf("[FATAL] cannot get info for %v, got: %v", flag.Arg(0), err) } } if info.IsDir() { w.writeHead(s) if *multi > 16 { *multi = 16 } if *multi > 1 { err = multiIdentifyP(w, s, flag.Arg(0), *nr) } else { if *throttlef != 0 { throttle = time.NewTicker(*throttlef) defer throttle.Stop() } err = multiIdentifyS(w, s, flag.Arg(0), "", *nr) } w.writeTail() if err != nil { log.Fatalf("[FATAL] %v\n", err) } lg.printElapsed() os.Exit(0) } w.writeHead(s) identifyFile(w, s, flag.Arg(0), info.Size(), info.ModTime().Format(time.RFC3339)) w.writeTail() lg.printElapsed() os.Exit(0) }