2022-03-09 13:42:01 +08:00
|
|
|
package usn
|
2021-11-15 17:25:04 +08:00
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"b612.me/stario"
|
|
|
|
|
"b612.me/win32api"
|
2026-06-09 15:59:31 +08:00
|
|
|
"encoding/binary"
|
2021-11-15 17:25:04 +08:00
|
|
|
"fmt"
|
2021-11-16 15:58:13 +08:00
|
|
|
"os"
|
2026-06-09 15:59:31 +08:00
|
|
|
"path/filepath"
|
2021-11-15 17:25:04 +08:00
|
|
|
"reflect"
|
2026-06-09 15:59:31 +08:00
|
|
|
"strings"
|
2021-11-15 17:25:04 +08:00
|
|
|
"syscall"
|
|
|
|
|
"unsafe"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type DiskInfo struct {
|
|
|
|
|
Driver string
|
|
|
|
|
Name string
|
|
|
|
|
Format string
|
|
|
|
|
SerialNumber uint32
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-09 15:59:31 +08:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-15 17:25:04 +08:00
|
|
|
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) {
|
2026-06-09 15:59:31 +08:00
|
|
|
inPtr, inSize, err := getPointer(in)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
outPtr, outSize, err := getPointer(out)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2021-11-15 17:25:04 +08:00
|
|
|
//_,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
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-09 15:59:31 +08:00
|
|
|
func getPointer(i interface{}) (pointer uintptr, size uintptr, err error) {
|
|
|
|
|
if i == nil {
|
|
|
|
|
return 0, 0, nil
|
|
|
|
|
}
|
2021-11-15 17:25:04 +08:00
|
|
|
v := reflect.ValueOf(i)
|
|
|
|
|
switch k := v.Kind(); k {
|
|
|
|
|
case reflect.Ptr:
|
2026-06-09 15:59:31 +08:00
|
|
|
if v.IsNil() {
|
|
|
|
|
return 0, 0, nil
|
|
|
|
|
}
|
2021-11-15 17:25:04 +08:00
|
|
|
t := v.Elem().Type()
|
|
|
|
|
size = t.Size()
|
|
|
|
|
pointer = v.Pointer()
|
|
|
|
|
case reflect.Slice:
|
2026-06-09 15:59:31 +08:00
|
|
|
if v.Len() == 0 {
|
|
|
|
|
return 0, 0, nil
|
|
|
|
|
}
|
|
|
|
|
size = uintptr(v.Len()) * v.Type().Elem().Size()
|
2021-11-15 17:25:04 +08:00
|
|
|
pointer = v.Pointer()
|
|
|
|
|
default:
|
2026-06-09 15:59:31 +08:00
|
|
|
return 0, 0, fmt.Errorf("unsupported DeviceIoControl buffer type %T", i)
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
2026-06-09 15:59:31 +08:00
|
|
|
return pointer, size, nil
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-09 15:59:31 +08:00
|
|
|
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")
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
2026-06-09 15:59:31 +08:00
|
|
|
if done == 0 {
|
|
|
|
|
return 0, nil
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
2026-06-09 15:59:31 +08:00
|
|
|
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)
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
2026-06-09 15:59:31 +08:00
|
|
|
|
|
|
|
|
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)
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
|
|
|
|
|
2026-06-09 15:59:31 +08:00
|
|
|
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
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
2026-06-09 15:59:31 +08:00
|
|
|
|
|
|
|
|
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
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
2026-06-09 15:59:31 +08:00
|
|
|
|
|
|
|
|
return next, nil
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
|
|
|
|
|
2026-06-09 15:59:31 +08:00
|
|
|
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) {
|
2021-11-15 17:25:04 +08:00
|
|
|
fileMap := make(map[win32api.DWORDLONG]FileEntry)
|
2026-06-09 15:59:31 +08:00
|
|
|
pDriver, err := volumeDevicePath(driver)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fileMap, err
|
|
|
|
|
}
|
2021-11-15 17:25:04 +08:00
|
|
|
fd, err := CreateFile(pDriver, syscall.O_RDONLY, win32api.FILE_ATTRIBUTE_NORMAL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fileMap, err
|
|
|
|
|
}
|
2026-06-09 15:59:31 +08:00
|
|
|
defer syscall.Close(fd)
|
2021-11-15 17:25:04 +08:00
|
|
|
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 {
|
2026-06-09 15:59:31 +08:00
|
|
|
applyPathCanonicalNames(driver, fileMap)
|
2021-11-15 17:25:04 +08:00
|
|
|
return fileMap, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-09 15:59:31 +08:00
|
|
|
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
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
2026-06-09 15:59:31 +08:00
|
|
|
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
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
2026-06-09 15:59:31 +08:00
|
|
|
return filterUSNFileMap(fileMap, searchFn), nil
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
|
|
|
|
|
2026-06-09 15:59:31 +08:00
|
|
|
func buildUSNPath(diskName string, fileMap map[win32api.DWORDLONG]FileEntry, id win32api.DWORDLONG) (name string) {
|
|
|
|
|
normalized, err := normalizeDiskName(diskName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
2021-11-15 17:25:04 +08:00
|
|
|
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
|
|
|
|
|
}
|
2026-06-09 15:59:31 +08:00
|
|
|
name = strings.TrimSuffix(normalized, "\\") + name
|
2021-11-15 17:25:04 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-09 15:59:31 +08:00
|
|
|
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{
|
2021-11-15 17:25:04 +08:00
|
|
|
Name: en.Name,
|
|
|
|
|
Parent: en.Parent,
|
|
|
|
|
Type: en.Type,
|
2026-06-09 15:59:31 +08:00
|
|
|
})
|
|
|
|
|
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
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
2026-06-09 15:59:31 +08:00
|
|
|
stat := newFileStatFromInformation(&info, name, path)
|
|
|
|
|
fileType, err := syscall.GetFileType(fd)
|
|
|
|
|
if err == nil {
|
|
|
|
|
stat.filetype = fileType
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
2026-06-09 15:59:31 +08:00
|
|
|
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)
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
ALL_FILES = iota
|
|
|
|
|
ONLY_FOLDER
|
|
|
|
|
NO_FOLDER
|
|
|
|
|
)
|
|
|
|
|
|
2021-11-16 15:58:13 +08:00
|
|
|
func ListNTFSUsnDriverFilesFn(diskName string, searchFn func(string, bool) bool) ([]string, error) {
|
2021-11-15 17:25:04 +08:00
|
|
|
var result []string
|
|
|
|
|
data, err := ListUsnFileFn(diskName, searchFn)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return result, err
|
|
|
|
|
}
|
2021-11-16 15:58:13 +08:00
|
|
|
return listNTFSUsnDriverFiles(diskName, searchFn, data)
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
|
|
|
|
|
2021-11-16 15:58:13 +08:00
|
|
|
func ListNTFSUsnDriverFiles(diskName string, folder uint8) ([]string, error) {
|
2021-11-15 17:25:04 +08:00
|
|
|
var result []string
|
|
|
|
|
data, err := ListUsnFile(diskName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return result, err
|
|
|
|
|
}
|
2021-11-16 15:58:13 +08:00
|
|
|
return listNTFSUsnDriverFiles(diskName, func(name string, tp bool) bool {
|
|
|
|
|
if !tp && folder == ONLY_FOLDER {
|
2021-11-15 17:25:04 +08:00
|
|
|
return false
|
|
|
|
|
}
|
2021-11-16 15:58:13 +08:00
|
|
|
if tp && folder == NO_FOLDER {
|
2021-11-15 17:25:04 +08:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}, data)
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-16 15:58:13 +08:00
|
|
|
func listNTFSUsnDriverFiles(diskName string, fn func(string, bool) bool, data map[win32api.DWORDLONG]FileEntry) ([]string, error) {
|
2021-11-15 17:25:04 +08:00
|
|
|
result := make([]string, len(data))
|
|
|
|
|
i := 0
|
|
|
|
|
for k, v := range data {
|
2021-11-16 15:58:13 +08:00
|
|
|
if !fn(v.Name, v.Type == 1) {
|
2021-11-15 17:25:04 +08:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
name := GetFullUsnPath(diskName, data, k)
|
|
|
|
|
result[i] = name
|
|
|
|
|
i++
|
|
|
|
|
}
|
2026-06-09 15:59:31 +08:00
|
|
|
return result[:i], nil
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
|
|
|
|
|
2021-11-16 15:58:13 +08:00
|
|
|
func ListNTFSUsnDriverInfoFn(diskName string, searchFn func(string, bool) bool) ([]FileStat, error) {
|
2021-11-15 17:25:04 +08:00
|
|
|
data, err := ListUsnFileFn(diskName, searchFn)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2021-11-16 15:58:13 +08:00
|
|
|
return listNTFSUsnDriverInfo(diskName, searchFn, data)
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
|
|
|
|
|
2021-11-16 15:58:13 +08:00
|
|
|
func ListNTFSUsnDriverInfo(diskName string, folder uint8) ([]FileStat, error) {
|
2021-11-15 17:25:04 +08:00
|
|
|
data, err := ListUsnFile(diskName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2021-11-16 15:58:13 +08:00
|
|
|
return listNTFSUsnDriverInfo(diskName, func(name string, tp bool) bool {
|
|
|
|
|
if !tp && folder == ONLY_FOLDER {
|
2021-11-15 17:25:04 +08:00
|
|
|
return false
|
|
|
|
|
}
|
2021-11-16 15:58:13 +08:00
|
|
|
if tp && folder == NO_FOLDER {
|
2021-11-15 17:25:04 +08:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}, data)
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-09 15:59:31 +08:00
|
|
|
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{}
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
2026-06-09 15:59:31 +08:00
|
|
|
if fn == nil {
|
|
|
|
|
fn = func(string, bool) bool { return true }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resultCh := make(chan FileStat, len(data))
|
2021-11-16 15:58:13 +08:00
|
|
|
wg := stario.NewWaitGroup(100)
|
2026-06-09 15:59:31 +08:00
|
|
|
for id, entry := range data {
|
|
|
|
|
if !fn(entry.Name, entry.Type == 1) {
|
2021-11-15 17:25:04 +08:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
wg.Add(1)
|
2026-06-09 15:59:31 +08:00
|
|
|
go func(id win32api.DWORDLONG, entry FileEntry) {
|
2021-11-15 17:25:04 +08:00
|
|
|
defer wg.Done()
|
2026-06-09 15:59:31 +08:00
|
|
|
stat, err := fetch(id, entry)
|
2021-11-15 17:25:04 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-06-09 15:59:31 +08:00
|
|
|
resultCh <- stat
|
|
|
|
|
}(id, entry)
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
|
|
|
|
wg.Wait()
|
2026-06-09 15:59:31 +08:00
|
|
|
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
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
|
|
|
|
|
2026-06-09 15:59:31 +08:00
|
|
|
func USNReasonString(reason win32api.DWORD) (s string) {
|
2021-11-15 17:25:04 +08:00
|
|
|
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
|
|
|
|
|
}
|
2026-06-09 15:59:31 +08:00
|
|
|
for i := 0; reason != 0; i++ {
|
|
|
|
|
if i >= len(reasons) {
|
|
|
|
|
if s == "" {
|
|
|
|
|
return fmt.Sprintf("0x%08X", uint32(reason)<<uint(i))
|
|
|
|
|
}
|
|
|
|
|
return s + fmt.Sprintf(", 0x%08X", uint32(reason)<<uint(i))
|
|
|
|
|
}
|
2021-11-15 17:25:04 +08:00
|
|
|
if reason&1 == 1 {
|
|
|
|
|
s = s + ", " + reasons[i]
|
|
|
|
|
}
|
|
|
|
|
reason >>= 1
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-09 15:59:31 +08:00
|
|
|
func getUsnJournalReasonString(reason win32api.DWORD) string {
|
|
|
|
|
return USNReasonString(reason)
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-15 17:25:04 +08:00
|
|
|
func MonitorUsnChange(driver string, rec chan FileMonitor) error {
|
2026-06-09 15:59:31 +08:00
|
|
|
pDriver, err := volumeDevicePath(driver)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2021-11-15 17:25:04 +08:00
|
|
|
fd, err := CreateFile(pDriver, syscall.O_RDONLY, win32api.FILE_ATTRIBUTE_NORMAL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-06-09 15:59:31 +08:00
|
|
|
defer syscall.Close(fd)
|
2021-11-15 17:25:04 +08:00
|
|
|
ujd, _, err := queryUsnJournal(fd)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rujd := win32api.READ_USN_JOURNAL_DATA{ujd.NextUsn, 0xFFFFFFFF, 0, 0, 1, ujd.UsnJournalID}
|
2026-06-09 15:59:31 +08:00
|
|
|
cache := make(map[win32api.DWORDLONG]FileEntry)
|
2021-11-15 17:25:04 +08:00
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
data, done, err := readUsnJournal(fd, &rujd)
|
2026-06-09 15:59:31 +08:00
|
|
|
if err != nil || done <= uint32(usnBufferHeaderSize) {
|
2021-11-15 17:25:04 +08:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-09 15:59:31 +08:00
|
|
|
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
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
2026-06-09 15:59:31 +08:00
|
|
|
rujd.StartUsn = win32api.USN(nextUsn)
|
|
|
|
|
if nextUsn == 0 {
|
2021-11-15 17:25:04 +08:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-16 15:58:13 +08:00
|
|
|
func GetUsnFileInfo(diskName string, fileMap map[win32api.DWORDLONG]FileEntry, id win32api.DWORDLONG) (FileStat, error) {
|
2026-06-09 15:59:31 +08:00
|
|
|
pDriver, err := volumeDevicePath(diskName)
|
2021-11-15 17:25:04 +08:00
|
|
|
if err != nil {
|
2021-11-16 15:58:13 +08:00
|
|
|
return FileStat{}, err
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
2026-06-09 15:59:31 +08:00
|
|
|
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])
|
2021-11-15 17:25:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Need a custom Open to work with backup_semantics
|
|
|
|
|
func OpenFileById(diskName string, id win32api.DWORDLONG, mode int, attrs uint32) (syscall.Handle, error) {
|
2026-06-09 15:59:31 +08:00
|
|
|
pDriver, err := volumeDevicePath(diskName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return syscall.InvalidHandle, err
|
|
|
|
|
}
|
2021-11-15 17:25:04 +08:00
|
|
|
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{
|
2026-06-09 15:59:31 +08:00
|
|
|
DwSize: win32api.DWORD(unsafe.Sizeof(win32api.FILE_ID_DESCRIPTOR{})),
|
|
|
|
|
Type: win32api.FileIdType,
|
2021-11-15 17:25:04 +08:00
|
|
|
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
|
|
|
|
|
}
|