package usn import ( "b612.me/stario" "b612.me/win32api" "encoding/binary" "fmt" "os" "path/filepath" "reflect" "strings" "syscall" "unsafe" ) type DiskInfo struct { Driver string Name string Format string SerialNumber uint32 } func normalizeDiskName(diskName string) (string, error) { name := strings.TrimSpace(strings.ReplaceAll(diskName, "/", "\\")) if name == "" { return "", fmt.Errorf("empty disk name") } volume := filepath.VolumeName(name) if len(volume) == 2 && volume[1] == ':' { return strings.ToUpper(volume[:1]) + ":\\", nil } if len(name) >= 2 && name[1] == ':' { return strings.ToUpper(name[:1]) + ":\\", nil } return "", fmt.Errorf("invalid disk name: %q", diskName) } func volumeDevicePath(diskName string) (string, error) { normalized, err := normalizeDiskName(diskName) if err != nil { return "", err } return "\\\\.\\" + strings.TrimSuffix(normalized, "\\"), nil } func ListDrivers() ([]string, error) { drivers := make([]string, 0, 26) buf := make([]uint16, 255) err := win32api.GetLogicalDriveStringsW(win32api.DWORD(len(buf)), &buf[0]) if err != nil { return drivers, err } var driver []rune for _, v := range buf { if v != 0 { driver = append(driver, rune(uint8(v))) if v == 92 { drivers = append(drivers, string(driver)) driver = []rune{} } } } return drivers, nil } func GetDiskInfo(disk string) (DiskInfo, error) { format := make([]rune, 0, 12) name := make([]rune, 0, 128) ptr, _ := syscall.UTF16PtrFromString(disk) var lpVolumeNameBuffer = make([]uint16, syscall.MAX_PATH+1) var nVolumeNameSize = win32api.DWORD(len(lpVolumeNameBuffer)) var lpVolumeSerialNumber uint32 var lpMaximumComponentLength uint32 var lpFileSystemFlags uint32 var lpFileSystemNameBuffer = make([]uint16, 255) var nFileSystemNameSize uint32 = syscall.MAX_PATH + 1 err := win32api.GetVolumeInformationW(ptr, &lpVolumeNameBuffer[0], nVolumeNameSize, &lpVolumeSerialNumber, &lpMaximumComponentLength, &lpFileSystemFlags, &lpFileSystemNameBuffer[0], win32api.DWORD(nFileSystemNameSize)) for _, v := range lpFileSystemNameBuffer { if v != 0 { format = append(format, rune(v)) } } for _, v := range lpVolumeNameBuffer { if v != 0 { name = append(name, rune(v)) } } return DiskInfo{ SerialNumber: lpVolumeSerialNumber, Driver: disk, Name: string(name), Format: string(format), }, err } func DeviceIoControl(handle syscall.Handle, controlCode uint32, in interface{}, out interface{}, done *uint32) (err error) { inPtr, inSize, err := getPointer(in) if err != nil { return err } outPtr, outSize, err := getPointer(out) if err != nil { return err } //_,err = syscall.Syscall9(procDeviceIoControl.Addr(), 8, uintptr(handle), uintptr(controlCode), inPtr, uintptr(inSize), outPtr, uintptr(outSize), uintptr(unsafe.Pointer(done)), uintptr(0), 0) _, err = win32api.DeviceIoControlPtr(win32api.HANDLE(handle), win32api.DWORD(controlCode), inPtr, win32api.DWORD(inSize), outPtr, win32api.DWORD(outSize), done, nil) return } func getPointer(i interface{}) (pointer uintptr, size uintptr, err error) { if i == nil { return 0, 0, nil } v := reflect.ValueOf(i) switch k := v.Kind(); k { case reflect.Ptr: if v.IsNil() { return 0, 0, nil } t := v.Elem().Type() size = t.Size() pointer = v.Pointer() case reflect.Slice: if v.Len() == 0 { return 0, 0, nil } size = uintptr(v.Len()) * v.Type().Elem().Size() pointer = v.Pointer() default: return 0, 0, fmt.Errorf("unsupported DeviceIoControl buffer type %T", i) } return pointer, size, nil } // Need a custom Open to work with backup_semantics func CreateFile(path string, mode int, attrs uint32) (fd syscall.Handle, err error) { if len(path) == 0 { return syscall.InvalidHandle, win32api.ERROR_FILE_NOT_FOUND } pathp, err := syscall.UTF16PtrFromString(path) if err != nil { return syscall.InvalidHandle, err } var access uint32 switch mode & (win32api.O_RDONLY | win32api.O_WRONLY | win32api.O_RDWR) { case win32api.O_RDONLY: access = win32api.GENERIC_READ case win32api.O_WRONLY: access = win32api.GENERIC_WRITE case win32api.O_RDWR: access = win32api.GENERIC_READ | win32api.GENERIC_WRITE } if mode&win32api.O_CREAT != 0 { access |= win32api.GENERIC_WRITE } if mode&win32api.O_APPEND != 0 { access &^= win32api.GENERIC_WRITE access |= win32api.FILE_APPEND_DATA } sharemode := uint32(win32api.FILE_SHARE_READ | win32api.FILE_SHARE_WRITE) var sa *syscall.SecurityAttributes if mode&win32api.O_CLOEXEC == 0 { sa = makeInheritSa() } var createmode uint32 switch { case mode&(win32api.O_CREAT|win32api.O_EXCL) == (win32api.O_CREAT | win32api.O_EXCL): createmode = win32api.CREATE_NEW case mode&(win32api.O_CREAT|win32api.O_TRUNC) == (win32api.O_CREAT | win32api.O_TRUNC): createmode = win32api.CREATE_ALWAYS case mode&win32api.O_CREAT == win32api.O_CREAT: createmode = win32api.OPEN_ALWAYS case mode&win32api.O_TRUNC == win32api.O_TRUNC: createmode = win32api.TRUNCATE_EXISTING default: createmode = win32api.OPEN_EXISTING } h, e := syscall.CreateFile(pathp, access, sharemode, sa, createmode, attrs, 0) return h, e } func makeInheritSa() *syscall.SecurityAttributes { var sa syscall.SecurityAttributes sa.Length = uint32(unsafe.Sizeof(sa)) sa.InheritHandle = 1 return &sa } // Query usn journal data func queryUsnJournal(fd syscall.Handle) (ujd win32api.USN_JOURNAL_DATA, done uint32, err error) { err = DeviceIoControl(fd, win32api.FSCTL_QUERY_USN_JOURNAL, []byte{}, &ujd, &done) return } func readUsnJournal(fd syscall.Handle, rujd *win32api.READ_USN_JOURNAL_DATA) (data []byte, done uint32, err error) { data = make([]byte, 0x1000) err = DeviceIoControl(fd, win32api.FSCTL_READ_USN_JOURNAL, rujd, data, &done) return } func enumUsnData(fd syscall.Handle, med *win32api.MFT_ENUM_DATA) (data []byte, done uint32, err error) { data = make([]byte, 0x10000) err = DeviceIoControl(fd, win32api.FSCTL_ENUM_USN_DATA, med, data, &done) return } type FileEntry struct { Name string Parent win32api.DWORDLONG Type uint8 } type FileMonitor struct { Name string Self win32api.DWORDLONG Parent win32api.DWORDLONG Type uint8 Reason string } var normalizePathForUSN = normalizeExistingLongPath const ( usnBufferHeaderSize = int(unsafe.Sizeof(win32api.USN(0))) usnRecordMinSize = int(unsafe.Offsetof(win32api.USN_RECORD{}.FileName)) usnRecordOffsetFileReference = int(unsafe.Offsetof(win32api.USN_RECORD{}.FileReferenceNumber)) usnRecordOffsetParentReference = int(unsafe.Offsetof(win32api.USN_RECORD{}.ParentFileReferenceNumber)) usnRecordOffsetReason = int(unsafe.Offsetof(win32api.USN_RECORD{}.Reason)) usnRecordOffsetFileAttributes = int(unsafe.Offsetof(win32api.USN_RECORD{}.FileAttributes)) usnRecordOffsetFileNameLength = int(unsafe.Offsetof(win32api.USN_RECORD{}.FileNameLength)) usnRecordOffsetFileNameOffset = int(unsafe.Offsetof(win32api.USN_RECORD{}.FileNameOffset)) ) type usnRecordData struct { FileReferenceNumber win32api.DWORDLONG ParentFileReferenceNumber win32api.DWORDLONG Reason win32api.DWORD FileAttributes win32api.DWORD FileName string } func parseUSNOutput(data []byte, done uint32, fn func(usnRecordData) error) (uint64, error) { if fn == nil { return 0, fmt.Errorf("nil USN record callback") } if done == 0 { return 0, nil } if done < uint32(usnBufferHeaderSize) { return 0, fmt.Errorf("USN output too short: %d", done) } if int(done) > len(data) { return 0, fmt.Errorf("USN output length %d exceeds buffer %d", done, len(data)) } next := binary.LittleEndian.Uint64(data[:usnBufferHeaderSize]) for offset := usnBufferHeaderSize; offset < int(done); { remaining := int(done) - offset if remaining < usnRecordMinSize { return next, fmt.Errorf("USN record header truncated: %d bytes remain", remaining) } recordLength := int(binary.LittleEndian.Uint32(data[offset:])) if recordLength < usnRecordMinSize { return next, fmt.Errorf("invalid USN record length %d", recordLength) } if recordLength > remaining { return next, fmt.Errorf("USN record length %d exceeds remaining %d", recordLength, remaining) } record := data[offset : offset+recordLength] nameLength := int(binary.LittleEndian.Uint16(record[usnRecordOffsetFileNameLength:])) nameOffset := int(binary.LittleEndian.Uint16(record[usnRecordOffsetFileNameOffset:])) if nameLength < 0 || nameLength%2 != 0 { return next, fmt.Errorf("invalid USN file name length %d", nameLength) } if nameOffset < usnRecordMinSize || nameOffset > recordLength { return next, fmt.Errorf("invalid USN file name offset %d", nameOffset) } if nameOffset+nameLength > recordLength { return next, fmt.Errorf("USN file name exceeds record boundary: offset=%d length=%d record=%d", nameOffset, nameLength, recordLength) } name, err := decodeUTF16Bytes(record[nameOffset : nameOffset+nameLength]) if err != nil { return next, err } entry := usnRecordData{ FileReferenceNumber: win32api.DWORDLONG(binary.LittleEndian.Uint64(record[usnRecordOffsetFileReference:])), ParentFileReferenceNumber: win32api.DWORDLONG(binary.LittleEndian.Uint64(record[usnRecordOffsetParentReference:])), Reason: win32api.DWORD(binary.LittleEndian.Uint32(record[usnRecordOffsetReason:])), FileAttributes: win32api.DWORD(binary.LittleEndian.Uint32(record[usnRecordOffsetFileAttributes:])), FileName: name, } if err := fn(entry); err != nil { return next, err } offset += recordLength } return next, nil } func decodeUTF16Bytes(data []byte) (string, error) { if len(data)%2 != 0 { return "", fmt.Errorf("UTF-16 byte length must be even, got %d", len(data)) } chars := make([]uint16, len(data)/2) for i := range chars { chars[i] = binary.LittleEndian.Uint16(data[i*2:]) } return syscall.UTF16ToString(chars), nil } func fileEntryFromUSNRecord(record usnRecordData) FileEntry { typed := uint8(0) if record.FileAttributes&win32api.FILE_ATTRIBUTE_DIRECTORY != 0 { typed = 1 } return FileEntry{ Name: record.FileName, Parent: record.ParentFileReferenceNumber, Type: typed, } } func shouldPreferUSNFileName(current string, candidate string) bool { if candidate == "" { return false } if current == "" { return true } if strings.EqualFold(current, candidate) { return false } currentShort := strings.Contains(current, "~") candidateShort := strings.Contains(candidate, "~") if currentShort != candidateShort { return currentShort && !candidateShort } return len(candidate) > len(current) } func mergeUSNFileEntry(current FileEntry, candidate FileEntry) FileEntry { if current.Name == "" && current.Parent == 0 && current.Type == 0 { return candidate } merged := current if shouldPreferUSNFileName(merged.Name, candidate.Name) { merged.Name = candidate.Name } if candidate.Name != "" && !strings.EqualFold(merged.Name, candidate.Name) && !shouldPreferUSNFileName(candidate.Name, merged.Name) { merged.Name = candidate.Name } if merged.Name == "" { merged.Name = candidate.Name } if candidate.Parent != 0 { merged.Parent = candidate.Parent } if candidate.Type == 1 { merged.Type = 1 } return merged } func needPathCanonicalNameOverlay(fileMap map[win32api.DWORDLONG]FileEntry) bool { for _, entry := range fileMap { if strings.Contains(entry.Name, "~") { return true } } return false } func windowsBaseName(path string) string { trimmed := strings.TrimRight(path, `\/`) if trimmed == "" { return "" } last := strings.LastIndexAny(trimmed, `\/`) if last < 0 { return trimmed } return trimmed[last+1:] } func applyPathCanonicalNames(driver string, fileMap map[win32api.DWORDLONG]FileEntry) { if len(fileMap) == 0 || !needPathCanonicalNameOverlay(fileMap) { return } for id, entry := range fileMap { if !strings.Contains(entry.Name, "~") { continue } path := buildUSNPath(driver, fileMap, id) normalized := normalizePathForUSN(path) base := windowsBaseName(normalized) if base == "" { continue } entry.Name = base fileMap[id] = entry } } func buildUSNFileMap(driver string) (map[win32api.DWORDLONG]FileEntry, error) { fileMap := make(map[win32api.DWORDLONG]FileEntry) pDriver, err := volumeDevicePath(driver) if err != nil { return fileMap, err } fd, err := CreateFile(pDriver, syscall.O_RDONLY, win32api.FILE_ATTRIBUTE_NORMAL) if err != nil { return fileMap, err } defer syscall.Close(fd) ujd, _, err := queryUsnJournal(fd) if err != nil { return fileMap, err } med := win32api.MFT_ENUM_DATA{0, 0, ujd.NextUsn} for { data, done, err := enumUsnData(fd, &med) if err != nil && done != 0 { return fileMap, err } if done == 0 { applyPathCanonicalNames(driver, fileMap) return fileMap, nil } nextRef, err := parseUSNOutput(data, done, func(record usnRecordData) error { fileMap[record.FileReferenceNumber] = mergeUSNFileEntry(fileMap[record.FileReferenceNumber], fileEntryFromUSNRecord(record)) return nil }) if err != nil { return fileMap, err } med.StartFileReferenceNumber = win32api.DWORDLONG(nextRef) } } func filterUSNFileMap(fileMap map[win32api.DWORDLONG]FileEntry, searchFn func(string, bool) bool) map[win32api.DWORDLONG]FileEntry { if searchFn == nil { return fileMap } filtered := make(map[win32api.DWORDLONG]FileEntry) for id, entry := range fileMap { if entry.Type == 1 || searchFn(entry.Name, entry.Type == 1) { filtered[id] = entry } } return filtered } func ListUsnFile(driver string) (map[win32api.DWORDLONG]FileEntry, error) { return buildUSNFileMap(driver) } func ListUsnFileFn(driver string, searchFn func(string, bool) bool) (map[win32api.DWORDLONG]FileEntry, error) { fileMap, err := buildUSNFileMap(driver) if err != nil { return fileMap, err } return filterUSNFileMap(fileMap, searchFn), nil } func buildUSNPath(diskName string, fileMap map[win32api.DWORDLONG]FileEntry, id win32api.DWORDLONG) (name string) { normalized, err := normalizeDiskName(diskName) if err != nil { return "" } for id != 0 { fe := fileMap[id] if id == fe.Parent { name = "\\" + name break } if name == "" { name = fe.Name } else { name = fe.Name + "\\" + name } id = fe.Parent } name = strings.TrimSuffix(normalized, "\\") + name return } func normalizeExistingLongPath(path string) string { if path == "" { return path } if normalized, ok := getLongPathName(path); ok { return trimLongPathPrefix(normalized) } longPath := fixLongPath(path) if longPath == path { return path } if normalized, ok := getLongPathName(longPath); ok { return trimLongPathPrefix(normalized) } return path } func getLongPathName(path string) (string, bool) { pathp, err := syscall.UTF16PtrFromString(path) if err != nil { return "", false } size := len(path) + 1 if size < syscall.MAX_PATH { size = syscall.MAX_PATH } for { buf := make([]uint16, size) n, err := syscall.GetLongPathName(pathp, &buf[0], uint32(len(buf))) if err != nil || n == 0 { return "", false } if int(n) < len(buf) { return syscall.UTF16ToString(buf[:n]), true } size = int(n) + 1 } } func trimLongPathPrefix(path string) string { switch { case strings.HasPrefix(path, `\\?\UNC\`): return `\\` + path[len(`\\?\UNC\`):] case strings.HasPrefix(path, `\\?\`): return path[len(`\\?\`):] default: return path } } func GetFullUsnPath(diskName string, fileMap map[win32api.DWORDLONG]FileEntry, id win32api.DWORDLONG) string { return normalizeExistingLongPath(buildUSNPath(diskName, fileMap, id)) } func GetFullUsnPathEntry(diskName string, fileMap map[win32api.DWORDLONG]FileEntry, en FileMonitor) string { fileMap[en.Self] = mergeUSNFileEntry(fileMap[en.Self], FileEntry{ Name: en.Name, Parent: en.Parent, Type: en.Type, }) return normalizeExistingLongPath(buildUSNPath(diskName, fileMap, en.Self)) } func fileStatFromHandle(fd syscall.Handle, name string, path string) (FileStat, error) { var info syscall.ByHandleFileInformation if err := syscall.GetFileInformationByHandle(fd, &info); err != nil { return FileStat{}, err } stat := newFileStatFromInformation(&info, name, path) fileType, err := syscall.GetFileType(fd) if err == nil { stat.filetype = fileType } return stat, nil } func fileStatFromPath(name string, path string) (FileStat, error) { fileInfo, err := os.Stat(path) if err != nil { return FileStat{}, err } data, ok := fileInfo.Sys().(*syscall.Win32FileAttributeData) if !ok { return FileStat{}, fmt.Errorf("unexpected file info payload %T", fileInfo.Sys()) } return FileStat{ name: name, path: path, FileAttributes: data.FileAttributes, CreationTime: data.CreationTime, LastAccessTime: data.LastAccessTime, LastWriteTime: data.LastWriteTime, FileSizeHigh: data.FileSizeHigh, FileSizeLow: data.FileSizeLow, }, nil } func fileOpenAttributes(entryType uint8) uint32 { if entryType == 1 { return win32api.FILE_FLAG_BACKUP_SEMANTICS } return win32api.FILE_ATTRIBUTE_NORMAL } func fileStatFromIDWithfd(volumeHandle syscall.Handle, id win32api.DWORDLONG, name string, path string, entryType uint8) (FileStat, error) { fileHandle, err := OpenFileByIdWithfd(volumeHandle, id, syscall.O_RDONLY, fileOpenAttributes(entryType)) if err != nil { return FileStat{}, err } defer syscall.Close(fileHandle) return fileStatFromHandle(fileHandle, name, path) } func fileStatForEntryWithfd(volumeHandle syscall.Handle, diskName string, data map[win32api.DWORDLONG]FileEntry, id win32api.DWORDLONG, entry FileEntry) (FileStat, error) { path := GetFullUsnPath(diskName, data, id) stat, err := fileStatFromIDWithfd(volumeHandle, id, entry.Name, path, entry.Type) if err == nil { return stat, nil } fallback, fallbackErr := fileStatFromPath(entry.Name, path) if fallbackErr == nil { return fallback, nil } return FileStat{}, fmt.Errorf("stat by id: %v; stat by path: %w", err, fallbackErr) } func fileStatForEntryByPath(diskName string, data map[win32api.DWORDLONG]FileEntry, id win32api.DWORDLONG, entry FileEntry) (FileStat, error) { path := GetFullUsnPath(diskName, data, id) return fileStatFromPath(entry.Name, path) } const ( ALL_FILES = iota ONLY_FOLDER NO_FOLDER ) func ListNTFSUsnDriverFilesFn(diskName string, searchFn func(string, bool) bool) ([]string, error) { var result []string data, err := ListUsnFileFn(diskName, searchFn) if err != nil { return result, err } return listNTFSUsnDriverFiles(diskName, searchFn, data) } func ListNTFSUsnDriverFiles(diskName string, folder uint8) ([]string, error) { var result []string data, err := ListUsnFile(diskName) if err != nil { return result, err } return listNTFSUsnDriverFiles(diskName, func(name string, tp bool) bool { if !tp && folder == ONLY_FOLDER { return false } if tp && folder == NO_FOLDER { return false } return true }, data) } func listNTFSUsnDriverFiles(diskName string, fn func(string, bool) bool, data map[win32api.DWORDLONG]FileEntry) ([]string, error) { result := make([]string, len(data)) i := 0 for k, v := range data { if !fn(v.Name, v.Type == 1) { continue } name := GetFullUsnPath(diskName, data, k) result[i] = name i++ } return result[:i], nil } func ListNTFSUsnDriverInfoFn(diskName string, searchFn func(string, bool) bool) ([]FileStat, error) { data, err := ListUsnFileFn(diskName, searchFn) if err != nil { return nil, err } return listNTFSUsnDriverInfo(diskName, searchFn, data) } func ListNTFSUsnDriverInfo(diskName string, folder uint8) ([]FileStat, error) { data, err := ListUsnFile(diskName) if err != nil { return nil, err } return listNTFSUsnDriverInfo(diskName, func(name string, tp bool) bool { if !tp && folder == ONLY_FOLDER { return false } if tp && folder == NO_FOLDER { return false } return true }, data) } type fileStatFetcher func(id win32api.DWORDLONG, entry FileEntry) (FileStat, error) func collectUSNFileStats(data map[win32api.DWORDLONG]FileEntry, fn func(string, bool) bool, fetch fileStatFetcher) []FileStat { if fetch == nil { return []FileStat{} } if fn == nil { fn = func(string, bool) bool { return true } } resultCh := make(chan FileStat, len(data)) wg := stario.NewWaitGroup(100) for id, entry := range data { if !fn(entry.Name, entry.Type == 1) { continue } wg.Add(1) go func(id win32api.DWORDLONG, entry FileEntry) { defer wg.Done() stat, err := fetch(id, entry) if err != nil { return } resultCh <- stat }(id, entry) } wg.Wait() close(resultCh) result := make([]FileStat, 0, len(data)) for stat := range resultCh { result = append(result, stat) } return result } func listNTFSUsnDriverInfo(diskName string, fn func(string, bool) bool, data map[win32api.DWORDLONG]FileEntry) ([]FileStat, error) { pDriver, err := volumeDevicePath(diskName) if err != nil { return nil, err } fd, err := CreateFile(pDriver, syscall.O_RDONLY, win32api.FILE_ATTRIBUTE_NORMAL) useByID := err == nil if useByID { defer syscall.Close(fd) } var fetch fileStatFetcher if useByID { fetch = func(id win32api.DWORDLONG, entry FileEntry) (FileStat, error) { return fileStatForEntryWithfd(fd, diskName, data, id, entry) } } else { fetch = func(id win32api.DWORDLONG, entry FileEntry) (FileStat, error) { return fileStatForEntryByPath(diskName, data, id, entry) } } return collectUSNFileStats(data, fn, fetch), nil } func USNReasonString(reason win32api.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; i++ { if i >= len(reasons) { if s == "" { return fmt.Sprintf("0x%08X", uint32(reason)<>= 1 } return } func getUsnJournalReasonString(reason win32api.DWORD) string { return USNReasonString(reason) } func MonitorUsnChange(driver string, rec chan FileMonitor) error { pDriver, err := volumeDevicePath(driver) if err != nil { return err } fd, err := CreateFile(pDriver, syscall.O_RDONLY, win32api.FILE_ATTRIBUTE_NORMAL) if err != nil { return err } defer syscall.Close(fd) ujd, _, err := queryUsnJournal(fd) if err != nil { return err } rujd := win32api.READ_USN_JOURNAL_DATA{ujd.NextUsn, 0xFFFFFFFF, 0, 0, 1, ujd.UsnJournalID} cache := make(map[win32api.DWORDLONG]FileEntry) for { data, done, err := readUsnJournal(fd, &rujd) if err != nil || done <= uint32(usnBufferHeaderSize) { return err } nextUsn, err := parseUSNOutput(data, done, func(record usnRecordData) error { entry := mergeUSNFileEntry(cache[record.FileReferenceNumber], fileEntryFromUSNRecord(record)) cache[record.FileReferenceNumber] = entry rec <- FileMonitor{Name: entry.Name, Parent: entry.Parent, Type: entry.Type, Self: record.FileReferenceNumber, Reason: getUsnJournalReasonString(record.Reason)} return nil }) if err != nil { return err } rujd.StartUsn = win32api.USN(nextUsn) if nextUsn == 0 { return nil } } } func GetUsnFileInfo(diskName string, fileMap map[win32api.DWORDLONG]FileEntry, id win32api.DWORDLONG) (FileStat, error) { pDriver, err := volumeDevicePath(diskName) if err != nil { return FileStat{}, err } volumeHandle, err := CreateFile(pDriver, syscall.O_RDONLY, win32api.FILE_ATTRIBUTE_NORMAL) if err != nil { return fileStatForEntryByPath(diskName, fileMap, id, fileMap[id]) } defer syscall.Close(volumeHandle) return fileStatForEntryWithfd(volumeHandle, diskName, fileMap, id, fileMap[id]) } // Need a custom Open to work with backup_semantics func OpenFileById(diskName string, id win32api.DWORDLONG, mode int, attrs uint32) (syscall.Handle, error) { pDriver, err := volumeDevicePath(diskName) if err != nil { return syscall.InvalidHandle, err } fd, err := CreateFile(pDriver, syscall.O_RDONLY, win32api.FILE_ATTRIBUTE_NORMAL) if err != nil { return syscall.InvalidHandle, err } defer syscall.Close(fd) return OpenFileByIdWithfd(fd, id, mode, attrs) } func OpenFileByIdWithfd(fd syscall.Handle, id win32api.DWORDLONG, mode int, attrs uint32) (syscall.Handle, error) { var access uint32 switch mode & (win32api.O_RDONLY | win32api.O_WRONLY | win32api.O_RDWR) { case win32api.O_RDONLY: access = win32api.GENERIC_READ case win32api.O_WRONLY: access = win32api.GENERIC_WRITE case win32api.O_RDWR: access = win32api.GENERIC_READ | win32api.GENERIC_WRITE } if mode&win32api.O_CREAT != 0 { access |= win32api.GENERIC_WRITE } if mode&win32api.O_APPEND != 0 { access &^= win32api.GENERIC_WRITE access |= win32api.FILE_APPEND_DATA } sharemode := uint32(win32api.FILE_SHARE_READ | win32api.FILE_SHARE_WRITE) var sa *syscall.SecurityAttributes if mode&win32api.O_CLOEXEC == 0 { sa = makeInheritSa() } fid := win32api.FILE_ID_DESCRIPTOR{ DwSize: win32api.DWORD(unsafe.Sizeof(win32api.FILE_ID_DESCRIPTOR{})), Type: win32api.FileIdType, FileId: id, } h, e := win32api.OpenFileById(win32api.HANDLE(fd), &fid, win32api.DWORD(access), win32api.DWORD(sharemode), sa, win32api.DWORD(attrs)) return syscall.Handle(h), e }