mft function rewrite

This commit is contained in:
2022-03-09 13:42:01 +08:00
parent db9e96e4ed
commit 426a959d0d
6 changed files with 116 additions and 54 deletions
+1 -1
View File
@@ -108,7 +108,7 @@ func main() {
if v.Format == "NTFS" && v.Driver == `C:\` {
starlog.Infoln("开始获取NTFS USN日志,磁盘:", v.Driver)
fileLists, err := wincmd.ListUsnFileFn(v.Driver, func(name string, typed uint8) bool {
fileLists, err := wincmd.ListUsnFileFn(v.Driver, func(name string, typed bool) bool {
return true
if ok, _ := regexp.MatchString(`\.exe$`, name); ok {
return true
+291
View File
@@ -0,0 +1,291 @@
package mft
import (
"b612.me/wincmd/ntfs/binutil"
"b612.me/wincmd/ntfs/utf16"
"encoding/binary"
"errors"
"io"
"os"
"reflect"
"runtime"
"strings"
"time"
"unsafe"
)
type MFTFile struct {
Name string
Path string
ModTime time.Time
Size uint64
Aszie uint64
IsDir bool
Node uint64
}
type FileEntry struct {
Name string
Parent uint64
}
func GetFileListsByMftFn(driver string, fn func(string, bool) bool) ([]MFTFile, error) {
var result []MFTFile
extendMftRecord := make(map[uint64][]Attribute)
fileMap := make(map[uint64]FileEntry)
f, size, err := GetMFTFile(driver)
if err != nil {
return []MFTFile{}, err
}
recordSize := int64(1024)
alreadyGot := int64(0)
maxRecordSize := size / recordSize
if maxRecordSize > 1024 {
maxRecordSize = 1024
}
for {
for {
if (size - alreadyGot) < maxRecordSize*recordSize {
maxRecordSize--
} else {
break
}
}
if maxRecordSize < 10 {
maxRecordSize = 1
}
buf := make([]byte, maxRecordSize*recordSize)
got, err := io.ReadFull(f, buf)
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return []MFTFile{}, err
}
alreadyGot += int64(got)
for j := int64(0); j < 1024*maxRecordSize; j += 1024 {
record, err := ParseRecord(buf[j : j+1024])
if err != nil {
continue
}
if record.BaseRecordReference.ToUint64() != 0 {
val := extendMftRecord[record.BaseRecordReference.ToUint64()]
for _, v := range record.Attributes {
if v.Type == AttributeTypeData && v.ActualSize != 0 {
val = append(val, v)
}
}
if len(val) != 0 {
extendMftRecord[record.BaseRecordReference.ToUint64()] = val
}
}
if record.Flags&RecordFlagInUse == 1 && record.Flags&RecordFlagIsIndex == 0 {
var file MFTFile
file.IsDir = record.Flags&RecordFlagIsDirectory != 0
file.Node = record.FileReference.ToUint64()
parent := uint64(0)
for _, v := range record.Attributes {
if v.Type == AttributeTypeData {
file.Size = v.ActualSize
file.Aszie = v.AllocatedSize
}
if v.Type == AttributeTypeStandardInformation {
if len(v.Data) >= 48 {
r := binutil.NewLittleEndianReader(v.Data)
file.ModTime = ConvertFileTime(r.Uint64(0x08))
}
}
if v.Type == AttributeTypeFileName {
name := utf16.DecodeString(v.Data[66:], binary.LittleEndian)
if len(file.Name) < len(name) && len(name) > 0 {
if len(file.Name) > 0 && !strings.Contains(file.Name, "~") {
continue
}
file.Name = name
}
if file.Name != "" {
parent = binutil.NewLittleEndianReader(v.Data[:8]).Uint64(0)
}
}
}
if file.Name != "" {
canAdd := fn(file.Name, file.IsDir)
if canAdd {
result = append(result, file)
}
if canAdd || file.IsDir {
fileMap[uint64(file.Node)] = FileEntry{
Name: file.Name,
Parent: uint64(parent),
}
}
}
}
}
}
(*reflect.SliceHeader)(unsafe.Pointer(&result)).Cap = len(result)
for k, v := range result {
if attrs, ok := extendMftRecord[v.Node]; ok {
if v.Aszie == 0 {
for _, v := range attrs {
if v.Type == AttributeTypeData && v.ActualSize != 0 {
result[k].Size = v.ActualSize
result[k].Aszie = v.AllocatedSize
}
}
}
delete(extendMftRecord, v.Node)
}
result[k].Path = GetFullUsnPath(driver, fileMap, uint64(v.Node))
}
fileMap = nil
runtime.GC()
return result, nil
}
func GetFileListsByMft(driver string) ([]MFTFile, error) {
return GetFileListsByMftFn(driver, func(string, bool) bool { return true })
}
func GetFileListsFromMftFileFn(filepath string, fn func(string, bool) bool) ([]MFTFile, error) {
var result []MFTFile
extendMftRecord := make(map[uint64][]Attribute)
fileMap := make(map[uint64]FileEntry)
f, err := os.Open(filepath)
if err != nil {
return []MFTFile{}, err
}
stat, err := f.Stat()
if err != nil {
return []MFTFile{}, err
}
size := stat.Size()
recordSize := int64(1024)
alreadyGot := int64(0)
maxRecordSize := size / recordSize
if maxRecordSize > 1024 {
maxRecordSize = 1024
}
for {
for {
if (size - alreadyGot) < maxRecordSize*recordSize {
maxRecordSize--
} else {
break
}
}
if maxRecordSize < 10 {
maxRecordSize = 1
}
buf := make([]byte, maxRecordSize*recordSize)
got, err := io.ReadFull(f, buf)
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return []MFTFile{}, err
}
alreadyGot += int64(got)
for j := int64(0); j < 1024*maxRecordSize; j += 1024 {
record, err := ParseRecord(buf[j : j+1024])
if err != nil {
continue
}
if record.BaseRecordReference.ToUint64() != 0 {
val := extendMftRecord[record.BaseRecordReference.ToUint64()]
for _, v := range record.Attributes {
if v.Type == AttributeTypeData && v.ActualSize != 0 {
val = append(val, v)
}
}
if len(val) != 0 {
extendMftRecord[record.BaseRecordReference.ToUint64()] = val
}
}
if record.Flags&RecordFlagInUse == 1 && record.Flags&RecordFlagIsIndex == 0 {
var file MFTFile
file.IsDir = record.Flags&RecordFlagIsDirectory != 0
file.Node = record.FileReference.ToUint64()
parent := uint64(0)
for _, v := range record.Attributes {
if v.Type == AttributeTypeData {
file.Size = v.ActualSize
file.Aszie = v.AllocatedSize
}
if v.Type == AttributeTypeStandardInformation {
if len(v.Data) >= 48 {
r := binutil.NewLittleEndianReader(v.Data)
file.ModTime = ConvertFileTime(r.Uint64(0x08))
}
}
if v.Type == AttributeTypeFileName {
name := utf16.DecodeString(v.Data[66:], binary.LittleEndian)
if len(file.Name) < len(name) && len(name) > 0 {
if len(file.Name) > 0 && !strings.Contains(file.Name, "~") {
continue
}
file.Name = name
}
if file.Name != "" {
parent = binutil.NewLittleEndianReader(v.Data[:8]).Uint64(0)
}
}
}
if file.Name != "" {
canAdd := fn(file.Name, file.IsDir)
if canAdd {
result = append(result, file)
}
if canAdd || file.IsDir {
fileMap[uint64(file.Node)] = FileEntry{
Name: file.Name,
Parent: uint64(parent),
}
}
}
}
}
}
(*reflect.SliceHeader)(unsafe.Pointer(&result)).Cap = len(result)
for k, v := range result {
if attrs, ok := extendMftRecord[v.Node]; ok {
if v.Aszie == 0 {
for _, v := range attrs {
if v.Type == AttributeTypeData && v.ActualSize != 0 {
result[k].Size = v.ActualSize
result[k].Aszie = v.AllocatedSize
}
}
}
delete(extendMftRecord, v.Node)
}
result[k].Path = GetFullUsnPath(" ", fileMap, uint64(v.Node))
}
fileMap = nil
runtime.GC()
return result, nil
}
func GetFileListsFromMftFile(filepath string) ([]MFTFile, error) {
return GetFileListsFromMftFileFn(filepath, func(string, bool) bool { return true })
}
func GetFullUsnPath(diskName string, fileMap map[uint64]FileEntry, id uint64) (name string) {
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 = diskName[:len(diskName)-1] + name
return
}
+2 -4
View File
@@ -5,7 +5,6 @@ import (
"b612.me/wincmd/ntfs/fragment"
"bytes"
"fmt"
"github.com/t9t/gomft/mft"
"io"
"os"
"runtime"
@@ -13,7 +12,6 @@ import (
const supportedOemId = "NTFS "
const isWin = runtime.GOOS == "windows"
func GetMFTFileBytes(volume string) ([]byte, error) {
@@ -85,12 +83,12 @@ func GetMFTFile(volume string) (io.Reader, int64, error) {
return nil, 0, fmt.Errorf("Unable to read $MFT record: %v\n", err)
}
record, err := mft.ParseRecord(mftData)
record, err := ParseRecord(mftData)
if err != nil {
return nil, 0, fmt.Errorf("Unable to parse $MFT record: %v\n", err)
}
dataAttributes := record.FindAttributes(mft.AttributeTypeData)
dataAttributes := record.FindAttributes(AttributeTypeData)
if len(dataAttributes) == 0 {
return nil, 0, fmt.Errorf("No $DATA attribute found in $MFT record\n")
}
+256
View File
@@ -0,0 +1,256 @@
package usn
import (
"b612.me/win32api"
"golang.org/x/sys/windows"
"os"
"sync"
"syscall"
"time"
)
const DevNull = "NUL"
// A fileStat is the implementation of FileInfo returned by Stat and Lstat.
type FileStat struct {
name string
// from ByHandleFileInformation, Win32FileAttributeData and Win32finddata
FileAttributes uint32
CreationTime syscall.Filetime
LastAccessTime syscall.Filetime
LastWriteTime syscall.Filetime
FileSizeHigh uint32
FileSizeLow uint32
// from Win32finddata
Reserved0 uint32
// what syscall.GetFileType returns
filetype uint32
// used to implement SameFile
sync.Mutex
path string
vol uint32
idxhi uint32
idxlo uint32
appendNameToPath bool
}
// newFileStatFromWin32finddata copies all required information
// from syscall.Win32finddata d into the newly created fileStat.
func newFileStatFromInformation(d *syscall.ByHandleFileInformation, name string, path string) FileStat {
return FileStat{
name: name,
path: path,
FileAttributes: d.FileAttributes,
CreationTime: d.CreationTime,
LastAccessTime: d.LastAccessTime,
LastWriteTime: d.LastWriteTime,
FileSizeHigh: d.FileSizeHigh,
FileSizeLow: d.FileSizeLow,
}
}
func (fs *FileStat) Name() string {
return fs.name
}
func (fs *FileStat) IsDir() bool {
return fs.FileAttributes&win32api.FILE_ATTRIBUTE_DIRECTORY != 0
}
func (fs *FileStat) isSymlink() bool {
// Use instructions described at
// https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/
// to recognize whether it's a symlink.
if fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
return false
}
return fs.Reserved0 == syscall.IO_REPARSE_TAG_SYMLINK ||
fs.Reserved0 == windows.IO_REPARSE_TAG_MOUNT_POINT
}
func (fs *FileStat) Size() int64 {
return int64(fs.FileSizeHigh)<<32 + int64(fs.FileSizeLow)
}
func (fs *FileStat) Mode() (m os.FileMode) {
if fs == &devNullStat {
return os.ModeDevice | os.ModeCharDevice | 0666
}
if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 {
m |= 0444
} else {
m |= 0666
}
if fs.isSymlink() {
return m | os.ModeSymlink
}
if fs.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
m |= os.ModeDir | 0111
}
switch fs.filetype {
case syscall.FILE_TYPE_PIPE:
m |= os.ModeNamedPipe
case syscall.FILE_TYPE_CHAR:
m |= os.ModeDevice | os.ModeCharDevice
}
return m
}
func (fs *FileStat) ModTime() time.Time {
return time.Unix(0, fs.LastWriteTime.Nanoseconds())
}
// Sys returns syscall.Win32FileAttributeData for file fs.
func (fs *FileStat) Sys() interface{} {
return &syscall.Win32FileAttributeData{
FileAttributes: fs.FileAttributes,
CreationTime: fs.CreationTime,
LastAccessTime: fs.LastAccessTime,
LastWriteTime: fs.LastWriteTime,
FileSizeHigh: fs.FileSizeHigh,
FileSizeLow: fs.FileSizeLow,
}
}
// saveInfoFromPath saves full path of the file to be used by os.SameFile later,
// and set name from path.
// devNullStat is fileStat structure describing DevNull file ("NUL").
var devNullStat = FileStat{
name: DevNull,
// hopefully this will work for SameFile
vol: 0,
idxhi: 0,
idxlo: 0,
}
func fixLongPath(path string) string {
// Do nothing (and don't allocate) if the path is "short".
// Empirically (at least on the Windows Server 2013 builder),
// the kernel is arbitrarily okay with < 248 bytes. That
// matches what the docs above say:
// "When using an API to create a directory, the specified
// path cannot be so long that you cannot append an 8.3 file
// name (that is, the directory name cannot exceed MAX_PATH
// minus 12)." Since MAX_PATH is 260, 260 - 12 = 248.
//
// The MSDN docs appear to say that a normal path that is 248 bytes long
// will work; empirically the path must be less then 248 bytes long.
if len(path) < 248 {
// Don't fix. (This is how Go 1.7 and earlier worked,
// not automatically generating the \\?\ form)
return path
}
// The extended form begins with \\?\, as in
// \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt.
// The extended form disables evaluation of . and .. path
// elements and disables the interpretation of / as equivalent
// to \. The conversion here rewrites / to \ and elides
// . elements as well as trailing or duplicate separators. For
// simplicity it avoids the conversion entirely for relative
// paths or paths containing .. elements. For now,
// \\server\share paths are not converted to
// \\?\UNC\server\share paths because the rules for doing so
// are less well-specified.
if len(path) >= 2 && path[:2] == `\\` {
// Don't canonicalize UNC paths.
return path
}
if !isAbs(path) {
// Relative path
return path
}
const prefix = `\\?`
pathbuf := make([]byte, len(prefix)+len(path)+len(`\`))
copy(pathbuf, prefix)
n := len(path)
r, w := 0, len(prefix)
for r < n {
switch {
case IsPathSeparator(path[r]):
// empty block
r++
case path[r] == '.' && (r+1 == n || IsPathSeparator(path[r+1])):
// /./
r++
case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || IsPathSeparator(path[r+2])):
// /../ is currently unhandled
return path
default:
pathbuf[w] = '\\'
w++
for ; r < n && !IsPathSeparator(path[r]); r++ {
pathbuf[w] = path[r]
w++
}
}
}
// A drive's root directory needs a trailing \
if w == len(`\\?\c:`) {
pathbuf[w] = '\\'
w++
}
return string(pathbuf[:w])
}
func IsPathSeparator(c uint8) bool {
// NOTE: Windows accept / as path separator.
return c == '\\' || c == '/'
}
func isAbs(path string) (b bool) {
v := volumeName(path)
if v == "" {
return false
}
path = path[len(v):]
if path == "" {
return false
}
return IsPathSeparator(path[0])
}
func volumeName(path string) (v string) {
if len(path) < 2 {
return ""
}
// with drive letter
c := path[0]
if path[1] == ':' &&
('0' <= c && c <= '9' || 'a' <= c && c <= 'z' ||
'A' <= c && c <= 'Z') {
return path[:2]
}
// is it UNC
if l := len(path); l >= 5 && IsPathSeparator(path[0]) && IsPathSeparator(path[1]) &&
!IsPathSeparator(path[2]) && path[2] != '.' {
// first, leading `\\` and next shouldn't be `\`. its server name.
for n := 3; n < l-1; n++ {
// second, next '\' shouldn't be repeated.
if IsPathSeparator(path[n]) {
n++
// third, following something characters. its share name.
if !IsPathSeparator(path[n]) {
if path[n] == '.' {
break
}
for ; n < l; n++ {
if IsPathSeparator(path[n]) {
break
}
}
return path[:n]
}
break
}
}
}
return ""
}
+13
View File
@@ -0,0 +1,13 @@
package usn
import (
"fmt"
"testing"
)
func Test_USN(t *testing.T) {
fmt.Println("start")
data, err := ListUsnFile("C:\\")
fmt.Println(err)
fmt.Println(len(data))
}
+596
View File
@@ -0,0 +1,596 @@
package usn
import (
"b612.me/stario"
"b612.me/win32api"
"fmt"
"os"
"reflect"
"runtime"
"syscall"
"unsafe"
)
type DiskInfo struct {
Driver string
Name string
Format string
SerialNumber uint32
}
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 := getPointer(in)
outPtr, outSize := getPointer(out)
//_,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, 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("error")
}
return
}
// 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
}
func ListUsnFile(driver string) (map[win32api.DWORDLONG]FileEntry, error) {
fileMap := make(map[win32api.DWORDLONG]FileEntry)
pDriver := "\\\\.\\" + driver[:len(driver)-1]
fd, err := CreateFile(pDriver, syscall.O_RDONLY, win32api.FILE_ATTRIBUTE_NORMAL)
if err != nil {
return fileMap, err
}
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 {
return fileMap, nil
}
var usn win32api.USN = *(*win32api.USN)(unsafe.Pointer(&data[0]))
// fmt.Println("usn", usn)
var ur *win32api.USN_RECORD
for i := unsafe.Sizeof(usn); i < uintptr(done); i += uintptr(ur.RecordLength) {
ur = (*win32api.USN_RECORD)(unsafe.Pointer(&data[i]))
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)
typed := uint8(0)
if ur.FileAttributes&win32api.FILE_ATTRIBUTE_DIRECTORY != 0 {
typed = 1
}
// fmt.Println("len", ur.FileNameLength, ur.FileNameOffset, "fn", fn)
fileMap[ur.FileReferenceNumber] = FileEntry{Name: fn, Parent: ur.ParentFileReferenceNumber, Type: typed}
}
med.StartFileReferenceNumber = win32api.DWORDLONG(usn)
}
}
func ListUsnFileFn(driver string, searchFn func(string, bool) bool) (map[win32api.DWORDLONG]FileEntry, error) {
fileMap := make(map[win32api.DWORDLONG]FileEntry)
pDriver := "\\\\.\\" + driver[:len(driver)-1]
fd, err := CreateFile(pDriver, syscall.O_RDONLY, win32api.FILE_ATTRIBUTE_NORMAL)
if err != nil {
return fileMap, err
}
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 {
return fileMap, nil
}
var usn win32api.USN = *(*win32api.USN)(unsafe.Pointer(&data[0]))
// fmt.Println("usn", usn)
var ur *win32api.USN_RECORD
for i := unsafe.Sizeof(usn); i < uintptr(done); i += uintptr(ur.RecordLength) {
ur = (*win32api.USN_RECORD)(unsafe.Pointer(&data[i]))
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)
typed := uint8(0)
if ur.FileAttributes&win32api.FILE_ATTRIBUTE_DIRECTORY != 0 {
typed = 1
}
if typed == 1 || searchFn(fn, typed == 1) {
// fmt.Println("len", ur.FileNameLength, ur.FileNameOffset, "fn", fn)
fileMap[ur.FileReferenceNumber] = FileEntry{Name: fn, Parent: ur.ParentFileReferenceNumber, Type: typed}
}
}
med.StartFileReferenceNumber = win32api.DWORDLONG(usn)
}
}
func GetFullUsnPath(diskName string, fileMap map[win32api.DWORDLONG]FileEntry, id win32api.DWORDLONG) (name string) {
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 = diskName[:len(diskName)-1] + name
return
}
func GetFullUsnPathEntry(diskName string, fileMap map[win32api.DWORDLONG]FileEntry, en FileMonitor) (name string) {
fileMap[en.Self] = FileEntry{
Name: en.Name,
Parent: en.Parent,
Type: en.Type,
}
id := en.Self
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 = diskName[:len(diskName)-1] + name
return
}
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++
}
(*reflect.SliceHeader)(unsafe.Pointer(&result)).Cap = i
(*reflect.SliceHeader)(unsafe.Pointer(&result)).Len = i
data = nil
data = make(map[win32api.DWORDLONG]FileEntry, 0)
runtime.GC()
return result, 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)
}
func listNTFSUsnDriverInfo(diskName string, fn func(string, bool) bool, data map[win32api.DWORDLONG]FileEntry) ([]FileStat, error) {
//fmt.Println("finished 1")
pDriver := "\\\\.\\" + diskName[:len(diskName)-1]
fd, err := CreateFile(pDriver, syscall.O_RDONLY, win32api.FILE_ATTRIBUTE_NORMAL)
if err != nil {
return nil, err
}
defer syscall.Close(fd)
result := make([]FileStat, len(data))
i := int(0)
wg := stario.NewWaitGroup(100)
for k, v := range data {
if !fn(v.Name, v.Type == 1) {
continue
}
wg.Add(1)
go func(k win32api.DWORDLONG, v FileEntry, i int) {
defer wg.Done()
//now := time.Now().UnixNano()
/*
fd2, err := OpenFileByIdWithfd(fd, k, syscall.O_RDONLY, win32api.FILE_ATTRIBUTE_NORMAL)
if err != nil {
return
}
//fmt.Println("cost", float64((time.Now().UnixNano()-now)/1000000))
var info syscall.ByHandleFileInformation
err = syscall.GetFileInformationByHandle(fd2, &info)
syscall.Close(fd2)
//fmt.Println("cost", float64((time.Now().UnixNano()-now)/1000000))
if err != nil {
return
}
*/
path := GetFullUsnPath(diskName, data, k)
fileInfo, err := os.Stat(path)
if err != nil {
return
}
fs := fileInfo.Sys().(*syscall.Win32FileAttributeData)
stat := FileStat{
FileAttributes: fs.FileAttributes,
CreationTime: fs.CreationTime,
LastAccessTime: fs.LastAccessTime,
LastWriteTime: fs.LastWriteTime,
FileSizeHigh: fs.FileSizeHigh,
FileSizeLow: fs.FileSizeLow,
}
stat.name = v.Name
stat.path = path
return
result[i] = stat
//result[i] = newFileStatFromInformation(&info, v.Name, path)
}(k, v, i)
i++
}
wg.Wait()
//fmt.Println("finished 2")
(*reflect.SliceHeader)(unsafe.Pointer(&result)).Cap = i
(*reflect.SliceHeader)(unsafe.Pointer(&result)).Len = i
data = nil
//data = make(map[win32api.DWORDLONG]FileEntry, 0)
runtime.GC()
return result, nil
}
func getUsnJournalReasonString(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; {
if reason&1 == 1 {
s = s + ", " + reasons[i]
}
reason >>= 1
i++
}
return
}
func MonitorUsnChange(driver string, rec chan FileMonitor) error {
pDriver := "\\\\.\\" + driver[:len(driver)-1]
fd, err := CreateFile(pDriver, syscall.O_RDONLY, win32api.FILE_ATTRIBUTE_NORMAL)
if err != nil {
return err
}
ujd, _, err := queryUsnJournal(fd)
if err != nil {
return err
}
rujd := win32api.READ_USN_JOURNAL_DATA{ujd.NextUsn, 0xFFFFFFFF, 0, 0, 1, ujd.UsnJournalID}
for {
var usn win32api.USN
data, done, err := readUsnJournal(fd, &rujd)
if err != nil || done <= uint32(unsafe.Sizeof(usn)) {
return err
}
usn = *(*win32api.USN)(unsafe.Pointer(&data[0]))
var ur *win32api.USN_RECORD
for i := unsafe.Sizeof(usn); i < uintptr(done); i += uintptr(ur.RecordLength) {
ur = (*win32api.USN_RECORD)(unsafe.Pointer(&data[i]))
nameLength := uintptr(ur.FileNameLength) / unsafe.Sizeof(ur.FileName[0])
fnp := unsafe.Pointer(&data[i+uintptr(ur.FileNameOffset)])
fn := syscall.UTF16ToString((*[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))
typed := uint8(0)
if ur.FileAttributes&win32api.FILE_ATTRIBUTE_DIRECTORY != 0 {
typed = 1
}
// fmt.Println("len", ur.FileNameLength, ur.FileNameOffset, "fn", fn)
rec <- FileMonitor{Name: fn, Parent: ur.ParentFileReferenceNumber, Type: typed, Self: ur.FileReferenceNumber, Reason: getUsnJournalReasonString(ur.Reason)}
}
rujd.StartUsn = usn
if usn == 0 {
return nil
}
}
}
func GetUsnFileInfo(diskName string, fileMap map[win32api.DWORDLONG]FileEntry, id win32api.DWORDLONG) (FileStat, error) {
name := fileMap[id].Name
path := GetFullUsnPath(diskName, fileMap, id)
fd, err := OpenFileById(diskName, id, syscall.O_RDONLY, win32api.FILE_ATTRIBUTE_NORMAL)
if err != nil {
return FileStat{}, err
}
var info syscall.ByHandleFileInformation
err = syscall.GetFileInformationByHandle(fd, &info)
return newFileStatFromInformation(&info, name, path), err
}
// Need a custom Open to work with backup_semantics
func OpenFileById(diskName string, id win32api.DWORDLONG, mode int, attrs uint32) (syscall.Handle, error) {
pDriver := "\\\\.\\" + diskName[:len(diskName)-1]
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: 16,
Type: 0,
FileId: id,
}
fid.DwSize = win32api.DWORD(unsafe.Sizeof(fid))
h, e := win32api.OpenFileById(win32api.HANDLE(fd), &fid, win32api.DWORD(access),
win32api.DWORD(sharemode), sa, win32api.DWORD(attrs))
return syscall.Handle(h), e
}