From dd51cf83426e386871dfa06ddc9583bab9f0eba0 Mon Sep 17 00:00:00 2001 From: Mark Rappoport Date: Sat, 22 Jan 2022 13:16:21 +0200 Subject: [PATCH 1/2] Call getfsstat() and strip mount paths from provided paths when monitoring a device-relative stream (issue 49) --- example/main.go | 26 +++++++++++---- wrap.go | 85 ++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 92 insertions(+), 19 deletions(-) diff --git a/example/main.go b/example/main.go index cf56c06..685feac 100644 --- a/example/main.go +++ b/example/main.go @@ -15,33 +15,45 @@ import ( ) func main() { - path, err := ioutil.TempDir("", "fsexample") + path, err := ioutil.TempDir("/Volumes/test/", "fsexample") if err != nil { log.Fatalf("Failed to create TempDir: %v", err) } + rootDev, err := fsevents.DeviceForPath("/") + if err != nil { + log.Fatalf("Failed to retrieve device for root path: %v", err) + } + dev, err := fsevents.DeviceForPath(path) if err != nil { log.Fatalf("Failed to retrieve device for path: %v", err) } - log.Print(dev) + if dev == rootDev { + dev = 0 + } + log.Println(fsevents.EventIDForDeviceBeforeTime(dev, time.Now())) es := &fsevents.EventStream{ Paths: []string{path}, Latency: 500 * time.Millisecond, Device: dev, - Flags: fsevents.FileEvents | fsevents.WatchRoot} - es.Start() - ec := es.Events + Flags: fsevents.FileEvents | fsevents.WatchRoot | fsevents.NoDefer} + if dev != 0 { + es.Resume = true + es.EventID = fsevents.EventIDForDeviceBeforeTime(dev, time.Now()) + } - log.Println("Device UUID", fsevents.GetDeviceUUID(dev)) + log.Printf("Device UUID %s, device ID: %d, path :%s\n", fsevents.GetDeviceUUID(dev), dev, path) + es.Start() go func() { - for msg := range ec { + for msg := range es.Events { for _, event := range msg { logEvent(event) } } + log.Println("Finished logging events") }() in := bufio.NewReader(os.Stdin) diff --git a/wrap.go b/wrap.go index 95f5583..a015820 100644 --- a/wrap.go +++ b/wrap.go @@ -7,6 +7,11 @@ package fsevents #cgo LDFLAGS: -framework CoreServices #include #include +#include +#include +#include + +int getfsstat(struct statfs *buf, int bufsize, int flags); static CFArrayRef ArrayCreateMutable(int len) { return CFArrayCreateMutable(NULL, len, &kCFTypeArrayCallBacks); @@ -23,6 +28,8 @@ static FSEventStreamRef EventStreamCreate(FSEventStreamContext * context, uintpt context->info = (void*) info; return FSEventStreamCreate(NULL, (FSEventStreamCallback) fsevtCallback, context, paths, since, latency, flags); } + + */ import "C" import ( @@ -31,6 +38,8 @@ import ( "path/filepath" "reflect" "runtime" + "strings" + "syscall" "time" "unsafe" ) @@ -150,22 +159,72 @@ func EventIDForDeviceBeforeTime(dev int32, before time.Time) uint64 { return uint64(C.FSEventsGetLastEventIdForDeviceBeforeTime(C.dev_t(dev), tm)) } +func charsToString(buf []int8) string { + ret := make([]byte, 0, len(buf)) + for _, c := range buf { + if c == 0 { + continue + } + ret = append(ret, byte(c)) + } + return string(ret) +} + +func getRelativePathsForDevices() (map[int32]string, error) { + num, err := syscall.Getfsstat(nil, C.MNT_NOWAIT) + if err != nil { + return nil, err + } + + buf := make([]syscall.Statfs_t, num) + _, err = syscall.Getfsstat(buf, C.MNT_NOWAIT) + if err != nil { + return nil, err + } + + ret := make(map[int32]string) + for _, fs := range buf[:num] { + ret[fs.Fsid.Val[0]] = charsToString(fs.Mntonname[:]) + } + + return ret, nil +} + // createPaths accepts the user defined set of paths and returns FSEvents // compatible array of paths -func createPaths(paths []string) (C.CFArrayRef, error) { - cPaths := C.ArrayCreateMutable(C.int(len(paths))) - var errs []error - for _, path := range paths { - p, err := filepath.Abs(path) - if err != nil { - // hack up some reporting errors, but don't prevent execution - // because of them - errs = append(errs, err) +func createPaths(paths []string, deviceID int32) (C.CFArrayRef, error) { + var ( + cPaths = C.ArrayCreateMutable(C.int(len(paths))) + relativePaths map[int32]string + errs []error + str C.CFStringRef + p, path string + err error + ) + + if deviceID > 0 { + relativePaths, err = getRelativePathsForDevices() + } + for _, path = range paths { + if devicePath, ok := relativePaths[deviceID]; ok { + // Ensure each path is stripped of it's device's mount path + if strings.HasPrefix(filepath.Clean(path), filepath.Clean(devicePath)) { + path = strings.TrimLeft(filepath.Clean(path), filepath.Clean(devicePath)) + } + str = makeCFString(path) + } else { + // Use absolute path + p, err = filepath.Abs(path) + if err != nil { + // hack up some reporting errors, but don't prevent execution + // because of them + errs = append(errs, err) + } + str = makeCFString(p) } - str := makeCFString(p) C.CFArrayAppendValue(C.CFMutableArrayRef(cPaths), unsafe.Pointer(str)) } - var err error + if len(errs) > 0 { err = fmt.Errorf("%q", errs) } @@ -188,7 +247,9 @@ func cfArrayLen(ref C.CFArrayRef) int { } func setupStream(paths []string, flags CreateFlags, callbackInfo uintptr, eventID uint64, latency time.Duration, deviceID int32) FSEventStreamRef { - cPaths, err := createPaths(paths) + + // Use relative paths for devices since the device path is already present; issue #49 + cPaths, err := createPaths(paths, deviceID) if err != nil { log.Printf("Error creating paths: %s", err) } From 1069f93f7f883e98f19e7c2639990f1327d264c1 Mon Sep 17 00:00:00 2001 From: Mark Rappoport Date: Sat, 22 Jan 2022 13:24:26 +0200 Subject: [PATCH 2/2] Remove local volume in example --- example/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/main.go b/example/main.go index 685feac..783fc42 100644 --- a/example/main.go +++ b/example/main.go @@ -15,7 +15,7 @@ import ( ) func main() { - path, err := ioutil.TempDir("/Volumes/test/", "fsexample") + path, err := ioutil.TempDir("", "fsexample") if err != nil { log.Fatalf("Failed to create TempDir: %v", err) }