From 707e904cd259790b5cca895fe4f7db8486d58306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Grill?= Date: Sat, 3 Aug 2024 07:20:07 +0200 Subject: [PATCH] Converted to monorepo --- {cmd/s3 => bindings}/basepathfs.go | 2 +- bindings/bindings.go | 97 +++++++++++++ bindings/s3/config.go | 62 +++++++++ cmd/s3/main.go | 90 ++---------- example/main.go | 2 +- filesystem/filesystem.go | 2 +- filesystem/filesystem_test.go | 2 +- go.mod | 5 +- projfs/README.md | 2 + projfs/functions.go | 150 ++++++++++++++++++++ projfs/provider.go | 8 ++ projfs/strings.go | 30 ++++ projfs/types.go | 211 +++++++++++++++++++++++++++++ 13 files changed, 578 insertions(+), 85 deletions(-) rename {cmd/s3 => bindings}/basepathfs.go (96%) create mode 100644 bindings/bindings.go create mode 100644 bindings/s3/config.go create mode 100644 projfs/README.md create mode 100644 projfs/functions.go create mode 100644 projfs/provider.go create mode 100644 projfs/strings.go create mode 100644 projfs/types.go diff --git a/cmd/s3/basepathfs.go b/bindings/basepathfs.go similarity index 96% rename from cmd/s3/basepathfs.go rename to bindings/basepathfs.go index 4cdbefd..3b45d8c 100644 --- a/cmd/s3/basepathfs.go +++ b/bindings/basepathfs.go @@ -1,4 +1,4 @@ -package main +package bindings import ( "io/fs" diff --git a/bindings/bindings.go b/bindings/bindings.go new file mode 100644 index 0000000..0bbb318 --- /dev/null +++ b/bindings/bindings.go @@ -0,0 +1,97 @@ +package bindings + +import ( + "flag" + "log" + "os" + "os/signal" + "reflect" + "strings" + "syscall" + "time" + + "github.com/balazsgrill/potatodrive/filesystem" + "github.com/spf13/afero" + "golang.org/x/sys/windows/registry" +) + +func ConfigToFlags(config any) { + structPtrValue := reflect.ValueOf(config) + structValue := structPtrValue.Elem() + structType := structValue.Type() + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + + tagstr := field.Tag.Get("flag") + if tagstr != "" { + tagdata := strings.Split(tagstr, ",") + tag := tagdata[0] + msg := tagdata[1] + switch field.Type.Kind() { + case reflect.String: + flag.StringVar((*string)(structValue.Field(i).Addr().UnsafePointer()), tag, "", msg) + case reflect.Bool: + flag.BoolVar((*bool)(structValue.Field(i).Addr().UnsafePointer()), tag, false, msg) + } + } + } +} + +func ReadConfigFromRegistry(key registry.Key, config any) error { + structPtrValue := reflect.ValueOf(config) + structValue := structPtrValue.Elem() + structType := structValue.Type() + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + fieldvalue := structValue.Field(i) + tag := field.Tag.Get("reg") + if tag != "" { + switch field.Type.Kind() { + case reflect.String: + value, _, err := key.GetStringValue(tag) + if os.IsNotExist(err) { + continue + } + if err != nil { + return err + } + fieldvalue.SetString(value) + case reflect.Bool: + value, _, err := key.GetIntegerValue(tag) + if os.IsNotExist(err) { + continue + } + if err != nil { + return err + } + fieldvalue.SetBool(value != 0) + } + } + } + return nil +} + +func BindVirtualizationInstance(localpath string, remotefs afero.Fs) error { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + closer, err := filesystem.StartProjecting(localpath, remotefs) + if err != nil { + return err + } + + t := time.NewTicker(30 * time.Second) + go func() { + for range t.C { + err = closer.PerformSynchronization() + if err != nil { + log.Panic(err) + } + } + }() + + <-c + t.Stop() + closer.Close() + os.Exit(1) + return nil +} diff --git a/bindings/s3/config.go b/bindings/s3/config.go new file mode 100644 index 0000000..171fdce --- /dev/null +++ b/bindings/s3/config.go @@ -0,0 +1,62 @@ +package s3 + +import ( + "errors" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/balazsgrill/potatodrive/bindings" + s3 "github.com/fclairamb/afero-s3" + "github.com/spf13/afero" +) + +type Config struct { + LocalPath string `flag:"localpath,Local folder" reg:"LocalPath"` + Endpoint string `flag:"endpoint,S3 endpoint" reg:"Endpoint"` + Region string `flag:"region,Region" reg:"Region"` + Bucket string `flag:"bucket,Bucket" reg:"Bucket"` + KeyId string `flag:"keyid,Access Key ID" reg:"KeyID"` + KeySecret string `flag:"secret,Access Key Secret" reg:"KeySecret"` + UseSSL bool `flag:"useSSL,Use SSL encryption for S3 connection" reg:"UseSSL"` +} + +func (c *Config) Validate() error { + if c.Endpoint == "" { + return errors.New("endpoint is mandatory") + } + if c.LocalPath == "" { + return errors.New("localpath is mandatory") + } + if c.Region == "" { + return errors.New("region is mandatory") + } + if c.Bucket == "" { + return errors.New("bucket is mandatory") + } + if c.KeyId == "" { + return errors.New("keyid is mandatory") + } + if c.KeySecret == "" { + return errors.New("secret is mandatory") + } + return nil +} + +func (c *Config) ToFileSystem() (afero.Fs, error) { + sess, err := session.NewSession(&aws.Config{ + Region: aws.String(c.Region), + Endpoint: aws.String(c.Endpoint), + DisableSSL: aws.Bool(!c.UseSSL), + S3ForcePathStyle: aws.Bool(true), + Credentials: credentials.NewStaticCredentials(c.KeyId, c.KeySecret, ""), + }) + if err != nil { + return nil, err + } + + fs := s3.NewFs(c.Bucket, sess) + fs.MkdirAll("root", 0777) + rootfs := bindings.NewBasePathFs(fs, "root") + return rootfs, nil +} diff --git a/cmd/s3/main.go b/cmd/s3/main.go index 8c8e402..d95e54b 100644 --- a/cmd/s3/main.go +++ b/cmd/s3/main.go @@ -3,28 +3,16 @@ package main import ( "flag" "log" - "os" - "os/signal" - "syscall" - "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/balazsgrill/projfero/filesystem" - s3 "github.com/fclairamb/afero-s3" + "github.com/balazsgrill/potatodrive/bindings" + cs3 "github.com/balazsgrill/potatodrive/bindings/s3" "golang.org/x/sys/windows/registry" ) func main() { regkey := flag.String("regkey", "", "Registry key that holds configuration") - endpoint := flag.String("endpoint", "", "S3 endpoint") - accessKeyID := flag.String("keyid", "", "Access Key ID") - secretAccessKey := flag.String("secred", "", "Access Key Secret") - useSSL := flag.Bool("useSSL", false, "Use SSL encryption for S3 connection") - region := flag.String("region", "", "Region") - bucket := flag.String("bucket", "", "Bucket") - localpath := flag.String("localpath", "", "Local folder") + config := &cs3.Config{} + bindings.ConfigToFlags(config) flag.Parse() if *regkey != "" { @@ -32,73 +20,21 @@ func main() { if err != nil { log.Panic(err) } - *endpoint, _, err = key.GetStringValue("Endpoint") - if err != nil { - log.Panic(err) - } - *accessKeyID, _, err = key.GetStringValue("KeyID") - if err != nil { - log.Panic(err) - } - *secretAccessKey, _, err = key.GetStringValue("KeySecret") - if err != nil { - log.Panic(err) - } - useSSLint, _, err := key.GetIntegerValue("UseSSL") - if err != nil { - log.Panic(err) - } - *useSSL = useSSLint != 0 - *region, _, err = key.GetStringValue("Region") - if err != nil { - log.Panic(err) - } - *bucket, _, err = key.GetStringValue("Bucket") - if err != nil { - log.Panic(err) - } - *localpath, _, err = key.GetStringValue("Directory") - if err != nil { - log.Panic(err) - } + bindings.ReadConfigFromRegistry(key, config) } - if *endpoint == "" || *accessKeyID == "" || *secretAccessKey == "" || *region == "" || *bucket == "" || *localpath == "" { - log.Panic("Missing configuration") + err := config.Validate() + if err != nil { + panic(err) } - sess, _ := session.NewSession(&aws.Config{ - Region: aws.String(*region), - Endpoint: aws.String(*endpoint), - DisableSSL: aws.Bool(!*useSSL), - S3ForcePathStyle: aws.Bool(true), - Credentials: credentials.NewStaticCredentials(*accessKeyID, *secretAccessKey, ""), - }) - - // Initialize the file system - fs := s3.NewFs(*bucket, sess) - fs.MkdirAll("root", 0777) - rootfs := NewBasePathFs(fs, "root") - - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - closer, err := filesystem.StartProjecting(*localpath, rootfs) + fs, err := config.ToFileSystem() if err != nil { log.Panic(err) } - t := time.NewTicker(30 * time.Second) - go func() { - for range t.C { - err = closer.PerformSynchronization() - if err != nil { - log.Panic(err) - } - } - }() - - <-c - t.Stop() - closer.Close() - os.Exit(1) + err = bindings.BindVirtualizationInstance(config.LocalPath, fs) + if err != nil { + log.Panic(err) + } } diff --git a/example/main.go b/example/main.go index aa2d809..cd99ad4 100644 --- a/example/main.go +++ b/example/main.go @@ -6,7 +6,7 @@ import ( "os/signal" "syscall" - "github.com/balazsgrill/projfero/filesystem" + "github.com/balazsgrill/potatodrive/filesystem" "github.com/spf13/afero" ) diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go index 84cb9b9..52fef82 100644 --- a/filesystem/filesystem.go +++ b/filesystem/filesystem.go @@ -12,7 +12,7 @@ import ( "C" - "github.com/balazsgrill/projfs" + "github.com/balazsgrill/potatodrive/projfs" "github.com/google/uuid" "github.com/spf13/afero" ) diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go index 60791d6..a99ebbe 100644 --- a/filesystem/filesystem_test.go +++ b/filesystem/filesystem_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "github.com/balazsgrill/projfero/filesystem" + "github.com/balazsgrill/potatodrive/filesystem" "github.com/spf13/afero" ) diff --git a/go.mod b/go.mod index cc66374..0edded2 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,10 @@ -module github.com/balazsgrill/projfero +module github.com/balazsgrill/potatodrive go 1.21.0 require ( fyne.io/fyne/v2 v2.5.0 github.com/aws/aws-sdk-go v1.54.20 - github.com/balazsgrill/projfs v0.0.2 github.com/fclairamb/afero-s3 v0.3.1 github.com/google/uuid v1.6.0 github.com/spf13/afero v1.11.0 @@ -43,5 +42,3 @@ require ( golang.org/x/text v0.16.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -//replace github.com/balazsgrill/projfs => ../projfs diff --git a/projfs/README.md b/projfs/README.md new file mode 100644 index 0000000..e2883b1 --- /dev/null +++ b/projfs/README.md @@ -0,0 +1,2 @@ +Golang bingings for [Windows Projected File System API](https://learn.microsoft.com/en-us/windows/win32/projfs/projected-file-system). +It can be used to implement user-space filesystem virtualization mounted to a local directory with local cache not unlike OneDrive. diff --git a/projfs/functions.go b/projfs/functions.go new file mode 100644 index 0000000..1805c2d --- /dev/null +++ b/projfs/functions.go @@ -0,0 +1,150 @@ +//go:build windows + +package projfs + +import ( + "fmt" + "syscall" + "unsafe" +) + +var ( + projectedfslib = syscall.NewLazyDLL("ProjectedFSLib.dll") + prjAllocateAlignedBuffer = projectedfslib.NewProc("PrjAllocateAlignedBuffer") + prjClearNegativePathCache = projectedfslib.NewProc("PrjClearNegativePathCache") + prjCompleteCommand = projectedfslib.NewProc("PrjCompleteCommand") + prjDeleteFile = projectedfslib.NewProc("PrjDeleteFile") + prjDoesNameContainWildCards = projectedfslib.NewProc("PrjDoesNameContainWildCards") + prjFileNameCompare = projectedfslib.NewProc("PrjFileNameCompare") + prjFileNameMatch = projectedfslib.NewProc("PrjFileNameMatch") + prjFillDirEntryBuffer = projectedfslib.NewProc("PrjFillDirEntryBuffer") + prjFillDirEntryBuffer2 = projectedfslib.NewProc("PrjFillDirEntryBuffer2") + prjFreeAlignedBuffer = projectedfslib.NewProc("PrjFreeAlignedBuffer") + prjGetOnDiskFileState = projectedfslib.NewProc("PrjGetOnDiskFileState") + prjGetVirtualizationInstanceInfo = projectedfslib.NewProc("PrjGetVirtualizationInstanceInfo") + prjMarkDirectoryAsPlaceholder = projectedfslib.NewProc("PrjMarkDirectoryAsPlaceholder") + prjStartVirtualizing = projectedfslib.NewProc("PrjStartVirtualizing") + prjStopVirtualizing = projectedfslib.NewProc("PrjStopVirtualizing") + prjUpdateFileIfNeeded = projectedfslib.NewProc("PrjUpdateFileIfNeeded") + prjWriteFileData = projectedfslib.NewProc("PrjWriteFileData") + prjWritePlaceholderInfo = projectedfslib.NewProc("PrjWritePlaceholderInfo") + prjWritePlaceholderInfo2 = projectedfslib.NewProc("PrjWritePlaceholderInfo2") +) + +func ErrorByCode(result uintptr) error { + if result == 0 { + return nil + } else { + return fmt.Errorf("error result: %x", result) + } +} + +func PrjAllocateAlignedBuffer(namespaceVirtualizationContext PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT, size uint32) uintptr { + res, _, _ := prjAllocateAlignedBuffer.Call(uintptr(namespaceVirtualizationContext), uintptr(size)) + return res +} + +func PrjClearNegativePathCache(namespaceVirtualizationContext PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT, totalEntryNumber *uint32) uintptr { + res, _, _ := prjClearNegativePathCache.Call(uintptr(namespaceVirtualizationContext), uintptr(unsafe.Pointer(totalEntryNumber))) + return res +} + +func PrjGetVirtualizationInstanceInfo(namespaceVirtualizationContext PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT, virtualizationInstanceInfo *PRJ_VIRTUALIZATION_INSTANCE_INFO) uintptr { + res, _, _ := prjGetVirtualizationInstanceInfo.Call(uintptr(namespaceVirtualizationContext), uintptr(unsafe.Pointer(virtualizationInstanceInfo))) + return res +} + +func PrjCompleteCommand(namespaceVirtualizationContext PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT, commandId uint32, completionresult int32, extendedParameters *PRJ_COMPLETE_COMMAND_EXTENDED_PARAMETERS) uintptr { + res, _, _ := prjCompleteCommand.Call(uintptr(namespaceVirtualizationContext), uintptr(commandId), uintptr(completionresult), uintptr(unsafe.Pointer(extendedParameters))) + return res +} + +func PrjDeleteFile(namespaceVirtualizationContext PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT, destinationFileName string, updateFlags uint32, failureReason *PRJ_UPDATE_FAILURE_CAUSES) uintptr { + sf := GetPointer(destinationFileName) + res, _, _ := prjDeleteFile.Call(uintptr(namespaceVirtualizationContext), sf, uintptr(updateFlags), uintptr(unsafe.Pointer(failureReason))) + return res +} + +func PrjDoesNameContainWildCards(searchExpression uintptr) bool { + b, _, _ := prjDoesNameContainWildCards.Call(searchExpression) + return b != 0 +} + +func PrjFileNameCompare(f1 string, f2 string) int32 { + sf1 := GetPointer(f1) + sf2 := GetPointer(f2) + i1, _, _ := prjFileNameCompare.Call(sf1, sf2) + return int32(i1) +} + +func PrjFileNameMatch(name string, pattern uintptr) bool { + sf1 := GetPointer(name) + i1, _, _ := prjFileNameMatch.Call(sf1, pattern) + return i1 != 0 +} + +func PrjFillDirEntryBuffer(filename string, fileBasicInfo *PRJ_FILE_BASIC_INFO, dirEntryBufferHandle PRJ_DIR_ENTRY_BUFFER_HANDLE) uintptr { + sf1 := GetPointer(filename) + res, _, _ := prjFillDirEntryBuffer.Call(sf1, uintptr(unsafe.Pointer(fileBasicInfo)), uintptr(dirEntryBufferHandle)) + return res +} + +func PrjFillDirEntryBuffer2(dirEntryBufferHandle PRJ_DIR_ENTRY_BUFFER_HANDLE, filename string, fileBasicInfo *PRJ_FILE_BASIC_INFO, extendedInfo *PRJ_EXTENDED_INFO) uintptr { + sf1 := GetPointer(filename) + res, _, _ := prjFillDirEntryBuffer2.Call(uintptr(dirEntryBufferHandle), sf1, uintptr(unsafe.Pointer(fileBasicInfo)), uintptr(unsafe.Pointer(extendedInfo))) + return res +} + +func PrjFreeAlignedBuffer(buffer *any) uintptr { + res, _, _ := prjFreeAlignedBuffer.Call(uintptr(unsafe.Pointer(buffer))) + return res +} + +func PrjGetOnDiskFileState(filename string, fileState *PRJ_FILE_STATE) uintptr { + sf1 := GetPointer(filename) + res, _, _ := prjGetOnDiskFileState.Call(sf1, uintptr(unsafe.Pointer(fileState))) + return res +} + +func PrjMarkDirectoryAsPlaceholder(rootPathName string, targetPathName string, versionInfo *PRJ_PLACEHOLDER_VERSION_INFO, virtualizationInstanceID *syscall.GUID) uintptr { + sf1 := GetPointer(rootPathName) + var sf2 uintptr + if targetPathName != "" { + sf2 = GetPointer(targetPathName) + } + res, _, _ := prjMarkDirectoryAsPlaceholder.Call(sf1, sf2, uintptr(unsafe.Pointer(versionInfo)), uintptr(unsafe.Pointer(virtualizationInstanceID))) + return res +} + +func PrjStartVirtualizing(virtualizationRootPath string, callbacks *PRJ_CALLBACKS, instanceContext any, options *PRJ_STARTVIRTUALIZING_OPTIONS, namespaceVirtualizationContext *PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT) uintptr { + sf1 := GetPointer(virtualizationRootPath) + res, _, _ := prjStartVirtualizing.Call(sf1, uintptr(unsafe.Pointer(callbacks.to_raw())), uintptr(unsafe.Pointer(&instanceContext)), uintptr(unsafe.Pointer(options)), uintptr(unsafe.Pointer(namespaceVirtualizationContext))) + return res +} + +func PrjStopVirtualizing(namespaceVirtualizationContext PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT) { + prjStopVirtualizing.Call(uintptr(namespaceVirtualizationContext)) +} + +func PrjUpdateFileIfNeeded(namespaceVirtualizationContext PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT, destinationFileName string, placeholderInfo *PRJ_PLACEHOLDER_INFO, placeholderInfoSize uint32, updateFlags PRJ_UPDATE_TYPES, failureReason *PRJ_UPDATE_FAILURE_CAUSES) uintptr { + sf1 := GetPointer(destinationFileName) + res, _, _ := prjUpdateFileIfNeeded.Call(uintptr(namespaceVirtualizationContext), sf1, uintptr(unsafe.Pointer(placeholderInfo)), uintptr(placeholderInfoSize), uintptr(updateFlags), uintptr(unsafe.Pointer(failureReason))) + return res +} + +func PrjWriteFileData(namespaceVirtualizationContext PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT, dataStreamId *syscall.GUID, buffer *byte, byteoffset uint64, length uint32) uintptr { + res, _, _ := prjWriteFileData.Call(uintptr(namespaceVirtualizationContext), uintptr(unsafe.Pointer(dataStreamId)), uintptr(unsafe.Pointer(buffer)), uintptr(byteoffset), uintptr(length)) + return res +} + +func PrjWritePlaceholderInfo(namespaceVirtualizationContext PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT, destinationFileName string, placeholderInfo *PRJ_PLACEHOLDER_INFO, placeholderInfoSize uint32) uintptr { + sf1 := GetPointer(destinationFileName) + res, _, _ := prjWritePlaceholderInfo.Call(uintptr(namespaceVirtualizationContext), sf1, uintptr(unsafe.Pointer(placeholderInfo)), uintptr(placeholderInfoSize)) + return res +} + +func PrjWritePlaceholderInfo2(namespaceVirtualizationContext PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT, destinationFileName string, placeholderInfo *PRJ_PLACEHOLDER_INFO, placeholderInfoSize uint32, ExtendedInfo *PRJ_EXTENDED_INFO) uintptr { + sf1 := GetPointer(destinationFileName) + res, _, _ := prjWritePlaceholderInfo2.Call(uintptr(namespaceVirtualizationContext), sf1, uintptr(unsafe.Pointer(placeholderInfo)), uintptr(placeholderInfoSize), uintptr(unsafe.Pointer(ExtendedInfo))) + return res +} diff --git a/projfs/provider.go b/projfs/provider.go new file mode 100644 index 0000000..11063a4 --- /dev/null +++ b/projfs/provider.go @@ -0,0 +1,8 @@ +//go:build windows + +package projfs + +type IProvider interface { + CancelCommand(commandID int32) + StartDirectoryEnumeration() +} diff --git a/projfs/strings.go b/projfs/strings.go new file mode 100644 index 0000000..c1ce7c7 --- /dev/null +++ b/projfs/strings.go @@ -0,0 +1,30 @@ +//go:build windows + +package projfs + +import ( + "syscall" + "unsafe" +) + +func GetPointer(str string) uintptr { + ptr, err := syscall.UTF16PtrFromString(str) + if err != nil { + return 0 + } + return uintptr(unsafe.Pointer(ptr)) +} + +func GetString(str uintptr) string { + p := (*uint16)(unsafe.Pointer(str)) + if p == nil { + return "" + } + end := unsafe.Pointer(p) + n := 0 + for *(*uint16)(end) != 0 { + end = unsafe.Pointer(uintptr(end) + unsafe.Sizeof(*p)) + n++ + } + return syscall.UTF16ToString(unsafe.Slice(p, n)) +} diff --git a/projfs/types.go b/projfs/types.go new file mode 100644 index 0000000..17a3a35 --- /dev/null +++ b/projfs/types.go @@ -0,0 +1,211 @@ +//go:build windows + +package projfs + +import "syscall" +import "C" + +type PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT syscall.Handle +type PRJ_DIR_ENTRY_BUFFER_HANDLE syscall.Handle +type PRJ_COMPLETE_COMMAND_TYPE uint32 +type PRJ_EXT_INFO_TYPE uint32 +type PRJ_FILE_STATE uint32 +type PRJ_NOTIFICATION uint32 +type PRJ_NOTIFY_TYPES uint32 +type PRJ_PLACEHOLDER_ID uint32 +type PRJ_STARTVIRTUALIZING_FLAGS uint32 +type PRJ_UPDATE_FAILURE_CAUSES uint32 +type PRJ_UPDATE_TYPES uint32 +type PRJ_CALLBACK_DATA_FLAGS uint32 + +type PRJ_VIRTUALIZATION_INSTANCE_INFO struct { + InstanceID syscall.GUID + WriteAlignment uint32 +} + +// https://learn.microsoft.com/en-us/windows/win32/api/projectedfslib/ns-projectedfslib-prj_complete_command_extended_parameters +type PRJ_COMPLETE_COMMAND_EXTENDED_PARAMETERS struct { + CommandType uint32 + Data uint32 +} + +const ( + PRJ_COMPLETE_COMMAND_TYPE_NOTIFICATION PRJ_COMPLETE_COMMAND_TYPE = 1 + PRJ_COMPLETE_COMMAND_TYPE_ENUMERATION PRJ_COMPLETE_COMMAND_TYPE = 2 + + PRJ_EXT_INFO_TYPE_SYMLINK PRJ_EXT_INFO_TYPE = 1 + + PRJ_FILE_STATE_PLACEHOLDER PRJ_FILE_STATE = 0x00000001 + PRJ_FILE_STATE_HYDRATED_PLACEHOLDER PRJ_FILE_STATE = 0x00000002 + PRJ_FILE_STATE_DIRTY_PLACEHOLDER PRJ_FILE_STATE = 0x00000004 + PRJ_FILE_STATE_FULL PRJ_FILE_STATE = 0x00000008 + PRJ_FILE_STATE_TOMBSTONE PRJ_FILE_STATE = 0x00000010 + + PRJ_NOTIFICATION_FILE_OPENED PRJ_NOTIFICATION = 0x00000002 + PRJ_NOTIFICATION_NEW_FILE_CREATED PRJ_NOTIFICATION = 0x00000004 + PRJ_NOTIFICATION_FILE_OVERWRITTEN PRJ_NOTIFICATION = 0x00000008 + PRJ_NOTIFICATION_PRE_DELETE PRJ_NOTIFICATION = 0x00000010 + PRJ_NOTIFICATION_PRE_RENAME PRJ_NOTIFICATION = 0x00000020 + PRJ_NOTIFICATION_PRE_SET_HARDLINK PRJ_NOTIFICATION = 0x00000040 + PRJ_NOTIFICATION_FILE_RENAMED PRJ_NOTIFICATION = 0x00000080 + PRJ_NOTIFICATION_HARDLINK_CREATED PRJ_NOTIFICATION = 0x00000100 + PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_NO_MODIFICATION PRJ_NOTIFICATION = 0x00000200 + PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_FILE_MODIFIED PRJ_NOTIFICATION = 0x00000400 + PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_FILE_DELETED PRJ_NOTIFICATION = 0x00000800 + PRJ_NOTIFICATION_FILE_PRE_CONVERT_TO_FULL PRJ_NOTIFICATION = 0x00001000 + + PRJ_NOTIFY_NONE PRJ_NOTIFY_TYPES = 0x00000000 + PRJ_NOTIFY_SUPPRESS_NOTIFICATIONS PRJ_NOTIFY_TYPES = 0x00000001 + PRJ_NOTIFY_FILE_OPENED PRJ_NOTIFY_TYPES = 0x00000002 + PRJ_NOTIFY_NEW_FILE_CREATED PRJ_NOTIFY_TYPES = 0x00000004 + PRJ_NOTIFY_FILE_OVERWRITTEN PRJ_NOTIFY_TYPES = 0x00000008 + PRJ_NOTIFY_PRE_DELETE PRJ_NOTIFY_TYPES = 0x00000010 + PRJ_NOTIFY_PRE_RENAME PRJ_NOTIFY_TYPES = 0x00000020 + PRJ_NOTIFY_PRE_SET_HARDLINK PRJ_NOTIFY_TYPES = 0x00000040 + PRJ_NOTIFY_FILE_RENAMED PRJ_NOTIFY_TYPES = 0x00000080 + PRJ_NOTIFY_HARDLINK_CREATED PRJ_NOTIFY_TYPES = 0x00000100 + PRJ_NOTIFY_FILE_HANDLE_CLOSED_NO_MODIFICATION PRJ_NOTIFY_TYPES = 0x00000200 + PRJ_NOTIFY_FILE_HANDLE_CLOSED_FILE_MODIFIED PRJ_NOTIFY_TYPES = 0x00000400 + PRJ_NOTIFY_FILE_HANDLE_CLOSED_FILE_DELETED PRJ_NOTIFY_TYPES = 0x00000800 + PRJ_NOTIFY_FILE_PRE_CONVERT_TO_FULL PRJ_NOTIFY_TYPES = 0x00001000 + PRJ_NOTIFY_USE_EXISTING_MASK PRJ_NOTIFY_TYPES = 0xFFFFFFFF + + PRJ_PLACEHOLDER_ID_LENGTH PRJ_PLACEHOLDER_ID = 128 + + PRJ_FLAG_NONE PRJ_STARTVIRTUALIZING_FLAGS = 0x00000000 + PRJ_FLAG_USE_NEGATIVE_PATH_CACHE PRJ_STARTVIRTUALIZING_FLAGS = 0x00000001 + + PRJ_UPDATE_FAILURE_CAUSE_NONE PRJ_UPDATE_FAILURE_CAUSES = 0x00000000 + PRJ_UPDATE_FAILURE_CAUSE_DIRTY_METADATA PRJ_UPDATE_FAILURE_CAUSES = 0x00000001 + PRJ_UPDATE_FAILURE_CAUSE_DIRTY_DATA PRJ_UPDATE_FAILURE_CAUSES = 0x00000002 + PRJ_UPDATE_FAILURE_CAUSE_TOMBSTONE PRJ_UPDATE_FAILURE_CAUSES = 0x00000004 + PRJ_UPDATE_FAILURE_CAUSE_READ_ONLY PRJ_UPDATE_FAILURE_CAUSES = 0x00000008 + + PRJ_UPDATE_NONE PRJ_UPDATE_TYPES = 0x00000000 + PRJ_UPDATE_ALLOW_DIRTY_METADATA PRJ_UPDATE_TYPES = 0x00000001 + PRJ_UPDATE_ALLOW_DIRTY_DATA PRJ_UPDATE_TYPES = 0x00000002 + PRJ_UPDATE_ALLOW_TOMBSTONE PRJ_UPDATE_TYPES = 0x00000004 + PRJ_UPDATE_RESERVED1 PRJ_UPDATE_TYPES = 0x00000008 + PRJ_UPDATE_RESERVED2 PRJ_UPDATE_TYPES = 0x00000010 + PRJ_UPDATE_ALLOW_READ_ONLY PRJ_UPDATE_TYPES = 0x00000020 + PRJ_UPDATE_MAX_VAL PRJ_UPDATE_TYPES = 0x00000040 + + PRJ_CB_DATA_FLAG_ENUM_RESTART_SCAN PRJ_CALLBACK_DATA_FLAGS = 0x00000001 + PRJ_CB_DATA_FLAG_ENUM_RETURN_SINGLE_ENTRY PRJ_CALLBACK_DATA_FLAGS = 0x00000002 +) + +type PRJ_CALLBACK_DATA struct { + Size uint32 + Flags PRJ_CALLBACK_DATA_FLAGS + NamespaceVirtualizationContext PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT + CommandId int32 + FileId syscall.GUID + DataStreamId syscall.GUID + FilePathName uintptr + VersionInfo *PRJ_PLACEHOLDER_VERSION_INFO + TriggeringProcessId *uint32 + TriggeringProcessImageFileName uintptr + InstanceContext uintptr +} + +func (data *PRJ_CALLBACK_DATA) GetFilePathName() string { + return GetString(data.FilePathName) +} + +type PRJ_CALLBACKS struct { + StartDirectoryEnumerationCallback PRJ_START_DIRECTORY_ENUMERATION_CB + EndDirectoryEnumerationCallback PRJ_END_DIRECTORY_ENUMERATION_CB + GetDirectoryEnumerationCallback PRJ_GET_DIRECTORY_ENUMERATION_CB + GetPlaceholderInfoCallback PRJ_GET_PLACEHOLDER_INFO_CB + GetFileDataCallback PRJ_GET_FILE_DATA_CB + QueryFileNameCallback PRJ_QUERY_FILE_NAME_CB + NotificationCallback PRJ_NOTIFICATION_CB + CancelCommandCallback PRJ_CANCEL_COMMAND_CB +} + +func (p *PRJ_CALLBACKS) to_raw() *PRJ_CALLBACKS_raw { + return &PRJ_CALLBACKS_raw{ + StartDirectoryEnumerationCallback: syscall.NewCallback(p.StartDirectoryEnumerationCallback), + EndDirectoryEnumerationCallback: syscall.NewCallback(p.EndDirectoryEnumerationCallback), + GetDirectoryEnumerationCallback: syscall.NewCallback(p.GetDirectoryEnumerationCallback), + GetPlaceholderInfoCallback: syscall.NewCallback(p.GetPlaceholderInfoCallback), + GetFileDataCallback: syscall.NewCallback(p.GetFileDataCallback), + QueryFileNameCallback: syscall.NewCallback(p.QueryFileNameCallback), + NotificationCallback: syscall.NewCallback(p.NotificationCallback), + CancelCommandCallback: syscall.NewCallback(p.CancelCommandCallback), + } +} + +type PRJ_CALLBACKS_raw struct { + StartDirectoryEnumerationCallback uintptr + EndDirectoryEnumerationCallback uintptr + GetDirectoryEnumerationCallback uintptr + GetPlaceholderInfoCallback uintptr + GetFileDataCallback uintptr + QueryFileNameCallback uintptr + NotificationCallback uintptr + CancelCommandCallback uintptr +} + +type PRJ_CANCEL_COMMAND_CB func(*PRJ_CALLBACK_DATA) uintptr +type PRJ_END_DIRECTORY_ENUMERATION_CB func(*PRJ_CALLBACK_DATA, *syscall.GUID) uintptr + +type PRJ_EXTENDED_INFO struct { + InfoType PRJ_EXT_INFO_TYPE + NextInfoOffset uint32 + TargetName uintptr +} + +type PRJ_FILE_BASIC_INFO struct { + IsDirectory bool + FileSize int64 + CreationTime syscall.Filetime + LastAccessTime syscall.Filetime + LastWriteTime syscall.Filetime + ChangeTime syscall.Filetime + FileAttributes uint32 +} + +type PRJ_START_DIRECTORY_ENUMERATION_CB func(callbackData *PRJ_CALLBACK_DATA, enumerationId *syscall.GUID) uintptr +type PRJ_GET_DIRECTORY_ENUMERATION_CB func(callbackData *PRJ_CALLBACK_DATA, enumerationId *syscall.GUID, searchExpression uintptr, dirEntryBufferHandle PRJ_DIR_ENTRY_BUFFER_HANDLE) uintptr +type PRJ_GET_FILE_DATA_CB func(callbackData *PRJ_CALLBACK_DATA, byteOffset uint64, length uint32) uintptr +type PRJ_GET_PLACEHOLDER_INFO_CB func(callbackData *PRJ_CALLBACK_DATA) uintptr +type PRJ_NOTIFICATION_CB func(callbackData *PRJ_CALLBACK_DATA, IsDirectory bool, notification PRJ_NOTIFICATION, destinationFileName uintptr, operationParameters *PRJ_NOTIFICATION_PARAMETERS) uintptr +type PRJ_QUERY_FILE_NAME_CB func(callbackData *PRJ_CALLBACK_DATA) uintptr + +type PRJ_NOTIFICATION_MAPPING struct { + NotificationBitMask PRJ_NOTIFY_TYPES + NotificationRoot uintptr +} + +type PRJ_NOTIFICATION_PARAMETERS PRJ_NOTIFY_TYPES +type PRJ_PLACEHOLDER_INFO struct { + FileBasicInfo PRJ_FILE_BASIC_INFO + EaInformation struct { + EaBufferSize uint32 + OffsetToFirstEa uint32 + } + SecurityInformation struct { + SecurityBufferSize uint32 + OffsetToSecurityDescriptor uint32 + } + StreamsInformationstruct struct { + StreamsInfoBufferSize uint32 + OffsetToFirstStreamInfo uint32 + } + VersionInfo PRJ_PLACEHOLDER_VERSION_INFO + VariableData *uint8 +} + +type PRJ_PLACEHOLDER_VERSION_INFO struct { + ProviderID [PRJ_PLACEHOLDER_ID_LENGTH]byte + ContentID [PRJ_PLACEHOLDER_ID_LENGTH]byte +} + +type PRJ_STARTVIRTUALIZING_OPTIONS struct { + Flags PRJ_STARTVIRTUALIZING_FLAGS + PoolThreadCount uint32 + ConcurrentThreadCount uint32 + NotificationMappings *PRJ_NOTIFICATION_MAPPING + NotificationMappingsCount uint32 +}