Skip to content

Commit

Permalink
Adding support for ChangeTime in Windows, adding HasChangeTime() and …
Browse files Browse the repository at this point in the history
…HasBirthTime() to timespec
  • Loading branch information
djherbis committed Nov 8, 2015
1 parent b7dd8e2 commit 3454a99
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 8 deletions.
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ func main() {
log.Println(t.AccessTime())
log.Println(t.ModTime())

if times.HasChangeTime {
if t.HasChangeTime() {
log.Println(t.ChangeTime())
}

if times.HasBirthTime {
if t.HasBirthTime() {
log.Println(t.BirthTime())
}
}
Expand All @@ -46,9 +46,13 @@ Supported Times
|:-----:|:-------:|:-----:|:-------:|:---------:|:------:|:-------:|:----:|:------:|:-------:|:-----:|
| atime |||||||||||
| mtime |||||||||||
| ctime | ||||||||| |
| ctime | * ||||||||| |
| btime || | | | ||||

* Windows XP does not have ChangeTime so HasChangeTime = false,
however Vista onward does have ChangeTime so Timespec.HasChangeTime() will
only return false on those platforms when the syscall used to obtain them fails.

Installation
------------
```sh
Expand Down
90 changes: 90 additions & 0 deletions ctime_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package times

import (
"os"
"syscall"
"time"
"unsafe"
)

type timespecEx struct {
atime
mtime
ctime
btime
}

// StatFile finds a Windows Timespec with ChangeTime.
func StatFile(file *os.File) (Timespec, error) {
var fileInfo fileBasicInfo
if err := getFileInformationByHandleEx(syscall.Handle(file.Fd()), &fileInfo); err != nil {
return nil, err
}

var t timespecEx
t.atime.v = time.Unix(0, fileInfo.LastAccessTime.Nanoseconds())
t.mtime.v = time.Unix(0, fileInfo.LastWriteTime.Nanoseconds())
t.ctime.v = time.Unix(0, fileInfo.ChangeTime.Nanoseconds())
t.btime.v = time.Unix(0, fileInfo.CreationTime.Nanoseconds())
return t, nil
}

func statEx(file string) (Timespec, error) {
if findProcErr != nil {
// fail fast, don't bother opening the file
return nil, findProcErr
}

f, err := os.Open(file)
if err != nil {
return nil, err
}
defer f.Close()
return StatFile(f)
}

var (
findProcErr error
procGetFileInformationByHandleEx *syscall.Proc
)

func init() {
var modkernel32 *syscall.DLL
if modkernel32, findProcErr = syscall.LoadDLL("kernel32.dll"); findProcErr == nil {
procGetFileInformationByHandleEx, findProcErr = modkernel32.FindProc("GetFileInformationByHandleEx")
}
}

// fileBasicInfo holds the C++ data for FileTimes.
//
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa364217(v=vs.85).aspx
type fileBasicInfo struct {
CreationTime syscall.Filetime
LastAccessTime syscall.Filetime
LastWriteTime syscall.Filetime
ChangeTime syscall.Filetime
FileAttributes uint32
_ uint32 // padding
}

type fileInformationClass int

const (
fileBasicInfoClass fileInformationClass = iota
)

func getFileInformationByHandleEx(handle syscall.Handle, data *fileBasicInfo) (err error) {
if findProcErr != nil {
return findProcErr
}

r1, _, e1 := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), 4, uintptr(handle), uintptr(fileBasicInfoClass), uintptr(unsafe.Pointer(data)), unsafe.Sizeof(*data), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
14 changes: 12 additions & 2 deletions stat.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ func Stat(name string) (Timespec, error) {
}

// Timespec provides access to file times.
// ChangeTime() panics unless times.HasChangeTime is true and
// BirthTime() panics unless times.HasBirthTime is true
// ChangeTime() panics unless HasChangeTime() is true and
// BirthTime() panics unless HasBirthTime() is true.
type Timespec interface {
ModTime() time.Time
AccessTime() time.Time
ChangeTime() time.Time
BirthTime() time.Time
HasChangeTime() bool
HasBirthTime() bool
}

type atime struct {
Expand All @@ -40,6 +42,8 @@ type ctime struct {
v time.Time
}

func (ctime) HasChangeTime() bool { return true }

func (c ctime) ChangeTime() time.Time { return c.v }

type mtime struct {
Expand All @@ -52,12 +56,18 @@ type btime struct {
v time.Time
}

func (btime) HasBirthTime() bool { return true }

func (b btime) BirthTime() time.Time { return b.v }

type noctime struct{}

func (noctime) HasChangeTime() bool { return false }

func (noctime) ChangeTime() time.Time { panic("ctime not available") }

type nobtime struct{}

func (nobtime) HasBirthTime() bool { return false }

func (nobtime) BirthTime() time.Time { panic("birthtime not available") }
4 changes: 2 additions & 2 deletions times_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ func TestGet(t *testing.T) {
if at.ModTime().Before(et) {
t.Errorf("expected mtime to be recent: got %v instead of ~%v", at.ModTime(), et)
}
if HasChangeTime && at.ChangeTime().Before(et) {
if at.HasChangeTime() && at.ChangeTime().Before(et) {
t.Errorf("expected ctime to be recent: got %v instead of ~%v", at.ChangeTime(), et)
}
if HasBirthTime && at.BirthTime().Before(et) {
if at.HasBirthTime() && at.BirthTime().Before(et) {
t.Errorf("expected btime to be recent: got %v instead of ~%v", at.BirthTime(), et)
}
}
Expand Down
7 changes: 6 additions & 1 deletion times_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ type timespec struct {
btime
}

func getTimespec(fi os.FileInfo) (t timespec) {
func getTimespec(fi os.FileInfo) Timespec {
if ts, err := statEx(fi.Name()); err == nil {
return ts
}

var t timespec
stat := fi.Sys().(*syscall.Win32FileAttributeData)
t.atime.v = time.Unix(0, stat.LastAccessTime.Nanoseconds())
t.mtime.v = time.Unix(0, stat.LastWriteTime.Nanoseconds())
Expand Down

0 comments on commit 3454a99

Please sign in to comment.