diff --git a/cmd/camput/files.go b/cmd/camput/files.go index 559e40f76..7c874ab73 100644 --- a/cmd/camput/files.go +++ b/cmd/camput/files.go @@ -52,6 +52,7 @@ type fileCmd struct { filePermanodes bool // make planned permanodes for each file (based on their digest) vivify bool exifTime bool // use metadata (such as in EXIF) to find the creation time of the file + capCtime bool // use mtime as creation time of the file, if it would be bigger than modification time diskUsage bool // show "du" disk usage only (dry run mode), don't actually upload argsFromInput bool // Android mode: filenames piped into stdin, one at a time. @@ -84,6 +85,7 @@ func init() { flags.BoolVar(&cmd.havecache, "havecache", true, "Use the 'have cache', a cache keeping track of what blobs the remote server should already have from previous uploads.") flags.BoolVar(&cmd.memstats, "debug-memstats", false, "Enter debug in-memory mode; collecting stats only. Doesn't upload anything.") flags.StringVar(&cmd.histo, "debug-histogram-file", "", "Optional file to create and write the blob size for each file uploaded. For use with GNU R and hist(read.table(\"filename\")$V1). Requires debug-memstats.") + flags.BoolVar(&cmd.capCtime, "capctime", false, "For file blobs use file modification time as creation time if it would be bigger (newer) than modification time. For stable filenode creation (you can forge mtime, but can't forge ctime).") flags.BoolVar(&flagUseSQLiteChildCache, "sqlitecache", false, "Use sqlite for the statcache and havecache instead of a flat cache.") } else { cmd.havecache = true @@ -148,6 +150,7 @@ func (c *fileCmd) RunCommand(args []string) error { tag: c.tag, vivify: c.vivify, exifTime: c.exifTime, + capCtime: c.capCtime, } var ( @@ -516,6 +519,9 @@ func (up *Uploader) uploadNodeRegularFile(n *node) (*client.PutResult, error) { filebb.SetModTime(modtime) } } + if up.fileOpts.capCtime { + filebb.CapCreationTime() + } var ( size = n.fi.Size() diff --git a/cmd/camput/uploader.go b/cmd/camput/uploader.go index 0ccbaaa09..75e003b25 100644 --- a/cmd/camput/uploader.go +++ b/cmd/camput/uploader.go @@ -54,8 +54,9 @@ type fileOptions struct { // the above permanode. tag string // perform for the client the actions needing gpg signing when uploading a file. - vivify bool - exifTime bool // use the time in exif metadata as the modtime if possible. + vivify bool + exifTime bool // use the time in exif metadata as the modtime if possible. + capCtime bool // use mtime as ctime if ctime > mtime } func (o *fileOptions) tags() []string { @@ -73,6 +74,10 @@ func (o *fileOptions) wantVivify() bool { return o != nil && o.vivify } +func (o *fileOptions) wantCapCtime() bool { + return o != nil && o.capCtime +} + func (up *Uploader) uploadString(s string) (*client.PutResult, error) { return up.Upload(client.NewUploadHandleFromString(s)) } diff --git a/pkg/schema/blob.go b/pkg/schema/blob.go index 6a00a381f..9ca59acdc 100644 --- a/pkg/schema/blob.go +++ b/pkg/schema/blob.go @@ -383,6 +383,19 @@ func (bb *Builder) SetModTime(t time.Time) *Builder { return bb } +// CapCreationTime caps the "unixCtime" field to be less or equal than "unixMtime" +func (bb *Builder) CapCreationTime() *Builder { + ctime, ok := bb.m["unixCtime"].(string) + if !ok { + return bb + } + mtime, ok := bb.m["unixMtime"].(string) + if ok && ctime > mtime { + bb.m["unixCtime"] = mtime + } + return bb +} + // ModTime returns the "unixMtime" modtime field, if set. func (bb *Builder) ModTime() (t time.Time, ok bool) { s, ok := bb.m["unixMtime"].(string)