public
Last active

Windows USN Journal sample in Go based on Jeffrey Richter's superb MSDN Journal article. A work in progress, intended to provide similar API to go.fsevents.

  • Download Gist
fsevents_windows.go
Go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393
//
// File: fsevents_windows.go
// Date: October 29, 2013
// Author: Peter Krnjevic <pkrnjevic@gmail.com>, on the shoulders of many others
//
// This code sample is released into the Public Domain.
//
package fsevents
 
import (
// "bytes"
// "encoding/binary"
"fmt"
"reflect"
"syscall"
"unsafe"
)
 
import (
// "github.com/lxn/go-winapi"
"github.com/lxn/walk"
)
 
type (
WCHAR uint16
WORD uint16
DWORD uint32
DWORDLONG uint64
LONGLONG int64
USN int64
LARGE_INTEGER LONGLONG
)
 
type USN_JOURNAL_DATA struct {
UsnJournalID DWORDLONG
FirstUsn USN
NextUsn USN
LowestValidUsn USN
MaxUsn USN
MaximumSize DWORDLONG
AllocationDelta DWORDLONG
}
 
type READ_USN_JOURNAL_DATA struct {
StartUsn USN
ReasonMask DWORD
ReturnOnlyOnClose DWORD
Timeout DWORDLONG
BytesToWaitFor DWORDLONG
UsnJournalID DWORDLONG
}
 
type USN_RECORD struct {
RecordLength DWORD
MajorVersion WORD
MinorVersion WORD
FileReferenceNumber DWORDLONG
ParentFileReferenceNumber DWORDLONG
Usn USN
TimeStamp LARGE_INTEGER
Reason DWORD
SourceInfo DWORD
SecurityId DWORD
FileAttributes DWORD
FileNameLength WORD
FileNameOffset WORD
FileName [1]WCHAR
}
 
type MFT_ENUM_DATA struct {
StartFileReferenceNumber DWORDLONG
LowUsn USN
HighUsn USN
}
 
const (
FSCTL_ENUM_USN_DATA = 0x900B3
FSCTL_QUERY_USN_JOURNAL = 0x900F4
FSCTL_READ_USN_JOURNAL = 0x900BB
O_RDONLY = syscall.O_RDONLY
O_RDWR = syscall.O_RDWR
O_CREAT = syscall.O_CREAT
O_WRONLY = syscall.O_WRONLY
GENERIC_READ = syscall.GENERIC_READ
GENERIC_WRITE = syscall.GENERIC_WRITE
FILE_APPEND_DATA = syscall.FILE_APPEND_DATA
FILE_SHARE_READ = syscall.FILE_SHARE_READ
FILE_SHARE_WRITE = syscall.FILE_SHARE_WRITE
ERROR_FILE_NOT_FOUND = syscall.ERROR_FILE_NOT_FOUND
O_APPEND = syscall.O_APPEND
O_CLOEXEC = syscall.O_CLOEXEC
O_EXCL = syscall.O_EXCL
O_TRUNC = syscall.O_TRUNC
CREATE_ALWAYS = syscall.CREATE_ALWAYS
CREATE_NEW = syscall.CREATE_NEW
OPEN_ALWAYS = syscall.OPEN_ALWAYS
TRUNCATE_EXISTING = syscall.TRUNCATE_EXISTING
OPEN_EXISTING = syscall.OPEN_EXISTING
FILE_ATTRIBUTE_NORMAL = syscall.FILE_ATTRIBUTE_NORMAL
FILE_FLAG_BACKUP_SEMANTICS = syscall.FILE_FLAG_BACKUP_SEMANTICS
FILE_ATTRIBUTE_DIRECTORY = syscall.FILE_ATTRIBUTE_DIRECTORY
MAX_LONG_PATH = syscall.MAX_LONG_PATH
)
 
var (
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
procDeviceIoControl = modkernel32.NewProc("DeviceIoControl")
usnJournalData USN_JOURNAL_DATA
readUsnJournalData READ_USN_JOURNAL_DATA
cb int
)
 
func getPointer(i interface{}) (pointer, size uintptr) {
v := reflect.ValueOf(i)
switch k := v.Kind(); k {
case reflect.Ptr:
t := v.Elem().Type()
size = t.Size()
pointer = v.Pointer()
case reflect.Slice:
size = uintptr(v.Cap())
pointer = v.Pointer()
default:
fmt.Println("oops")
}
return
}
 
func DeviceIoControl(handle syscall.Handle, controlCode uint32, in interface{}, out interface{}, done *uint32) (err error) {
inPtr, inSize := getPointer(in)
outPtr, outSize := getPointer(out)
r1, _, e1 := syscall.Syscall9(procDeviceIoControl.Addr(), 8, uintptr(handle), uintptr(controlCode), inPtr, uintptr(inSize), outPtr, uintptr(outSize), uintptr(unsafe.Pointer(done)), uintptr(0), 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
 
func makeInheritSa() *syscall.SecurityAttributes {
var sa syscall.SecurityAttributes
sa.Length = uint32(unsafe.Sizeof(sa))
sa.InheritHandle = 1
return &sa
}
 
// Need a custom Open to work with backup_semantics
func open(path string, mode int, attrs uint32) (fd syscall.Handle, err error) {
if len(path) == 0 {
return syscall.InvalidHandle, ERROR_FILE_NOT_FOUND
}
pathp, err := syscall.UTF16PtrFromString(path)
if err != nil {
return syscall.InvalidHandle, err
}
var access uint32
switch mode & (O_RDONLY | O_WRONLY | O_RDWR) {
case O_RDONLY:
access = GENERIC_READ
case O_WRONLY:
access = GENERIC_WRITE
case O_RDWR:
access = GENERIC_READ | GENERIC_WRITE
}
if mode&O_CREAT != 0 {
access |= GENERIC_WRITE
}
if mode&O_APPEND != 0 {
access &^= GENERIC_WRITE
access |= FILE_APPEND_DATA
}
sharemode := uint32(FILE_SHARE_READ | FILE_SHARE_WRITE)
var sa *syscall.SecurityAttributes
if mode&O_CLOEXEC == 0 {
sa = makeInheritSa()
}
var createmode uint32
switch {
case mode&(O_CREAT|O_EXCL) == (O_CREAT | O_EXCL):
createmode = CREATE_NEW
case mode&(O_CREAT|O_TRUNC) == (O_CREAT | O_TRUNC):
createmode = CREATE_ALWAYS
case mode&O_CREAT == O_CREAT:
createmode = OPEN_ALWAYS
case mode&O_TRUNC == O_TRUNC:
createmode = TRUNCATE_EXISTING
default:
createmode = OPEN_EXISTING
}
h, e := syscall.CreateFile(pathp, access, sharemode, sa, createmode, attrs, 0)
return h, e
}
 
func getUsnJournalReasonString(reason DWORD) (s string) {
var reasons = []string{
"DataOverwrite", // 0x00000001
"DataExtend", // 0x00000002
"DataTruncation", // 0x00000004
"0x00000008", // 0x00000008
"NamedDataOverwrite", // 0x00000010
"NamedDataExtend", // 0x00000020
"NamedDataTruncation", // 0x00000040
"0x00000080", // 0x00000080
"FileCreate", // 0x00000100
"FileDelete", // 0x00000200
"PropertyChange", // 0x00000400
"SecurityChange", // 0x00000800
"RenameOldName", // 0x00001000
"RenameNewName", // 0x00002000
"IndexableChange", // 0x00004000
"BasicInfoChange", // 0x00008000
"HardLinkChange", // 0x00010000
"CompressionChange", // 0x00020000
"EncryptionChange", // 0x00040000
"ObjectIdChange", // 0x00080000
"ReparsePointChange", // 0x00100000
"StreamChange", // 0x00200000
"0x00400000", // 0x00400000
"0x00800000", // 0x00800000
"0x01000000", // 0x01000000
"0x02000000", // 0x02000000
"0x04000000", // 0x04000000
"0x08000000", // 0x08000000
"0x10000000", // 0x10000000
"0x20000000", // 0x20000000
"0x40000000", // 0x40000000
"*Close*", // 0x80000000
}
for i := 0; reason != 0; {
if reason&1 == 1 {
s = s + ", " + reasons[i]
}
reason >>= 1
i++
}
return
}
 
// Query usn journal data
func queryUsnJournal(fd syscall.Handle) (ujd USN_JOURNAL_DATA, done uint32, err error) {
err = DeviceIoControl(fd, FSCTL_QUERY_USN_JOURNAL, []byte{}, &ujd, &done)
return
}
 
func readUsnJournal(fd syscall.Handle, rujd *READ_USN_JOURNAL_DATA) (data []byte, done uint32, err error) {
data = make([]byte, 0x1000)
err = DeviceIoControl(fd, FSCTL_READ_USN_JOURNAL, rujd, data, &done)
return
}
 
func enumUsnData(fd syscall.Handle, med *MFT_ENUM_DATA) (data []byte, done uint32, err error) {
data = make([]byte, 0x10000)
err = DeviceIoControl(fd, FSCTL_ENUM_USN_DATA, med, data, &done)
return
}
 
type folderEntry struct {
name string
parent DWORDLONG
}
 
// Build map of folder names using MFT (based on PopulateMethod2)
func buildFolderMap() (folders map[DWORDLONG]folderEntry) {
folders = make(map[DWORDLONG]folderEntry)
drives, _ := walk.DriveNames()
fmt.Println(drives)
 
fd, err := open("\\\\.\\C:", syscall.O_RDONLY, FILE_ATTRIBUTE_NORMAL)
fmt.Println(fd, err)
 
ujd, _, err := queryUsnJournal(fd)
fmt.Printf("ujd = %v\n", ujd)
 
// Open directory to read MFT and store off FRN (file reference numbers)
dir, err := open("C:\\", syscall.O_RDONLY, FILE_FLAG_BACKUP_SEMANTICS)
fmt.Println("dir,err", dir, err)
 
var fi syscall.ByHandleFileInformation
err = syscall.GetFileInformationByHandle(dir, &fi)
err = syscall.CloseHandle(dir)
fmt.Println("err, fi", err, fi)
 
indexRoot := fi.FileSizeHigh<<32 | fi.FileSizeLow
_ = indexRoot
 
med := MFT_ENUM_DATA{0, 0, ujd.NextUsn}
 
for {
data, done, err := enumUsnData(fd, &med)
if err != nil {
fmt.Println(err)
}
if done == 0 {
return
}
 
var usn USN = *(*USN)(unsafe.Pointer(&data[0]))
// fmt.Println("usn", usn)
 
var ur *USN_RECORD
for i := unsafe.Sizeof(usn); i < uintptr(done); i += uintptr(ur.RecordLength) {
ur = (*USN_RECORD)(unsafe.Pointer(&data[i]))
if ur.FileAttributes&FILE_ATTRIBUTE_DIRECTORY != 0 {
nameLength := uintptr(ur.FileNameLength) / unsafe.Sizeof(ur.FileName[0])
fnp := unsafe.Pointer(&data[i+uintptr(ur.FileNameOffset)])
fnUtf := (*[10000]uint16)(fnp)[:nameLength]
fn := syscall.UTF16ToString(fnUtf)
(*reflect.SliceHeader)(unsafe.Pointer(&fn)).Cap = int(nameLength)
// fmt.Println("len", ur.FileNameLength, ur.FileNameOffset, "fn", fn)
folders[ur.FileReferenceNumber] = folderEntry{fn, ur.ParentFileReferenceNumber}
}
}
med.StartFileReferenceNumber = DWORDLONG(usn)
}
}
 
// Assemble the path by looking up parent pointers in the folders map
func getFullPath(folders map[DWORDLONG]folderEntry, parent DWORDLONG) (name string) {
for parent != 0 {
fe := folders[parent]
name = fe.name + "/" + name
parent = fe.parent
}
return
}
 
func processAvailableRecords(ch chan *USN_RECORD, folders map[DWORDLONG]folderEntry) {
drives, _ := walk.DriveNames()
fmt.Println(drives)
 
fd, err := open("\\\\.\\C:", syscall.O_RDONLY, FILE_ATTRIBUTE_NORMAL)
fmt.Println("fd, err", fd, err)
 
ujd, _, err := queryUsnJournal(fd)
fmt.Printf("ujd = %v\n", ujd)
 
rujd := READ_USN_JOURNAL_DATA{ujd.FirstUsn, 0xFFFFFFFF, 0, 0, 1, ujd.UsnJournalID}
 
for {
var usn USN
data, done, err := readUsnJournal(fd, &rujd)
if err != nil || done <= uint32(unsafe.Sizeof(usn)) {
return
}
 
usn = *(*USN)(unsafe.Pointer(&data[0]))
fmt.Println("usn", usn)
 
var ur *USN_RECORD
for i := unsafe.Sizeof(usn); i < uintptr(done); i += uintptr(ur.RecordLength) {
ur = (*USN_RECORD)(unsafe.Pointer(&data[i]))
if ur.FileAttributes&FILE_ATTRIBUTE_DIRECTORY != 0 {
nameLength := uintptr(ur.FileNameLength) / unsafe.Sizeof(ur.FileName[0])
fnp := unsafe.Pointer(&data[i+uintptr(ur.FileNameOffset)])
fn := (*[10000]uint16)(fnp)[:nameLength]
(*reflect.SliceHeader)(unsafe.Pointer(&fn)).Cap = int(nameLength)
// fmt.Println("len", ur.FileNameLength, ur.FileNameOffset, "fn", getFullPath(folders, ur.ParentFileReferenceNumber), syscall.UTF16ToString(fn), getUsnJournalReasonString(ur.Reason))
ch <- ur
}
}
rujd.StartUsn = usn
if usn == 0 {
return
}
}
}
 
// Main
func main() {
folders := buildFolderMap()
ch := make(chan *USN_RECORD)
go processAvailableRecords(ch, folders)
for {
ur := <-ch
fmt.Println("len", ur.FileNameLength, ur.FileNameOffset, "fn", getFullPath(folders, ur.ParentFileReferenceNumber), getUsnJournalReasonString(ur.Reason))
// fmt.Println(r)
}
}
 
type PathEvent struct {
Path string
Flags uint32
Eid uint64
}
 
func WatchPaths(paths []string, eid int64) chan []PathEvent {
}
 
func Unwatch(ch chan []PathEvent) {
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.