diff --git a/README.md b/README.md index 7c5e4cc..94f8896 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 🗃️ snapkup v0.1.0 +# 🗃️ snapkup v0.2.0 Snapkup is a simple backup tool that takes snapshots of your filesystem (or the parts that you'll decide), storing them efficiently and conveniently. @@ -10,7 +10,7 @@ Snapkup's goal is to store efficiently one or more filesystem's situation at giv - You register one or more backup roots, directory or files that will be snapshotted - You take one or more snapshots. Snapkup lists all the tree for those roots, taking a snapshot of the contents - All the files in the roots are deduplicated, and only the files that are different are stored - - All files that can be compressed are stored as such, using `zstd -9` + - It's possible to compress the files, using `zstd -9` - Files are stored in an efficient manner, with a shallow directory structure. - You can restore the situation of the roots at a given snapshot, later on - Files' and dirs' mode and modification time are preserved @@ -29,23 +29,53 @@ We will backup the contents of the `C:\MyImportantDir`, using the `C:\MySnapkupD ### Register the directory to backup as a root -`snapkup.exe -d C:\MySnapkupDir add-root C:\MyImportantDir` +`snapkup.exe -d C:\MySnapkupDir root add C:\MyImportantDir` ### Take your first snapshot -`snapkup.exe -d C:\MySnapkupDir snap` +`snapkup.exe -d C:\MySnapkupDir snap take` -*add `-z` if you want to compress the files being backed up*. +*Add `-z` if you want to compress the files being backed up. Add `-l` to specify a label.* + +`snapkup.exe -d C:\MySnapkupDir snap take -z -l "My first label"` + +*Alias: `snap do`* + +### Change the label of a snap + +`snapkup.exe -d C:\MySnapkupDir snap label 0 "My First Label"` + +*Alias: `snap lbl`* + +### Get info on a snapshot + +`snapkup.exe -d C:\MySnapkupDir snap info 0` + +*gives info like: number of files, number of dirs, size, and how much space on backup filesystem will be freed if this snap is deleted.* + +### Get the file list on a snapshot + +`snapkup.exe -d C:\MySnapkupDir snap filelist 0` + +*prints a list of the directories and files for a snap.* + +*Alias: `snap fl`* ### Delete it, because... just because. -`snapkup.exe -d C:\MySnapkupDir del-snap 0` +`snapkup.exe -d C:\MySnapkupDir snap del 0` + +*Alias: `snap rm`* ### Or restore it! -`snapkup.exe -d C:\MySnapkupDir restore 0 C:\MyRestoreDir` +`snapkup.exe -d C:\MySnapkupDir snap restore 0 C:\MyRestoreDir` + +*the destination directory must be empty. It is also possible to specify a prefix path to select only a part of the file list:* + +`snapkup.exe -d C:\MySnapkupDir snap restore 0 C:\MyRestoreDir --prefix-path /foo/bar` -*the destination directory should be empty.* +*Alias: `snap res`* ## Status diff --git a/src/commands/add_root/add_root.go b/src/commands/add_root/add_root.go index 49b540a..c249681 100644 --- a/src/commands/add_root/add_root.go +++ b/src/commands/add_root/add_root.go @@ -24,7 +24,6 @@ func AddRoot(bkpDir string, toAdd string) error { return errBeginning } - // TODO QueryOnce throwaway := 1 row := db.QueryRow("SELECT 1 FROM ROOTS WHERE PATH = ?", toAdd) if errQuerying := row.Scan(&throwaway); errQuerying == nil { diff --git a/src/commands/del_snap/del_snap.go b/src/commands/del_snap/del_snap.go index 514e829..87f3dec 100644 --- a/src/commands/del_snap/del_snap.go +++ b/src/commands/del_snap/del_snap.go @@ -53,7 +53,7 @@ func DelSnap(bkpDir string, toDel int) error { if errScanning := rows.Scan(&hash); errScanning != nil { return errScanning } - pathToDel := path.Join(bkpDir, hash[0:2], hash[2:]) + pathToDel := path.Join(bkpDir, hash[0:1], hash) if errDeleting := os.Remove(pathToDel); errDeleting != nil { fmt.Fprintf(os.Stderr, "ERROR: deleting file %s; %v\n", hash, errDeleting) } diff --git a/src/commands/info_snap/info_snap.go b/src/commands/info_snap/info_snap.go new file mode 100644 index 0000000..edf974f --- /dev/null +++ b/src/commands/info_snap/info_snap.go @@ -0,0 +1,92 @@ +package info_snap + +import ( + "database/sql" + "fmt" + + "github.com/proofrock/snapkup/util" +) + +const sql1 = `WITH +consts AS (SELECT ? AS snap), +data AS ( + SELECT 1 AS key, COUNT(1) AS val FROM ITEMS WHERE SNAP = (SELECT snap FROM consts) AND IS_DIR = 0 +UNION ALL SELECT 2 AS key, COUNT(1) AS val FROM ITEMS WHERE SNAP = (SELECT snap FROM consts) AND IS_DIR = 1 +UNION ALL SELECT 3 AS key, SUM(b.SIZE) AS val FROM ITEMS i, BLOBS b WHERE i.HASH = b.HASH AND SNAP = (SELECT snap FROM consts) AND IS_DIR = 0 +UNION ALL SELECT 4 AS key, SUM(BLOB_SIZE) AS val FROM BLOBS WHERE HASH IN (SELECT HASH FROM ITEMS WHERE SNAP = (SELECT snap FROM consts) AND IS_DIR = 0) +UNION ALL SELECT 5 AS key, SUM(BLOB_SIZE) AS val FROM BLOBS +UNION ALL SELECT 6 AS key, SUM(BLOB_SIZE) AS val FROM BLOBS WHERE HASH NOT IN (SELECT HASH FROM ITEMS WHERE SNAP != (SELECT snap FROM consts) AND IS_DIR = 0) +) +SELECT val FROM data ORDER BY key ASC` + +var titles = [6]string{ + "Files", + "Directories", + "Size", + "Stored size", + "Tot. stored (all snaps)", + "Free when deleted", +} + +var isInByte = [6]bool{false, false, true, true, true, true} + +const suffixes = "KMGTPE" + +const unit = 1024 + +func fmtBytes(b int64) string { + if b < unit { + return fmt.Sprintf("%d b", b) + } + div, exp := int64(unit), 0 + for n := b / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %cb", + float64(b)/float64(div), suffixes[exp]) +} + +func InfoSnap(bkpDir string, snap int) error { + maxLen := 0 + for _, title := range titles { + if len(title) > maxLen { + maxLen = len(title) + } + } + + dbPath, errComposingDbPath := util.DbFile(bkpDir) + if errComposingDbPath != nil { + return errComposingDbPath + } + + db, errOpeningDb := sql.Open("sqlite3", dbPath) + if errOpeningDb != nil { + return errOpeningDb + } + defer db.Close() + + rows, errQuerying := db.Query(sql1, snap) + if errQuerying != nil { + return errQuerying + } + defer rows.Close() + i := 0 + for rows.Next() { + var val int64 + if errScanning := rows.Scan(&val); errScanning != nil { + return errScanning + } + if isInByte[i] { + fmt.Printf(fmt.Sprintf("%%-%ds: %%s\n", maxLen), titles[i], fmtBytes(val)) + } else { + fmt.Printf(fmt.Sprintf("%%-%ds: %%d\n", maxLen), titles[i], val) + } + i++ + } + if errClosingQry := rows.Err(); errClosingQry != nil { + return errClosingQry + } + + return nil +} diff --git a/src/commands/init/init.go b/src/commands/init/init.go index 630d48e..8fe6ab6 100644 --- a/src/commands/init/init.go +++ b/src/commands/init/init.go @@ -4,18 +4,26 @@ import ( "database/sql" "fmt" "io/fs" + "math/rand" "os" "path" + "time" _ "github.com/mattn/go-sqlite3" "github.com/proofrock/snapkup/util" ) -var sqls = [4]string{ +var sqls = [5]string{ + `CREATE TABLE "PARAMS" ( + "KEY" TEXT NOT NULL, + "VALUE" TEXT NOT NULL, + PRIMARY KEY("KEY") + )`, `CREATE TABLE "SNAPS" ( "ID" INTEGER NOT NULL, "TIMESTAMP" INTEGER NOT NULL, + "LABEL" TEXT NOT NULL, PRIMARY KEY("ID") )`, `CREATE TABLE "ITEMS" ( @@ -41,8 +49,6 @@ var sqls = [4]string{ )`, } -const hex = "0123456789abcdef" - func Init(bkpDir string) error { if isEmpty, errCheckingEmpty := util.IsEmpty(bkpDir); errCheckingEmpty != nil { return errCheckingEmpty @@ -70,14 +76,24 @@ func Init(bkpDir string) error { } } + iv := make([]byte, 16, 16) + rand.Seed(time.Now().Unix()) + if _, errRandomizing := rand.Read(iv); errRandomizing != nil { + return errRandomizing + } + + if _, errExecing := tx.Exec("INSERT INTO PARAMS (KEY, VALUE) VALUES ('IV', ?)", iv); errExecing != nil { + tx.Rollback() + return errExecing + } + if errCommitting := tx.Commit(); errCommitting != nil { return errCommitting } - for i := 0; i < 16; i++ { - for j := 0; j < 16; j++ { - os.Mkdir(path.Join(bkpDir, hex[i:i+1]+hex[j:j+1]), fs.FileMode(0700)) - } + hex := []rune("0123456789abcdef") + for i := 0; i < len(hex); i++ { + os.Mkdir(path.Join(bkpDir, string(hex[i])), fs.FileMode(0700)) } println("Backup directory correctly initialized in ", bkpDir) diff --git a/src/commands/label_snap/label_snap.go b/src/commands/label_snap/label_snap.go new file mode 100644 index 0000000..312aba0 --- /dev/null +++ b/src/commands/label_snap/label_snap.go @@ -0,0 +1,28 @@ +package labelsnap + +import ( + "database/sql" + + "github.com/proofrock/snapkup/util" +) + +func LabelSnap(bkpDir string, snap int, label string) error { + dbPath, errComposingDbPath := util.DbFile(bkpDir) + if errComposingDbPath != nil { + return errComposingDbPath + } + + db, errOpeningDb := sql.Open("sqlite3", dbPath) + if errOpeningDb != nil { + return errOpeningDb + } + defer db.Close() + + if _, errExecing := db.Exec("UPDATE SNAPS SET LABEL = ? WHERE ID = ?", label, snap); errExecing != nil { + return errExecing + } + + println("Ok.") + + return nil +} diff --git a/src/commands/list_snap/list_snap.go b/src/commands/list_snap/list_snap.go new file mode 100644 index 0000000..3638b76 --- /dev/null +++ b/src/commands/list_snap/list_snap.go @@ -0,0 +1,40 @@ +package list_snap + +import ( + "database/sql" + + "github.com/proofrock/snapkup/util" +) + +const sql1 = "SELECT PATH FROM ITEMS WHERE SNAP = ? ORDER BY PATH ASC" + +func ListSnap(bkpDir string, snap int) error { + dbPath, errComposingDbPath := util.DbFile(bkpDir) + if errComposingDbPath != nil { + return errComposingDbPath + } + + db, errOpeningDb := sql.Open("sqlite3", dbPath) + if errOpeningDb != nil { + return errOpeningDb + } + defer db.Close() + + rows, errQuerying := db.Query(sql1, snap) + if errQuerying != nil { + return errQuerying + } + defer rows.Close() + for rows.Next() { + var path string + if errScanning := rows.Scan(&path); errScanning != nil { + return errScanning + } + println(path) + } + if errClosingQry := rows.Err(); errClosingQry != nil { + return errClosingQry + } + + return nil +} diff --git a/src/commands/list_snaps/list_snaps.go b/src/commands/list_snaps/list_snaps.go index d03e418..2e894de 100644 --- a/src/commands/list_snaps/list_snaps.go +++ b/src/commands/list_snaps/list_snaps.go @@ -11,6 +11,7 @@ import ( type snap struct { id int timestamp int64 + label string } func ListSnaps(bkpDir string) error { @@ -26,14 +27,14 @@ func ListSnaps(bkpDir string) error { defer db.Close() var snaps []snap - rows, errQuerying := db.Query("SELECT ID, TIMESTAMP FROM SNAPS ORDER BY ID DESC") + rows, errQuerying := db.Query("SELECT ID, TIMESTAMP, LABEL FROM SNAPS ORDER BY ID DESC") if errQuerying != nil { return errQuerying } defer rows.Close() for rows.Next() { var snap snap - if errScanning := rows.Scan(&snap.id, &snap.timestamp); errScanning != nil { + if errScanning := rows.Scan(&snap.id, &snap.timestamp, &snap.label); errScanning != nil { return errScanning } snaps = append(snaps, snap) @@ -44,7 +45,7 @@ func ListSnaps(bkpDir string) error { for _, snap := range snaps { ts := time.UnixMilli(snap.timestamp).Local().Format("2 Jan 2006, 15:04:05 (MST)") - fmt.Printf("Snap %d:\t%s\n", snap.id, ts) + fmt.Printf("Snap %d:\t%s\t%s\n", snap.id, ts, snap.label) } return nil diff --git a/src/commands/restore/restore.go b/src/commands/restore/restore.go index 96fcbf3..51b1a0c 100644 --- a/src/commands/restore/restore.go +++ b/src/commands/restore/restore.go @@ -7,27 +7,29 @@ import ( "os" "path" "path/filepath" + "strings" "time" "github.com/proofrock/snapkup/util" ) const sql1 = ` -SELECT i.PATH, i.HASH, b.IS_COMPRESSED, i.MODE, i.MOD_TIME +SELECT i.PATH, i.HASH, i.IS_DIR, COALESCE(b.IS_COMPRESSED, 0), i.MODE, i.MOD_TIME FROM ITEMS i - JOIN BLOBS b ON b.HASH = i.HASH + LEFT JOIN BLOBS b ON b.HASH = i.HASH WHERE i.SNAP = ? ORDER BY i.PATH ASC` type item struct { Path string Hash string + IsDir int IsCompressed int Mode uint32 ModTime int64 } -func Restore(bkpDir string, snap int, restoreDir string) error { +func Restore(bkpDir string, snap int, restoreDir string, restorePrefixPath *string) error { if isEmpty, errCheckingEmpty := util.IsEmpty(restoreDir); errCheckingEmpty != nil { return errCheckingEmpty } else if !isEmpty { @@ -56,10 +58,14 @@ func Restore(bkpDir string, snap int, restoreDir string) error { defer rows.Close() for rows.Next() { var item item - if errScanning := rows.Scan(&item.Path, &item.Hash, &item.IsCompressed, &item.Mode, &item.ModTime); errScanning != nil { + if errScanning := rows.Scan(&item.Path, &item.Hash, &item.IsDir, &item.IsCompressed, &item.Mode, &item.ModTime); errScanning != nil { return errScanning } + if restorePrefixPath != nil && !strings.HasPrefix(item.Path, *restorePrefixPath) { + continue + } + if item.Hash == "" { numDirs++ } else { @@ -77,9 +83,9 @@ func Restore(bkpDir string, snap int, restoreDir string) error { for _, item := range items { dest := path.Join(restoreDir, item.Path) - if item.Hash != "" { + if item.IsDir == 0 { // it's a file - source := path.Join(bkpDir, item.Hash[0:2], item.Hash[2:]) + source := path.Join(bkpDir, item.Hash[0:1], item.Hash) if errMkingDir := os.MkdirAll(filepath.Dir(dest), os.FileMode(0700)); errMkingDir != nil { return errMkingDir @@ -88,6 +94,10 @@ func Restore(bkpDir string, snap int, restoreDir string) error { if errCopying := util.Restore(source, dest, item.IsCompressed == 1); errCopying != nil { return errCopying } + } else { + if errMkingDir := os.MkdirAll(dest, os.FileMode(0700)); errMkingDir != nil { + return errMkingDir + } } } diff --git a/src/commands/snap/snap.go b/src/commands/snap/snap.go index d6a7600..3fe0d9d 100644 --- a/src/commands/snap/snap.go +++ b/src/commands/snap/snap.go @@ -10,7 +10,7 @@ import ( "github.com/proofrock/snapkup/util" ) -func Snap(bkpDir string, compress bool) error { +func Snap(bkpDir string, compress bool, label string) error { dbPath, errComposingDbPath := util.DbFile(bkpDir) if errComposingDbPath != nil { return errComposingDbPath @@ -49,15 +49,28 @@ func Snap(bkpDir string, compress bool) error { return errGettingRoots } - files, numFiles, numDirs := util.WalkFSTree(roots) + var iv []byte + row := db.QueryRow("SELECT VALUE FROM PARAMS WHERE KEY = 'IV'") + if errQuerying := row.Scan(&iv); errQuerying != nil { + tx.Rollback() + return errQuerying + } + + files, numFiles, numDirs := util.WalkFSTree(roots, iv) fmt.Printf("Found %d files and %d directories.\n", numFiles, numDirs) sort.Slice(files, func(i int, j int) bool { return files[i].FullPath < files[j].FullPath }) - snap, errRecSnap := recNewSnap(tx) - if errRecSnap != nil { + var snap int + row = tx.QueryRow("SELECT COALESCE(MAX(ID) + 1, 0) FROM SNAPS") + if errIdSnap := row.Scan(&snap); errIdSnap != nil { + tx.Rollback() + return errIdSnap + } + _, errNewSnap := tx.Exec("INSERT INTO SNAPS (ID, TIMESTAMP, LABEL) VALUES (?, ?, ?)", snap, time.Now().UnixMilli(), label) + if errNewSnap != nil { tx.Rollback() - return errRecSnap + return errNewSnap } st1tx := tx.Stmt(st1) @@ -105,7 +118,7 @@ func Snap(bkpDir string, compress bool) error { // Iterates over the blobs to write, and writes them (compressing or not) for hash, finfo := range newHashes { - pathDest := path.Join(bkpDir, hash[0:2], hash[2:]) + pathDest := path.Join(bkpDir, hash[0:1], hash) blobSize, errCopying := util.Store(finfo.FullPath, pathDest, compress) if errCopying != nil { @@ -132,13 +145,3 @@ func Snap(bkpDir string, compress bool) error { return nil } - -func recNewSnap(tx *sql.Tx) (nuSnap int, errNewSnap error) { - row := tx.QueryRow("SELECT COALESCE(MAX(ID) + 1, 0) FROM SNAPS") - errNewSnap = row.Scan(&nuSnap) - if errNewSnap != nil { - return - } - _, errNewSnap = tx.Exec("INSERT INTO SNAPS (ID, TIMESTAMP) VALUES (?, ?)", nuSnap, time.Now().UnixMilli()) - return -} diff --git a/src/main.go b/src/main.go index bff8964..162bc5f 100644 --- a/src/main.go +++ b/src/main.go @@ -10,8 +10,11 @@ import ( addroot "github.com/proofrock/snapkup/commands/add_root" delroot "github.com/proofrock/snapkup/commands/del_root" delsnaps "github.com/proofrock/snapkup/commands/del_snap" + "github.com/proofrock/snapkup/commands/info_snap" initcmd "github.com/proofrock/snapkup/commands/init" + labelsnap "github.com/proofrock/snapkup/commands/label_snap" listroots "github.com/proofrock/snapkup/commands/list_roots" + "github.com/proofrock/snapkup/commands/list_snap" listsnaps "github.com/proofrock/snapkup/commands/list_snaps" "github.com/proofrock/snapkup/commands/restore" snap "github.com/proofrock/snapkup/commands/snap" @@ -19,32 +22,48 @@ import ( "github.com/proofrock/snapkup/util" ) -const version = "v0.1.0" +const version = "v0.2.0" var ( relBkpDir = kingpin.Flag("backup-dir", "The directory to store backups into.").Required().Short('d').ExistingDir() initCmd = kingpin.Command("init", "Initializes an empty backups directory.") - addRootCmd = kingpin.Command("add-root", "Adds a new root to the pool.") + rootCmd = kingpin.Command("root", "Commands related to the root(s)").Alias("r") + + addRootCmd = rootCmd.Command("add", "Adds a new root to the pool.").Alias("a") relRootToAdd = addRootCmd.Arg("root", "The new root to add.").Required().ExistingFileOrDir() - listRootsCmd = kingpin.Command("list-roots", "Lists the roots currently in the pool") + listRootsCmd = rootCmd.Command("list", "Lists the roots currently in the pool").Alias("ls") - delRootCmd = kingpin.Command("del-root", "Removes a root from the pool.") + delRootCmd = rootCmd.Command("del", "Removes a root from the pool.").Alias("rm") rootToDel = delRootCmd.Arg("root", "The root to remove.").Required().String() - snapCmd = kingpin.Command("snap", "Takes a new snapshot of the roots.") - snapCompress = kingpin.Flag("compress", "Compresses the stored files.").Short('z').Bool() + snpCmd = kingpin.Command("snap", "Commands related to the snap(s)").Alias("s") + + snapCmd = snpCmd.Command("take", "Takes a new snapshot of the roots.").Alias("do") + snapCompress = snapCmd.Flag("compress", "Compresses the stored files.").Short('z').Bool() + snapLabel = snapCmd.Flag("label", "Label for this snap.").Short('l').Default("").String() - listSnapsCmd = kingpin.Command("list-snaps", "Lists the snaps currently in the pool") + listSnapsCmd = snpCmd.Command("list", "Lists the snaps currently in the pool").Alias("ls") - delSnapCmd = kingpin.Command("del-snap", "Removes a snap from the pool.") + delSnapCmd = snpCmd.Command("del", "Removes a snap from the pool.").Alias("rm") snapToDel = delSnapCmd.Arg("snap", "The snap to remove.").Required().Int() - restoreCmd = kingpin.Command("restore", "Restores a snap.") - snapToRestore = restoreCmd.Arg("snap", "The snap to restore.").Required().Int() - relDirToRestore = restoreCmd.Arg("restore-dir", "The dir to restore into. Must exist and be empty.").Required().ExistingDir() + restoreCmd = snpCmd.Command("restore", "Restores a snap.").Alias("res") + snapToRestore = restoreCmd.Arg("snap", "The snap to restore.").Required().Int() + relDirToRestore = restoreCmd.Arg("restore-dir", "The dir to restore into. Must exist and be empty.").Required().ExistingDir() + restorePrefixPath = restoreCmd.Flag("prefix-path", "Only the files whose path starts with this prefix are considered.").String() + + infoSnapCmd = snpCmd.Command("info", "Gives relevant information on a snap.") + snapToInfo = infoSnapCmd.Arg("snap", "The snap to give info about.").Required().Int() + + listSnapCmd = snpCmd.Command("filelist", "Prints the list of files for a snap.").Alias("fl") + snapToList = listSnapCmd.Arg("snap", "The snap to list files for.").Required().Int() + + labelSnapCmd = snpCmd.Command("label", "Sets or changes the label of a snap.").Alias("lbl") + snapToLabel = labelSnapCmd.Arg("snap", "The snap to label.").Required().Int() + labelSnapLabel = labelSnapCmd.Arg("label", "The label.").Required().String() ) func app() (errApp error) { @@ -74,7 +93,7 @@ func app() (errApp error) { errApp = delroot.DelRoot(bkpDir, *rootToDel) case snapCmd.FullCommand(): - errApp = snap.Snap(bkpDir, *snapCompress) + errApp = snap.Snap(bkpDir, *snapCompress, *snapLabel) case listSnapsCmd.FullCommand(): errApp = listsnaps.ListSnaps(bkpDir) @@ -86,8 +105,17 @@ func app() (errApp error) { if dirToRestore, errAbsolutizing := filepath.Abs(*relDirToRestore); errAbsolutizing != nil { errApp = errAbsolutizing } else { - errApp = restore.Restore(bkpDir, *snapToRestore, dirToRestore) + errApp = restore.Restore(bkpDir, *snapToRestore, dirToRestore, restorePrefixPath) } + + case infoSnapCmd.FullCommand(): + errApp = info_snap.InfoSnap(bkpDir, *snapToInfo) + + case listSnapCmd.FullCommand(): + errApp = list_snap.ListSnap(bkpDir, *snapToList) + + case labelSnapCmd.FullCommand(): + errApp = labelsnap.LabelSnap(bkpDir, *snapToLabel, *labelSnapLabel) } } diff --git a/src/util/files.go b/src/util/files.go index b8544e3..94875be 100644 --- a/src/util/files.go +++ b/src/util/files.go @@ -38,7 +38,7 @@ type FileNfo struct { Mode fs.FileMode } -func WalkFSTree(roots []string) (files []FileNfo, numFiles int, numDirs int) { +func WalkFSTree(roots []string, iv []byte) (files []FileNfo, numFiles int, numDirs int) { for _, root := range roots { if froot, errStatsing := os.Stat(root); errStatsing != nil { fmt.Fprintf(os.Stderr, "Error in Stat() of root: %v\n", errStatsing) @@ -55,7 +55,7 @@ func WalkFSTree(roots []string) (files []FileNfo, numFiles int, numDirs int) { } else { numFiles++ isDir = 0 - if _hash, errHashing := FileHash(path); errHashing != nil { + if _hash, errHashing := FileHash(path, iv); errHashing != nil { fmt.Fprintf(os.Stderr, "Error hashing file: %v\n", errHashing) } else { hash = _hash @@ -75,7 +75,7 @@ func WalkFSTree(roots []string) (files []FileNfo, numFiles int, numDirs int) { return nil }) } else { - if hash, errHashing := FileHash(root); errHashing != nil { + if hash, errHashing := FileHash(root, iv); errHashing != nil { fmt.Fprintf(os.Stderr, "Error hashing file: %v\n", errHashing) } else { files = append(files, FileNfo{ @@ -97,16 +97,14 @@ func WalkFSTree(roots []string) (files []FileNfo, numFiles int, numDirs int) { const bufSize = 1024 * 32 // 32Kb -var nothingUpMySleeve = []byte("SnapkupIsCool!!!") - -func FileHash(path string) (string, error) { +func FileHash(path string, iv []byte) (string, error) { source, errOpening := os.Open(path) if errOpening != nil { return "", errOpening } defer source.Close() - hasher := siphash.New128(nothingUpMySleeve) + hasher := siphash.New128(iv) buf := make([]byte, bufSize) for { n, errHashingFile := source.Read(buf) @@ -123,13 +121,6 @@ func FileHash(path string) (string, error) { } ret := hasher.Sum(nil) - stat, _ := source.Stat() - len := stat.Size() - - for len > 0 { - ret = append(ret, byte(0xff&len)) - len >>= 8 - } return strings.ToLower(hex.EncodeToString(ret)), nil }