├── .gitattributes ├── .github └── workflows │ └── elixir.yml ├── .gitignore ├── .travis.yml ├── CNAME ├── LICENSE ├── README.md ├── c_src ├── bsd │ └── main.c ├── mac │ ├── cli.c │ ├── cli.h │ ├── common.h │ ├── compat.c │ ├── compat.h │ └── main.c └── windows │ ├── ArgumentParser.cs │ ├── Arguments.cs │ ├── AssemblyInfo.cs │ ├── Makefile │ └── Runner.cs ├── include └── api.hrl ├── index.html ├── man ├── fs.htm ├── fs_app.htm ├── fs_event_bridge.htm ├── fs_server.htm ├── fs_sup.htm ├── fsevents.htm ├── inotifywait.htm ├── inotifywait_win32.htm └── kqueue.htm ├── mix.exs ├── priv └── inotifywait.exe ├── rebar.config └── src ├── fs.app.src ├── fs.erl ├── fs_app.erl ├── fs_event_bridge.erl ├── fs_server.erl ├── fs_sup.erl └── sys ├── fsevents.erl ├── inotifywait.erl ├── inotifywait_win32.erl └── kqueue.erl /.gitattributes: -------------------------------------------------------------------------------- 1 | *.c linguist-detectable=false 2 | *.cc linguist-detectable=false 3 | Makefile linguist-detectable=false 4 | *.cs linguist-detectable=false 5 | *.html linguist-detectable=false 6 | *.htm linguist-detectable=false 7 | -------------------------------------------------------------------------------- /.github/workflows/elixir.yml: -------------------------------------------------------------------------------- 1 | name: mix 2 | on: push 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - uses: erlef/setup-elixir@v1 9 | with: 10 | otp-version: '26' 11 | elixir-version: 1.16.x 12 | - name: Dependencies 13 | run: | 14 | mix local.rebar --force 15 | mix local.hex --force 16 | mix deps.get 17 | - name: Compilation 18 | run: mix compile 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | deps 2 | priv/mac_listener 3 | *.d 4 | *.o 5 | ebin 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | otp_release: 3 | - 20.0 4 | - 21.0 5 | - 22.0 6 | - 23.0 7 | script: 8 | - "curl -fsSL https://raw.github.com/synrc/mad/master/mad > mad && chmod +x mad && ./mad dep com" 9 | - "rebar3 dialyzer" 10 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | fs.n2o.dev 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DHARMA License 2 | 3 | Copyright (c) 2015—2025 Synrc Research 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | YOU CANNOT USE THIS SOFTWARE IN ANY (PROVABLE BY MONEY TRACE) 10 | PROCESS CHAIN OF EXTERMINATING UKRAINIANS BY ANY MEANS OF FASCIST 11 | ACTIONS AGAINST OUR TERRITORIAL INTEGRITY, CULTURAL DIVERSITY BY 12 | APPLYING MILITARY INVASIONS, ECONOMICAL WARS, HUMANITARIAN DISASTERS, 13 | ARTFICIAL HOLODOMORS, GENOCIDE, RAPING, LOOTING, ROBBERIES, SPREADING 14 | FAKE INFORMATION, AND OTHER CONTEMPORARY WEAPONS OF WAR AT SCALE 15 | OR IN INVIDIVUAL MANNER. 16 | 17 | YOU CANNOT USE THIS SOFTWARE BY ANY MEANS IN INTEREST OF LEGAL 18 | ENTITIES OR INDIVIDUALS WHO IS SUPPORTING NOW OR WAS SUPPORTING 19 | BACK THEN FASCISM, RUSCISM, COMMUNISM, CHAUVINISM, HUMILIATION, 20 | AND OTHER SUPPRESSIVE IDEOLOGIES IN DIFFERENT EXPRESSIONS. 21 | 22 | STOP KILLING UKRAINIANS, 23 | THE COUNTER RENDERS TENS OF MILLIONS. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 26 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 27 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 28 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 29 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 30 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 31 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FS: Native Listener (Mac Windows Linux) 2 | ======================================= 3 | 4 | [![Actions Status](https://github.com/synrc/fs/workflows/mix/badge.svg)](https://github.com/synrc/fs/actions) 5 | [![Hex pm](http://img.shields.io/hexpm/v/fs.svg?style=flat)](https://hex.pm/packages/fs) 6 | 7 | Backends 8 | -------- 9 | 10 | * Mac [fsevent](https://github.com/thibaudgg/rb-fsevent) 11 | * Linux [inotify](https://github.com/rvoicilas/inotify-tools/wiki) 12 | * Windows [inotify-win](https://github.com/thekid/inotify-win) 13 | 14 | NOTE: On Linux you need to install inotify-tools. 15 | 16 | ### Subscribe to Notifications 17 | 18 | ```erlang 19 | > fs:start_link(fs_watcher, "/Users/5HT/synrc/fs"). % need to start the fs watcher 20 | > fs:subscribe(fs_watcher). % the pid will receive events as messages 21 | > flush(). 22 | Shell got {<0.47.0>, 23 | {fs,file_event}, 24 | {"/Users/5HT/synrc/fs/src/README.md",[closed,modified]}} 25 | ``` 26 | 27 | ### List Events from Backend 28 | 29 | ```erlang 30 | > fs:known_events(fs_watcher). % returns events known by your backend 31 | [mustscansubdirs,userdropped,kerneldropped,eventidswrapped, 32 | historydone,rootchanged,mount,unmount,created,removed, 33 | inodemetamod,renamed,modified,finderinfomod,changeowner, 34 | xattrmod,isfile,isdir,issymlink,ownevent] 35 | ``` 36 | 37 | ### Sample Subscriber 38 | 39 | ```erlang 40 | > fs:start_looper(). % starts a sample process that logs events 41 | =INFO REPORT==== 28-Aug-2013::19:36:26 === 42 | file_event: "/tank/proger/erlfsmon/src/4913" [closed,modified] 43 | ``` 44 | 45 | Credits 46 | ------- 47 | 48 | * Vladimir Kirillov 49 | * Maxim Sokhatsky 50 | * Dominic Letz 51 | 52 | OM A HUM 53 | -------------------------------------------------------------------------------- /c_src/bsd/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main(int argc, char *argv[]) { 9 | struct kevent event; 10 | struct kevent change; 11 | int fd, kq, nev; 12 | if ((fd = open(argv[1], O_RDONLY)) == -1) return 1; 13 | EV_SET(&change, fd, EVFILT_VNODE , EV_ADD 14 | | EV_CLEAR 15 | | EV_DELETE 16 | | EV_EOF 17 | | EV_DISPATCH 18 | | EV_ONESHOT, 19 | NOTE_DELETE 20 | | NOTE_RENAME 21 | | NOTE_EXTEND 22 | | NOTE_ATTRIB 23 | | NOTE_LINK 24 | | NOTE_REVOKE 25 | | NOTE_WRITE, 0, 0); 26 | if ((kq = kqueue()) == -1) return 1; 27 | nev = kevent(kq, &change, 1, &event, 1, NULL); 28 | if (nev < 0) { return 1; } else if (nev > 0) { if (event.flags & EV_ERROR) { return 1; } } 29 | close(kq); 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /c_src/mac/cli.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "cli.h" 3 | 4 | const char* cli_info_purpose = "A flexible command-line interface for the FSEvents API"; 5 | const char* cli_info_usage = "Usage: fsevent_watch [OPTIONS]... [PATHS]..."; 6 | const char* cli_info_help[] = { 7 | " -h, --help you're looking at it", 8 | " -V, --version print version number and exit", 9 | " -p, --show-plist display the embedded Info.plist values", 10 | " -s, --since-when=EventID fire historical events since ID", 11 | " -l, --latency=seconds latency period (default='0.5')", 12 | " -n, --no-defer enable no-defer latency modifier", 13 | " -r, --watch-root watch for when the root path has changed", 14 | // " -i, --ignore-self ignore current process", 15 | " -F, --file-events provide file level event data", 16 | " -f, --format=name output format (ignored)", 17 | 0 18 | }; 19 | 20 | static void default_args (struct cli_info* args_info) 21 | { 22 | args_info->since_when_arg = kFSEventStreamEventIdSinceNow; 23 | args_info->latency_arg = 0.5; 24 | args_info->no_defer_flag = false; 25 | args_info->watch_root_flag = false; 26 | args_info->ignore_self_flag = false; 27 | args_info->file_events_flag = false; 28 | args_info->mark_self_flag = false; 29 | args_info->format_arg = 0; 30 | } 31 | 32 | static void cli_parser_release (struct cli_info* args_info) 33 | { 34 | unsigned int i; 35 | 36 | for (i=0; i < args_info->inputs_num; ++i) { 37 | free(args_info->inputs[i]); 38 | } 39 | 40 | if (args_info->inputs_num) { 41 | free(args_info->inputs); 42 | } 43 | 44 | args_info->inputs_num = 0; 45 | } 46 | 47 | void cli_parser_init (struct cli_info* args_info) 48 | { 49 | default_args(args_info); 50 | 51 | args_info->inputs = 0; 52 | args_info->inputs_num = 0; 53 | } 54 | 55 | void cli_parser_free (struct cli_info* args_info) 56 | { 57 | cli_parser_release(args_info); 58 | } 59 | 60 | static void cli_print_info_dict (const void *key, 61 | const void *value, 62 | void *context) 63 | { 64 | CFStringRef entry = CFStringCreateWithFormat(NULL, NULL, 65 | CFSTR("%@:\n %@"), key, value); 66 | if (entry) { 67 | CFShow(entry); 68 | CFRelease(entry); 69 | } 70 | } 71 | 72 | void cli_show_plist (void) 73 | { 74 | CFBundleRef mainBundle = CFBundleGetMainBundle(); 75 | CFRetain(mainBundle); 76 | CFDictionaryRef mainBundleDict = CFBundleGetInfoDictionary(mainBundle); 77 | if (mainBundleDict) { 78 | CFRetain(mainBundleDict); 79 | printf("Embedded Info.plist metadata:\n\n"); 80 | CFDictionaryApplyFunction(mainBundleDict, cli_print_info_dict, NULL); 81 | CFRelease(mainBundleDict); 82 | } 83 | CFRelease(mainBundle); 84 | printf("\n"); 85 | } 86 | 87 | void cli_print_version (void) 88 | { 89 | printf("%s %s\n\n", "VXZ", "1.0"); 90 | } 91 | 92 | void cli_print_help (void) 93 | { 94 | cli_print_version(); 95 | 96 | printf("\n%s\n", cli_info_purpose); 97 | printf("\n%s\n", cli_info_usage); 98 | printf("\n"); 99 | 100 | int i = 0; 101 | while (cli_info_help[i]) { 102 | printf("%s\n", cli_info_help[i++]); 103 | } 104 | } 105 | 106 | int cli_parser (int argc, const char** argv, struct cli_info* args_info) 107 | { 108 | static struct option longopts[] = { 109 | { "help", no_argument, NULL, 'h' }, 110 | { "version", no_argument, NULL, 'V' }, 111 | { "show-plist", no_argument, NULL, 'p' }, 112 | { "since-when", required_argument, NULL, 's' }, 113 | { "latency", required_argument, NULL, 'l' }, 114 | { "no-defer", no_argument, NULL, 'n' }, 115 | { "watch-root", no_argument, NULL, 'r' }, 116 | { "ignore-self", no_argument, NULL, 'i' }, 117 | { "file-events", no_argument, NULL, 'F' }, 118 | { "mark-self", no_argument, NULL, 'm' }, 119 | { "format", required_argument, NULL, 'f' }, 120 | { 0, 0, 0, 0 } 121 | }; 122 | 123 | const char* shortopts = "hVps:l:nriFf:"; 124 | 125 | int c = -1; 126 | 127 | while ((c = getopt_long(argc, (char * const*)argv, shortopts, longopts, NULL)) != -1) { 128 | switch(c) { 129 | case 's': // since-when 130 | args_info->since_when_arg = strtoull(optarg, NULL, 0); 131 | break; 132 | case 'l': // latency 133 | args_info->latency_arg = strtod(optarg, NULL); 134 | break; 135 | case 'n': // no-defer 136 | args_info->no_defer_flag = true; 137 | break; 138 | case 'r': // watch-root 139 | args_info->watch_root_flag = true; 140 | break; 141 | case 'i': // ignore-self 142 | args_info->ignore_self_flag = true; 143 | break; 144 | case 'F': // file-events 145 | args_info->file_events_flag = true; 146 | break; 147 | case 'm': // mark-self 148 | args_info->mark_self_flag = true; 149 | break; 150 | case 'f': // format 151 | // XXX: ignored 152 | break; 153 | case 'V': // version 154 | cli_print_version(); 155 | exit(EXIT_SUCCESS); 156 | case 'p': // show-plist 157 | cli_show_plist(); 158 | exit(EXIT_SUCCESS); 159 | case 'h': // help 160 | case '?': // invalid option 161 | case ':': // missing argument 162 | cli_print_help(); 163 | exit((c == 'h') ? EXIT_SUCCESS : EXIT_FAILURE); 164 | } 165 | } 166 | 167 | if (optind < argc) { 168 | int i = 0; 169 | args_info->inputs_num = (unsigned int)(argc - optind); 170 | args_info->inputs = 171 | (char**)(malloc ((args_info->inputs_num)*sizeof(char*))); 172 | while (optind < argc) 173 | if (argv[optind++] != argv[0]) { 174 | args_info->inputs[i++] = strdup(argv[optind-1]); 175 | } 176 | } 177 | 178 | return EXIT_SUCCESS; 179 | } 180 | 181 | -------------------------------------------------------------------------------- /c_src/mac/cli.h: -------------------------------------------------------------------------------- 1 | #ifndef CLI_H 2 | #define CLI_H 3 | 4 | #include "common.h" 5 | 6 | #ifndef CLI_NAME 7 | #define CLI_NAME "fsevent_watch" 8 | #endif /* CLI_NAME */ 9 | 10 | struct cli_info { 11 | UInt64 since_when_arg; 12 | double latency_arg; 13 | bool no_defer_flag; 14 | bool watch_root_flag; 15 | bool ignore_self_flag; 16 | bool file_events_flag; 17 | bool mark_self_flag; 18 | int format_arg; 19 | 20 | char** inputs; 21 | unsigned inputs_num; 22 | }; 23 | 24 | extern const char* cli_info_purpose; 25 | extern const char* cli_info_usage; 26 | extern const char* cli_info_help[]; 27 | 28 | void cli_print_help(void); 29 | void cli_print_version(void); 30 | 31 | int cli_parser (int argc, const char** argv, struct cli_info* args_info); 32 | void cli_parser_init (struct cli_info* args_info); 33 | void cli_parser_free (struct cli_info* args_info); 34 | 35 | 36 | #endif /* CLI_H */ 37 | -------------------------------------------------------------------------------- /c_src/mac/common.h: -------------------------------------------------------------------------------- 1 | #ifndef fsevent_watch_common_h 2 | #define fsevent_watch_common_h 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "compat.h" 9 | 10 | #define _str(s) #s 11 | #define _xstr(s) _str(s) 12 | 13 | #define COMPILED_AT __DATE__ " " __TIME__ 14 | 15 | #define FPRINTF_FLAG_CHECK(flags, flag, msg, fd) \ 16 | do { \ 17 | if ((flags) & (flag)) { \ 18 | fprintf(fd, "%s\n", msg); } } \ 19 | while (0) 20 | 21 | #define FLAG_CHECK_STDERR(flags, flag, msg) \ 22 | FPRINTF_FLAG_CHECK(flags, flag, msg, stderr) 23 | 24 | /* 25 | * FSEVENTSBITS: 26 | * generated by `make printflags` (and pasted here) 27 | * flags MUST be ordered (bits ascending) and sorted 28 | * 29 | * idea from: http://www.openbsd.org/cgi-bin/cvsweb/src/sbin/ifconfig/ifconfig.c (see printb()) 30 | */ 31 | #define FSEVENTSBITS \ 32 | "\1mustscansubdirs\2userdropped\3kerneldropped\4eventidswrapped\5historydone\6rootchanged\7mount\10unmount\11created\12removed\13inodemetamod\14renamed\15modified\16finderinfomod\17changeowner\20xattrmod\21isfile\22isdir\23issymlink\24ownevent" 33 | 34 | static inline void 35 | sprintb(char *buf, unsigned short v, char *bits) 36 | { 37 | int i, any = 0; 38 | char c; 39 | char *bufp = buf; 40 | 41 | while ((i = *bits++)) { 42 | if (v & (1 << (i-1))) { 43 | if (any) 44 | *bufp++ = ','; 45 | any = 1; 46 | for (; (c = *bits) > 32; bits++) 47 | *bufp++ = c; 48 | } else 49 | for (; *bits > 32; bits++) 50 | ; 51 | } 52 | *bufp = '\0'; 53 | } 54 | 55 | #endif /* fsevent_watch_common_h */ 56 | -------------------------------------------------------------------------------- /c_src/mac/compat.c: -------------------------------------------------------------------------------- 1 | #include "compat.h" 2 | 3 | #if MAC_OS_X_VERSION_MAX_ALLOWED < 1060 4 | FSEventStreamCreateFlags kFSEventStreamCreateFlagIgnoreSelf = 0x00000008; 5 | #endif 6 | 7 | #if MAC_OS_X_VERSION_MAX_ALLOWED < 1070 8 | FSEventStreamCreateFlags kFSEventStreamCreateFlagFileEvents = 0x00000010; 9 | FSEventStreamEventFlags kFSEventStreamEventFlagItemCreated = 0x00000100; 10 | FSEventStreamEventFlags kFSEventStreamEventFlagItemRemoved = 0x00000200; 11 | FSEventStreamEventFlags kFSEventStreamEventFlagItemInodeMetaMod = 0x00000400; 12 | FSEventStreamEventFlags kFSEventStreamEventFlagItemRenamed = 0x00000800; 13 | FSEventStreamEventFlags kFSEventStreamEventFlagItemModified = 0x00001000; 14 | FSEventStreamEventFlags kFSEventStreamEventFlagItemFinderInfoMod = 0x00002000; 15 | FSEventStreamEventFlags kFSEventStreamEventFlagItemChangeOwner = 0x00004000; 16 | FSEventStreamEventFlags kFSEventStreamEventFlagItemXattrMod = 0x00008000; 17 | FSEventStreamEventFlags kFSEventStreamEventFlagItemIsFile = 0x00010000; 18 | FSEventStreamEventFlags kFSEventStreamEventFlagItemIsDir = 0x00020000; 19 | FSEventStreamEventFlags kFSEventStreamEventFlagItemIsSymlink = 0x00040000; 20 | #endif 21 | 22 | #if MAC_OS_X_VERSION_MAX_ALLOWED < 1090 23 | FSEventStreamCreateFlags kFSEventStreamCreateFlagMarkSelf = 0x00000020; 24 | FSEventStreamEventFlags kFSEventStreamEventFlagOwnEvent = 0x00080000; 25 | #endif 26 | -------------------------------------------------------------------------------- /c_src/mac/compat.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @headerfile compat.h 3 | * FSEventStream flag compatibility shim 4 | * 5 | * In order to compile a binary against an older SDK yet still support the 6 | * features present in later OS releases, we need to define any missing enum 7 | * constants not present in the older SDK. This allows us to safely defer 8 | * feature detection to runtime (and avoid recompilation). 9 | */ 10 | 11 | 12 | #ifndef fsevent_watch_compat_h 13 | #define fsevent_watch_compat_h 14 | 15 | #ifndef __CORESERVICES__ 16 | #include 17 | #endif // __CORESERVICES__ 18 | 19 | #if MAC_OS_X_VERSION_MAX_ALLOWED < 1060 20 | // ignoring events originating from the current process introduced in 10.6 21 | extern FSEventStreamCreateFlags kFSEventStreamCreateFlagIgnoreSelf; 22 | #endif 23 | 24 | #if MAC_OS_X_VERSION_MAX_ALLOWED < 1070 25 | // file-level events introduced in 10.7 26 | extern FSEventStreamCreateFlags kFSEventStreamCreateFlagFileEvents; 27 | extern FSEventStreamEventFlags kFSEventStreamEventFlagItemCreated, 28 | kFSEventStreamEventFlagItemRemoved, 29 | kFSEventStreamEventFlagItemInodeMetaMod, 30 | kFSEventStreamEventFlagItemRenamed, 31 | kFSEventStreamEventFlagItemModified, 32 | kFSEventStreamEventFlagItemFinderInfoMod, 33 | kFSEventStreamEventFlagItemChangeOwner, 34 | kFSEventStreamEventFlagItemXattrMod, 35 | kFSEventStreamEventFlagItemIsFile, 36 | kFSEventStreamEventFlagItemIsDir, 37 | kFSEventStreamEventFlagItemIsSymlink; 38 | #endif 39 | 40 | #if MAC_OS_X_VERSION_MAX_ALLOWED < 1090 41 | // marking, rather than ignoring, events originating from the current process introduced in 10.9 42 | extern FSEventStreamCreateFlags kFSEventStreamCreateFlagMarkSelf; 43 | extern FSEventStreamEventFlags kFSEventStreamEventFlagOwnEvent; 44 | #endif 45 | 46 | 47 | #endif // fsevent_watch_compat_h 48 | -------------------------------------------------------------------------------- /c_src/mac/main.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "cli.h" 3 | #include 4 | #include 5 | #include 6 | 7 | // TODO: set on fire. cli.{h,c} handle both parsing and defaults, so there's 8 | // no need to set those here. also, in order to scope metadata by path, 9 | // each stream will need its own configuration... so this won't work as 10 | // a global any more. In the end the goal is to make the output format 11 | // able to declare not just that something happened and what flags were 12 | // attached, but what path it was watching that caused those events (so 13 | // that the path itself can be used for routing that information to the 14 | // relevant callback). 15 | // 16 | // Structure for storing metadata parsed from the commandline 17 | static struct { 18 | FSEventStreamEventId sinceWhen; 19 | CFTimeInterval latency; 20 | FSEventStreamCreateFlags flags; 21 | CFMutableArrayRef paths; 22 | int format; 23 | } config = { 24 | (UInt64) kFSEventStreamEventIdSinceNow, 25 | (double) 0.3, 26 | (CFOptionFlags) kFSEventStreamCreateFlagNone, 27 | NULL, 28 | 0 29 | }; 30 | 31 | // Prototypes 32 | static void append_path(const char* path); 33 | static inline void parse_cli_settings(int argc, const char* argv[]); 34 | static void callback(FSEventStreamRef streamRef, 35 | void* clientCallBackInfo, 36 | size_t numEvents, 37 | void* eventPaths, 38 | const FSEventStreamEventFlags eventFlags[], 39 | const FSEventStreamEventId eventIds[]); 40 | 41 | 42 | static void append_path(const char* path) 43 | { 44 | CFStringRef pathRef = CFStringCreateWithCString(kCFAllocatorDefault, 45 | path, 46 | kCFStringEncodingUTF8); 47 | CFArrayAppendValue(config.paths, pathRef); 48 | CFRelease(pathRef); 49 | } 50 | 51 | // Parse commandline settings 52 | static inline void parse_cli_settings(int argc, const char* argv[]) 53 | { 54 | // runtime os version detection 55 | char buf[25]; 56 | size_t buflen = 25; 57 | sysctlbyname("kern.osproductversion", &buf, &buflen, NULL, 0); 58 | int osMajorVersion, osMinorVersion; 59 | int res = sscanf(buf, "%d.%d", &osMajorVersion, &osMinorVersion); 60 | if (res != 2) 61 | { 62 | osMajorVersion = osMinorVersion = 0; 63 | } 64 | 65 | if ((osMajorVersion == 10) & (osMinorVersion < 5)) { 66 | fprintf(stderr, "The FSEvents API is unavailable on this version of macos!\n"); 67 | exit(EXIT_FAILURE); 68 | } 69 | 70 | struct cli_info args_info; 71 | cli_parser_init(&args_info); 72 | 73 | if (cli_parser(argc, argv, &args_info) != 0) { 74 | exit(EXIT_FAILURE); 75 | } 76 | 77 | config.paths = CFArrayCreateMutable(NULL, 78 | (CFIndex)0, 79 | &kCFTypeArrayCallBacks); 80 | 81 | config.sinceWhen = args_info.since_when_arg; 82 | config.latency = args_info.latency_arg; 83 | config.format = args_info.format_arg; 84 | 85 | if (args_info.no_defer_flag) { 86 | config.flags |= kFSEventStreamCreateFlagNoDefer; 87 | } 88 | if (args_info.watch_root_flag) { 89 | config.flags |= kFSEventStreamCreateFlagWatchRoot; 90 | } 91 | 92 | if (args_info.ignore_self_flag) { 93 | if ((osMajorVersion < 10) || ((osMajorVersion == 10) && (osMinorVersion >= 6))) { 94 | fprintf(stderr, "MacOSX 10.6 or later is required for --ignore-self\n"); 95 | exit(EXIT_FAILURE); 96 | } else { 97 | config.flags |= kFSEventStreamCreateFlagIgnoreSelf; 98 | } 99 | } 100 | 101 | if (args_info.file_events_flag) { 102 | if ((osMajorVersion < 10) || ((osMajorVersion == 10) && (osMinorVersion < 7))) { 103 | fprintf(stderr, "MacOSX 10.7 or later required for --file-events\n"); 104 | exit(EXIT_FAILURE); 105 | } else { 106 | config.flags |= kFSEventStreamCreateFlagFileEvents; 107 | } 108 | } 109 | 110 | if (args_info.mark_self_flag) { 111 | if ((osMajorVersion < 10) || ((osMajorVersion == 10) && (osMinorVersion < 9))) { 112 | fprintf(stderr, "MacOSX 10.9 or later required for --mark-self\n"); 113 | exit(EXIT_FAILURE); 114 | } else { 115 | config.flags |= kFSEventStreamCreateFlagMarkSelf; 116 | } 117 | } 118 | 119 | if (args_info.inputs_num == 0) { 120 | append_path("."); 121 | } else { 122 | for (unsigned int i=0; i < args_info.inputs_num; ++i) { 123 | append_path(args_info.inputs[i]); 124 | } 125 | } 126 | 127 | cli_parser_free(&args_info); 128 | 129 | #ifdef DEBUG 130 | fprintf(stderr, "config.sinceWhen %llu\n", config.sinceWhen); 131 | fprintf(stderr, "config.latency %f\n", config.latency); 132 | fprintf(stderr, "config.flags %#.8x\n", config.flags); 133 | 134 | FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagUseCFTypes, 135 | " Using CF instead of C types"); 136 | FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagNoDefer, 137 | " NoDefer latency modifier enabled"); 138 | FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagWatchRoot, 139 | " WatchRoot notifications enabled"); 140 | FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagIgnoreSelf, 141 | " IgnoreSelf enabled"); 142 | FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagFileEvents, 143 | " FileEvents enabled"); 144 | 145 | fprintf(stderr, "config.paths\n"); 146 | 147 | long numpaths = CFArrayGetCount(config.paths); 148 | 149 | for (long i = 0; i < numpaths; i++) { 150 | char path[PATH_MAX]; 151 | CFStringGetCString(CFArrayGetValueAtIndex(config.paths, i), 152 | path, 153 | PATH_MAX, 154 | kCFStringEncodingUTF8); 155 | fprintf(stderr, " %s\n", path); 156 | } 157 | 158 | fprintf(stderr, "\n"); 159 | #endif 160 | } 161 | 162 | static void callback(__attribute__((unused)) FSEventStreamRef streamRef, 163 | __attribute__((unused)) void* clientCallBackInfo, 164 | size_t numEvents, 165 | void* eventPaths, 166 | const FSEventStreamEventFlags eventFlags[], 167 | const FSEventStreamEventId eventIds[]) 168 | { 169 | char** paths = eventPaths; 170 | char *buf = calloc(sizeof(FSEVENTSBITS), sizeof(char)); 171 | 172 | for (size_t i = 0; i < numEvents; i++) { 173 | sprintb(buf, eventFlags[i], FSEVENTSBITS); 174 | printf("%llu\t%#.8x=[%s]\t%s\n", eventIds[i], eventFlags[i], buf, paths[i]); 175 | } 176 | fflush(stdout); 177 | free(buf); 178 | 179 | if (fcntl(STDIN_FILENO, F_GETFD) == -1) { 180 | CFRunLoopStop(CFRunLoopGetCurrent()); 181 | } 182 | } 183 | 184 | static void stdin_callback(CFFileDescriptorRef fdref, CFOptionFlags callBackTypes, void *info) 185 | { 186 | char buf[1024]; 187 | int nread; 188 | 189 | do { 190 | nread = read(STDIN_FILENO, buf, sizeof(buf)); 191 | if (nread == -1 && errno == EAGAIN) { 192 | CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack); 193 | return; 194 | } else if (nread == 0) { 195 | exit(1); 196 | return; 197 | } 198 | } while (nread > 0); 199 | } 200 | 201 | int main(int argc, const char* argv[]) 202 | { 203 | parse_cli_settings(argc, argv); 204 | 205 | FSEventStreamContext context = {0, NULL, NULL, NULL, NULL}; 206 | FSEventStreamRef stream; 207 | stream = FSEventStreamCreate(kCFAllocatorDefault, 208 | (FSEventStreamCallback)&callback, 209 | &context, 210 | config.paths, 211 | config.sinceWhen, 212 | config.latency, 213 | config.flags); 214 | 215 | #ifdef DEBUG 216 | FSEventStreamShow(stream); 217 | fprintf(stderr, "\n"); 218 | #endif 219 | 220 | fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); 221 | 222 | CFFileDescriptorRef fdref = CFFileDescriptorCreate(kCFAllocatorDefault, STDIN_FILENO, false, stdin_callback, NULL); 223 | CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack); 224 | CFRunLoopSourceRef source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, fdref, 0); 225 | CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); 226 | CFRelease(source); 227 | 228 | FSEventStreamScheduleWithRunLoop(stream, 229 | CFRunLoopGetCurrent(), 230 | kCFRunLoopDefaultMode); 231 | FSEventStreamStart(stream); 232 | CFRunLoopRun(); 233 | FSEventStreamFlushSync(stream); 234 | FSEventStreamStop(stream); 235 | 236 | return 0; 237 | } 238 | 239 | // vim: ts=2 sts=2 et sw=2 240 | -------------------------------------------------------------------------------- /c_src/windows/ArgumentParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Collections.Generic; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace De.Thekid.INotify 7 | { 8 | 9 | /// See also inotifywait(1) - Linux man page 10 | public class ArgumentParser 11 | { 12 | 13 | /// Helper method for parser 14 | protected string Value(string[] args, int i, string name) 15 | { 16 | if (i > args.Length) 17 | { 18 | throw new ArgumentException("Argument " + name + " requires a value"); 19 | } 20 | return args[i]; 21 | } 22 | 23 | /// Tokenizes "printf" format string into an array of strings 24 | protected string[] TokenizeFormat(string arg) 25 | { 26 | var result = new List(); 27 | var tokens = arg.Split(new char[]{ '%' }); 28 | foreach (var token in tokens) 29 | { 30 | if (token.Length == 0) continue; 31 | 32 | if ("efwT".IndexOf(token[0]) != -1) 33 | { 34 | result.Add(token[0].ToString()); 35 | if (token.Length > 1) 36 | { 37 | result.Add(token.Substring(1)); 38 | } 39 | } 40 | else 41 | { 42 | result.Add(token); 43 | } 44 | } 45 | return result.ToArray(); 46 | } 47 | 48 | private void ParseArgument(string option, string[] args, ref int i, Arguments result) 49 | { 50 | if ("--recursive" == option || "-r" == option) 51 | { 52 | result.Recursive = true; 53 | } 54 | else if ("--monitor" == option || "-m" == option) 55 | { 56 | result.Monitor = true; 57 | } 58 | else if ("--quiet" == option || "-q" == option) 59 | { 60 | result.Quiet = true; 61 | } 62 | else if ("--event" == option || "-e" == option) 63 | { 64 | result.Events = new List(Value(args, ++i, "event").Split(',')); 65 | } 66 | else if ("--format" == option) 67 | { 68 | result.Format = TokenizeFormat(Value(args, ++i, "format")); 69 | } 70 | else if ("--exclude" == option) 71 | { 72 | result.Exclude = new Regex(Value(args, ++i, "exclude")); 73 | } 74 | else if ("--excludei" == option) 75 | { 76 | result.Exclude = new Regex(Value(args, ++i, "exclude"), RegexOptions.IgnoreCase); 77 | } 78 | else if (option.StartsWith("--event=")) 79 | { 80 | result.Events = new List(option.Split(new Char[]{'='}, 2)[1].Split(',')); 81 | } 82 | else if (option.StartsWith("--format=")) 83 | { 84 | result.Format = TokenizeFormat(option.Split(new Char[]{'='}, 2)[1]); 85 | } 86 | else if (option.StartsWith("--exclude=")) 87 | { 88 | result.Exclude = new Regex(option.Split(new Char[]{'='}, 2)[1]); 89 | } 90 | else if (option.StartsWith("--excludei=")) 91 | { 92 | result.Exclude = new Regex(option.Split(new Char[]{'='}, 2)[1], RegexOptions.IgnoreCase); 93 | } 94 | else if (Directory.Exists(option)) 95 | { 96 | result.Paths.Add(System.IO.Path.GetFullPath(option)); 97 | } 98 | } 99 | 100 | /// Creates a new argument parser and parses the arguments 101 | public Arguments Parse(string[] args) 102 | { 103 | var result = new Arguments(); 104 | for (var i = 0; i < args.Length; i++) 105 | { 106 | if (!args[i].StartsWith("--") && args[i].StartsWith("-") && args[i].Length > 2) 107 | { 108 | string options = args[i]; 109 | for (var j = 1; j < options.Length; j++) 110 | { 111 | ParseArgument("-" + options.Substring(j, 1), args, ref i, result); 112 | } 113 | } 114 | else 115 | { 116 | ParseArgument(args[i], args, ref i, result); 117 | } 118 | } 119 | return result; 120 | } 121 | 122 | /// Usage 123 | public void PrintUsage(string name, TextWriter writer) 124 | { 125 | writer.WriteLine("Usage: " + name + " [options] path [...]"); 126 | writer.WriteLine(); 127 | writer.WriteLine("Options:"); 128 | writer.WriteLine("-r/--recursive: Recursively watch all files and subdirectories inside path"); 129 | writer.WriteLine("-m/--monitor: Keep running until killed (e.g. via Ctrl+C)"); 130 | writer.WriteLine("-q/--quiet: Do not output information about actions"); 131 | writer.WriteLine("-e/--event list: Which events (create, modify, delete, move) to watch, comma-separated. Default: all"); 132 | writer.WriteLine("--format format: Format string for output."); 133 | writer.WriteLine("--exclude: Do not process any events whose filename matches the specified regex"); 134 | writer.WriteLine("--excludei: Ditto, case-insensitive"); 135 | writer.WriteLine(); 136 | writer.WriteLine("Formats:"); 137 | writer.WriteLine("%e : Event name"); 138 | writer.WriteLine("%f : File name"); 139 | writer.WriteLine("%w : Path name"); 140 | writer.WriteLine("%T : Current date and time"); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /c_src/windows/Arguments.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace De.Thekid.INotify 6 | { 7 | 8 | public class Arguments 9 | { 10 | // Default values 11 | private List _Events = new List(new string[] { "create", "modify", "delete", "move" }); 12 | private string[] _Format = new string[] { "w", " ", "e", " ", "f" }; 13 | private List _Paths = new List(); 14 | 15 | public bool Recursive { get; set; } 16 | public bool Monitor { get; set; } 17 | public bool Quiet { get; set; } 18 | public List Paths { 19 | get { return this._Paths; } 20 | } 21 | public string[] Format { 22 | get { return this._Format; } 23 | set { this._Format = value; } 24 | } 25 | public List Events 26 | { 27 | get { return this._Events; } 28 | set { this._Events = value; } 29 | } 30 | public Regex Exclude { get; set; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /c_src/windows/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | 6 | [assembly: AssemblyTitle("https://github.com/thekid/inotify-win")] 7 | [assembly: AssemblyDescription("A port of the inotifywait tool for Windows")] 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("Timm Friebe")] 10 | [assembly: AssemblyProduct("inotify-win")] 11 | [assembly: AssemblyCopyright("Copyright © 2012 - 2015 Timm Friebe")] 12 | [assembly: AssemblyTrademark("")] 13 | [assembly: AssemblyCulture("")] 14 | [assembly: AssemblyVersion("1.5.1.0")] 15 | [assembly: ComVisible(false)] 16 | [assembly: Guid("4254314b-ae21-4e2f-ba52-d6f3d83a86b5")] 17 | -------------------------------------------------------------------------------- /c_src/windows/Makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(OS),Windows_NT) 2 | BASE=$(shell cd "$(WINDIR)";pwd -W) 3 | CSC?=$(shell ls -1d $(BASE)/Microsoft.NET/Framework/v*|sort -rn|head -1)/csc.exe 4 | else 5 | CSC?=csc 6 | endif 7 | 8 | MKDIR_P = mkdir -p 9 | 10 | inotifywait.exe: c_src/windows/*.cs 11 | ${MKDIR_P} priv 12 | $(CSC) //nologo //target:exe //out:priv\\$@ c_src\\windows\\*.cs 13 | 14 | clean: 15 | -rm priv\\inotifywait.exe 16 | -------------------------------------------------------------------------------- /c_src/windows/Runner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.IO; 4 | using System.Collections.Generic; 5 | 6 | namespace De.Thekid.INotify 7 | { 8 | // List of possible changes 9 | public enum Change 10 | { 11 | CREATE, MODIFY, DELETE, MOVED_FROM, MOVED_TO 12 | } 13 | 14 | /// Main class 15 | public class Runner 16 | { 17 | // Mappings 18 | protected static Dictionary Changes = new Dictionary(); 19 | 20 | private List _threads = new List(); 21 | private bool _stopMonitoring = false; 22 | private ManualResetEventSlim _stopMonitoringEvent; 23 | private object _notificationReactionLock = new object(); 24 | private Arguments _args = null; 25 | 26 | static Runner() 27 | { 28 | Changes[WatcherChangeTypes.Created]= Change.CREATE; 29 | Changes[WatcherChangeTypes.Changed]= Change.MODIFY; 30 | Changes[WatcherChangeTypes.Deleted]= Change.DELETE; 31 | } 32 | 33 | public Runner(Arguments args) 34 | { 35 | _args = args; 36 | } 37 | 38 | /// Callback for errors in watcher 39 | protected void OnWatcherError(object source, ErrorEventArgs e) 40 | { 41 | Console.Error.WriteLine("*** {0}", e.GetException()); 42 | } 43 | 44 | private void OnWatcherNotification(object sender, FileSystemEventArgs e) 45 | { 46 | FileSystemWatcher w = (FileSystemWatcher)sender; 47 | HandleNotification(w, e, () => Output(Console.Out, _args.Format, w, Changes[e.ChangeType], e.Name)); 48 | } 49 | 50 | private void OnRenameNotification(object sender, RenamedEventArgs e) 51 | { 52 | FileSystemWatcher w = (FileSystemWatcher)sender; 53 | HandleNotification(w, e, () => 54 | { 55 | Output(Console.Out, _args.Format, w, Change.MOVED_FROM, e.OldName); 56 | Output(Console.Out, _args.Format, w, Change.MOVED_TO, e.Name); 57 | }); 58 | } 59 | 60 | private void HandleNotification(FileSystemWatcher sender, FileSystemEventArgs e, Action outputAction) 61 | { 62 | FileSystemWatcher w = (FileSystemWatcher)sender; 63 | // Lock so we don't output more than one change if we were only supposed to watch for one. 64 | // And to serialize access to the console 65 | lock (_notificationReactionLock) 66 | { 67 | // if only looking for one change and another thread beat us to it, return 68 | if (!_args.Monitor && _stopMonitoring) 69 | { 70 | return; 71 | } 72 | 73 | if (null != _args.Exclude && _args.Exclude.IsMatch(e.FullPath)) 74 | { 75 | return; 76 | } 77 | 78 | outputAction(); 79 | 80 | // If only looking for one change, signal to stop 81 | if (!_args.Monitor) 82 | { 83 | _stopMonitoring = true; 84 | _stopMonitoringEvent.Set(); 85 | } 86 | } 87 | } 88 | 89 | /// Output method 90 | protected void Output(TextWriter writer, string[] tokens, FileSystemWatcher source, Change type, string name) 91 | { 92 | foreach (var token in tokens) 93 | { 94 | var path = Path.Combine(source.Path, name); 95 | switch (token[0]) 96 | { 97 | case 'e': 98 | writer.Write(type); 99 | if (Directory.Exists(path)) 100 | { 101 | writer.Write(",ISDIR"); 102 | } 103 | break; 104 | case 'f': writer.Write(Path.GetFileName(path)); break; 105 | case 'w': writer.Write(Path.Combine(source.Path, Path.GetDirectoryName(path))); break; 106 | case 'T': writer.Write(DateTime.Now); break; 107 | default: writer.Write(token); break; 108 | } 109 | } 110 | writer.WriteLine(); 111 | } 112 | 113 | public void Stop(object data) { 114 | string s = Console.ReadLine(); 115 | _stopMonitoring = true; 116 | _stopMonitoringEvent.Set(); 117 | 118 | } 119 | 120 | public void Processor(object data) { 121 | string path = (string)data; 122 | using (var w = new FileSystemWatcher { 123 | Path = path, 124 | IncludeSubdirectories = _args.Recursive, 125 | Filter = "*.*" 126 | }) { 127 | w.Error += new ErrorEventHandler(OnWatcherError); 128 | 129 | // Parse "events" argument 130 | WatcherChangeTypes changes = 0; 131 | if (_args.Events.Contains("create")) 132 | { 133 | changes |= WatcherChangeTypes.Created; 134 | w.Created += new FileSystemEventHandler(OnWatcherNotification); 135 | } 136 | if (_args.Events.Contains("modify")) 137 | { 138 | changes |= WatcherChangeTypes.Changed; 139 | w.Changed += new FileSystemEventHandler(OnWatcherNotification); 140 | } 141 | if (_args.Events.Contains("delete")) 142 | { 143 | changes |= WatcherChangeTypes.Deleted; 144 | w.Deleted += new FileSystemEventHandler(OnWatcherNotification); 145 | } 146 | if (_args.Events.Contains("move")) 147 | { 148 | changes |= WatcherChangeTypes.Renamed; 149 | w.Renamed += new RenamedEventHandler(OnRenameNotification); 150 | } 151 | 152 | // Main loop 153 | if (!_args.Quiet) 154 | { 155 | Console.Error.WriteLine( 156 | "===> {0} for {1} in {2}{3} for {4}", 157 | _args.Monitor ? "Monitoring" : "Watching", 158 | changes, 159 | path, 160 | _args.Recursive ? " -r" : "", 161 | String.Join(", ", _args.Events.ToArray()) 162 | ); 163 | } 164 | w.EnableRaisingEvents = true; 165 | _stopMonitoringEvent.Wait(); 166 | } 167 | } 168 | 169 | /// Entry point 170 | public int Run() 171 | { 172 | using (_stopMonitoringEvent = new ManualResetEventSlim(initialState: false)) 173 | { 174 | foreach (var path in _args.Paths) 175 | { 176 | Thread t = new Thread(new ParameterizedThreadStart(Processor)); 177 | t.Start(path); 178 | _threads.Add(t); 179 | } 180 | Thread stop = new Thread(new ParameterizedThreadStart(Stop)); 181 | stop.Start(""); 182 | _stopMonitoringEvent.Wait(); 183 | foreach (var thread in _threads) 184 | { 185 | if (thread.IsAlive) 186 | thread.Abort(); 187 | thread.Join(); 188 | } 189 | return 0; 190 | } 191 | } 192 | 193 | /// Entry point method 194 | public static int Main(string[] args) 195 | { 196 | var p = new ArgumentParser(); 197 | 198 | // Show usage if no args or standard "help" args are given 199 | if (0 == args.Length || args[0].Equals("-?") || args[0].Equals("--help")) 200 | { 201 | p.PrintUsage("inotifywait", Console.Error); 202 | return 1; 203 | } 204 | 205 | // Run! 206 | return new Runner(p.Parse(args)).Run(); 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /include/api.hrl: -------------------------------------------------------------------------------- 1 | -define(API, [find_executable/0, start_port/2, known_events/0, line_to_event/1]). 2 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FS 5 | 6 | 7 |
18 | 19 |

FS

20 |
41 |
42 |

INTRO

43 |

Subscribe to Notifications

44 |
45 | > fs:start_link(fs_watcher, "/Users/5HT/synrc/fs"). 46 | > fs:subscribe(fs_watcher). 47 | > flush(). 48 | Shell got {<0.47.0>,{fs,file_event}, 49 | {"/Users/5HT/synrc/fs/src/README.md", 50 | [closed,modified]}} 51 |
52 |

Native Events

53 |
54 | > fs:known_events(fs_watcher). 55 | [mustscansubdirs,userdropped,kerneldropped,eventidswrapped, 56 | historydone,rootchanged,mount,unmount,created,removed, 57 | inodemetamod,renamed,modified,finderinfomod,changeowner, 58 | xattrmod,isfile,isdir,issymlink,ownevent] 59 |
60 |

Sample Scheduler

61 |
62 | > fs:start_looper(). 63 | =INFO REPORT==== 28-Aug-2013::19:36:26 === 64 | file_event: "/tank/proger/erlfsmon/src/4913" [closed,modified] 65 |
66 |
67 |
68 |

CONTRIBUTORS

69 |
70 |
71 |
72 |
73 | Made with to N2O 74 |
75 | 76 | 77 | -------------------------------------------------------------------------------- /man/fs.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FS 6 | 7 | 8 |
20 | 21 |

FS

22 |
23 |
24 | 25 |

INTRO

26 | 27 |

The FS module.

28 |
29 |
30 |

This module may refer to: 31 | MAN_MODULES 32 |

33 |
34 |
35 | 2005—2019 © Synrc Research Center 36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /man/fs_app.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | APP 4 | 5 | 6 |
18 | 19 |

APP

20 |
21 |
22 | 23 |

INTRO

24 | 25 |

The APP module.

26 |
27 |
28 |

This module may refer to: 29 | MAN_MODULES 30 |

31 |
32 |
33 | 2005—2019 © Synrc Research Center 34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /man/fs_event_bridge.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | EVENT 5 | 6 | 7 |
19 | 20 |

EVENT

21 |
22 |
23 | 24 |

INTRO

25 | 26 |

The EVENT module.

27 |
28 |
29 |

This module may refer to: 30 | MAN_MODULES 31 |

32 |
33 |
34 | 2005—2019 © Synrc Research Center 35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /man/fs_server.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SERVER 5 | 6 | 7 |
19 | 20 |

SERVER

21 |
22 |
23 | 24 |

INTRO

25 | 26 |

The SERVER module.

27 |
28 |
29 |

This module may refer to: 30 | MAN_MODULES 31 |

32 |
33 |
34 | 2005—2019 © Synrc Research Center 35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /man/fs_sup.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SUP 5 | 6 | 7 |
19 | 20 |

SUP

21 |
22 |
23 | 24 |

INTRO

25 | 26 |

The SUP module.

27 |
28 |
29 |

This module may refer to: 30 | MAN_MODULES 31 |

32 |
33 |
34 | 2005—2019 © Synrc Research Center 35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /man/fsevents.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FSEVENTS 5 | 6 | 7 |
19 | 20 |

FSEVENTS

21 |
22 |
23 | 24 |

INTRO

25 | 26 |

The FSEVENTS module.

27 |
28 |
29 |

This module may refer to: 30 | MAN_MODULES 31 |

32 |
33 |
34 | 2005—2019 © Synrc Research Center 35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /man/inotifywait.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | INOTIFYWAIT 5 | 6 | 7 |
19 | 20 |

INOTIFYWAIT

21 |
22 |
23 | 24 |

INTRO

25 | 26 |

The INOTIFYWAIT module.

27 |
28 |
29 |

This module may refer to: 30 | MAN_MODULES 31 |

32 |
33 |
34 | 2005—2019 © Synrc Research Center 35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /man/inotifywait_win32.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WIN32 5 | 6 | 7 |
19 | 20 |

WIN32

21 |
22 |
23 | 24 |

INTRO

25 | 26 |

The WIN32 module.

27 |
28 |
29 |

This module may refer to: 30 | MAN_MODULES 31 |

32 |
33 |
34 | 2005—2019 © Synrc Research Center 35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /man/kqueue.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | KQUEUE 5 | 6 | 7 |
19 | 20 |

KQUEUE

21 |
22 |
23 | 24 |

INTRO

25 | 26 |

The KQUEUE module.

27 |
28 |
29 |

This module may refer to: 30 | MAN_MODULES 31 |

32 |
33 |
34 | 2005—2019 © Synrc Research Center 35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule FS.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :fs, 6 | version: "11.4.1", 7 | description: "FS Native Listener (Mac Windows Linux)", 8 | elixir: "~> 1.9", 9 | deps: deps(), 10 | docs: [], 11 | package: package()] 12 | end 13 | 14 | defp package do 15 | [name: :fs, 16 | files: ["include", "priv", "src", "c_src", "LICENSE", "README.md", "rebar.config"], 17 | maintainers: ["Namdak Tonpa"], 18 | licenses: ["MIT"], 19 | links: %{"GitHub" => "https://github.com/synrc/fs"}] 20 | end 21 | 22 | defp deps do 23 | [{:ex_doc, "~> 0.11", only: :dev}] 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /priv/inotifywait.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synrc/fs/bbe4ae94fb63d2b60cef4ae4cfe56978e3cabaae/priv/inotifywait.exe -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {port_env, 2 | [{"darwin", "LDFLAGS", "-framework CoreFoundation -framework CoreServices"}, 3 | {"darwin", "CC", "clang"}, 4 | {"darwin", "CFLAGS", "-Wno-deprecated-declarations"}, 5 | {"freebsd", "LDFLAGS", ""}, 6 | {"freebsd", "CC", "cc"}, 7 | {"freebsd", "CFLAGS", ""}] 8 | }. 9 | 10 | {hex, [{doc, edoc}]}. 11 | 12 | {port_specs, 13 | [{"darwin", "priv/mac_listener", ["c_src/mac/*.c"]}]}. 14 | 15 | {plugins, [{pc, "1.15.0"}]}. 16 | {provider_hooks, [ 17 | {pre, [ 18 | {compile, {pc, compile}}, 19 | {clean, {pc, clean}} 20 | ]} 21 | ]}. 22 | 23 | %{pre_hooks, [ 24 | % {"win32", compile, "make -f c_src/windows/Makefile"}, 25 | % {"win32", clean, "make -f c_src/windows/Makefile clean"} 26 | % ] 27 | %}. 28 | 29 | {project_plugins, [rebar3_format]}. 30 | {format, [ 31 | {files, ["src/*.erl", "test/*.erl"]}, 32 | {formatter, otp_formatter}, 33 | {options, #{ line_length => 108, 34 | paper => 250, 35 | spaces_around_fields => false, 36 | inlining => all, 37 | inline_clause_bodies => true, 38 | inline_expressions => true, 39 | inline_qualified_function_composition => true, 40 | inline_simple_funs => true, 41 | inline_items => all, 42 | inline_fields => true, 43 | inline_attributes => true 44 | }}]}. 45 | -------------------------------------------------------------------------------- /src/fs.app.src: -------------------------------------------------------------------------------- 1 | {application, fs, 2 | [{description, "FS Native Listener (Mac Windows Linux)"}, 3 | {vsn, "11.4.1"}, 4 | {registered, []}, 5 | {applications, [kernel,stdlib]}, 6 | {mod, { fs_app, []}}, 7 | {env, [ {backwards_compatible, true} ]}, 8 | {maintainers,["Maxim Sokhatsky"]}, 9 | {licenses, ["ISC"]}, 10 | {links,[{"Github","https://github.com/synrc/fs"}]} 11 | ]}. 12 | -------------------------------------------------------------------------------- /src/fs.erl: -------------------------------------------------------------------------------- 1 | -module(fs). 2 | 3 | -include_lib("kernel/include/file.hrl"). 4 | 5 | -export([start_link/1, 6 | start_link/2, 7 | subscribe/0, 8 | subscribe/1, 9 | known_events/0, 10 | known_events/1, 11 | start_looper/0, 12 | start_looper/1, 13 | find_executable/2, 14 | path/0]). 15 | 16 | % sample subscriber 17 | 18 | start_link(Name) -> start_link(Name, path()). 19 | 20 | start_link(Name, Path) -> 21 | SupName = name(Name, "sup"), 22 | FileHandler = name(Name, "file"), 23 | fs_sup:start_link(SupName, Name, FileHandler, Path). 24 | 25 | subscribe() -> subscribe(default_fs). 26 | 27 | subscribe(Name) -> 28 | gen_event:add_sup_handler(Name, 29 | {fs_event_bridge, self()}, 30 | [self()]). 31 | 32 | path() -> 33 | case application:get_env(fs, path) of 34 | {ok, P} -> filename:absname(P); 35 | undefined -> filename:absname("") 36 | end. 37 | 38 | known_events() -> known_events(default_fs). 39 | 40 | known_events(Name) -> 41 | gen_server:call(name(Name, "file"), known_events). 42 | 43 | start_looper() -> start_looper(default_fs). 44 | 45 | start_looper(Name) -> 46 | spawn(fun () -> 47 | subscribe(Name), 48 | loop() 49 | end). 50 | 51 | loop() -> 52 | receive 53 | {_Pid, {fs, file_event}, {Path, Flags}} -> 54 | error_logger:info_msg("file_event: ~p ~p", 55 | [Path, Flags]); 56 | _ -> ignore 57 | end, 58 | loop(). 59 | 60 | find_executable(Cmd, DepsPath) -> 61 | Executable = 62 | case priv_file(Cmd) of 63 | false -> mad_file(DepsPath); 64 | Priv -> Priv 65 | end, 66 | case filename:pathtype(Executable) of 67 | relative -> 68 | filename:join(path(), Executable); 69 | _PathType -> 70 | Executable 71 | end. 72 | 73 | mad_file(DepsPath) -> 74 | case filelib:is_regular(DepsPath) of 75 | true -> DepsPath; 76 | false -> 77 | case load_file(DepsPath) of 78 | {error, _} -> 79 | %% This path has been already checked in find_executable/2 80 | false; 81 | {ok, ETSFile} -> 82 | filelib:ensure_dir(DepsPath), 83 | file:write_file(DepsPath, ETSFile), 84 | file:write_file_info(DepsPath, 85 | #file_info{mode = 8#00555}) 86 | end 87 | end. 88 | 89 | priv_file(Cmd) -> 90 | case code:priv_dir(fs) of 91 | Priv when is_list(Priv) -> 92 | Path = filename:join(Priv, Cmd), 93 | case filelib:is_regular(Path) of 94 | true -> Path; 95 | false -> false 96 | end; 97 | _ -> false 98 | end. 99 | 100 | name(Name, Prefix) -> 101 | NameList = erlang:atom_to_list(Name), 102 | list_to_atom(NameList ++ Prefix). 103 | 104 | ets_created() -> 105 | case ets:info(filesystem) of 106 | undefined -> 107 | ets:new(filesystem, 108 | [set, named_table, {keypos, 1}, public]); 109 | _ -> skip 110 | end. 111 | 112 | load_file(Name) -> 113 | ets_created(), 114 | case ets:lookup(filesystem, Name) of 115 | [{Name, Bin}] -> {ok, Bin}; 116 | _ -> {error, etsfs} 117 | end. 118 | -------------------------------------------------------------------------------- /src/fs_app.erl: -------------------------------------------------------------------------------- 1 | -module(fs_app). 2 | 3 | -behaviour(application). 4 | 5 | -export([start/2, stop/1]). 6 | 7 | start(_StartType, _StartArgs) -> 8 | case application:get_env(fs, backwards_compatible) of 9 | {ok, false} -> {ok, self()}; 10 | {ok, true} -> fs:start_link(default_fs) 11 | end. 12 | 13 | stop(_State) -> ok. 14 | -------------------------------------------------------------------------------- /src/fs_event_bridge.erl: -------------------------------------------------------------------------------- 1 | -module(fs_event_bridge). 2 | 3 | -behaviour(gen_event). 4 | 5 | -export([init/1, 6 | handle_event/2, 7 | handle_call/2, 8 | handle_info/2, 9 | code_change/3, 10 | terminate/2]). 11 | 12 | init([Pid]) -> {ok, Pid}. 13 | 14 | handle_event(Event, Pid) -> 15 | Pid ! Event, 16 | {ok, Pid}. 17 | 18 | handle_call(_, State) -> {ok, ok, State}. 19 | 20 | handle_info(_, State) -> {ok, State}. 21 | 22 | code_change(_OldVsn, State, _Extra) -> {ok, State}. 23 | 24 | terminate(_Reason, _State) -> ok. 25 | -------------------------------------------------------------------------------- /src/fs_server.erl: -------------------------------------------------------------------------------- 1 | -module(fs_server). 2 | 3 | -behaviour(gen_server). 4 | 5 | -define(SERVER, ?MODULE). 6 | 7 | -export([start_link/5]). 8 | 9 | -export([init/1, 10 | handle_call/3, 11 | handle_cast/2, 12 | handle_info/2, 13 | terminate/2, 14 | code_change/3]). 15 | 16 | -record(state, {event_handler, port, path, backend, cwd, crashes}). 17 | 18 | notify(EventHandler, file_event = A, Msg) -> 19 | Key = {fs, A}, 20 | gen_event:notify(EventHandler, {self(), Key, Msg}). 21 | 22 | start_link(Name, EventHandler, Backend, Path, Cwd) -> 23 | gen_server:start_link({local, Name}, 24 | ?MODULE, 25 | [EventHandler, Backend, Path, Cwd], 26 | []). 27 | 28 | init([EventHandler, Backend, Path, Cwd]) -> 29 | {ok, 30 | #state{event_handler = EventHandler, 31 | port = Backend:start_port(Path, Cwd), path = Path, 32 | backend = Backend,cwd=Cwd,crashes=0}}. 33 | 34 | handle_call(known_events, _From, 35 | #state{backend = Backend} = State) -> 36 | {reply, Backend:known_events(), State}; 37 | handle_call(_Request, _From, State) -> 38 | {reply, ok, State}. 39 | 40 | handle_cast(_Msg, State) -> {noreply, State}. 41 | 42 | handle_info({_Port, {data, {eol, Line}}}, 43 | #state{event_handler = EventHandler, 44 | backend = Backend} = 45 | State) -> 46 | Event = Backend:line_to_event(Line), 47 | notify(EventHandler, file_event, Event), 48 | {noreply, State}; 49 | handle_info({_Port, {data, {noeol, Line}}}, State) -> 50 | error_logger:error_msg("~p line too long: ~p, ignoring~n", 51 | [?SERVER, Line]), 52 | {noreply, State}; 53 | 54 | handle_info({_Port, {exit_status, Status}}, #state{path=Path,backend=Backend,cwd=Cwd,crashes=Crashes} = State) -> 55 | error_logger:error_msg("~p port_exit ~p, retry ~p~n", [?SERVER, Status, Crashes]), 56 | timer:sleep(100*(Crashes+1)*(Crashes+1)), 57 | {noreply, State#state{port=Backend:start_port(Path, Cwd),crashes=Crashes+1}}; 58 | 59 | handle_info(_Info, State) -> {noreply, State}. 60 | 61 | terminate(_Reason, #state{port = Port}) -> 62 | catch port_close(Port), 63 | ok. 64 | 65 | code_change(_OldVsn, State, _Extra) -> {ok, State}. 66 | -------------------------------------------------------------------------------- /src/fs_sup.erl: -------------------------------------------------------------------------------- 1 | -module(fs_sup). 2 | 3 | -behaviour(supervisor). 4 | 5 | -export([start_link/4]). 6 | 7 | -export([init/1]). 8 | 9 | -define(CHILD(I, Type, Args), 10 | {I, {I, start_link, Args}, permanent, 5000, Type, [I]}). 11 | 12 | start_link(SupName, EventHandler, FileHandler, Path) -> 13 | supervisor:start_link({local, SupName}, 14 | ?MODULE, 15 | [EventHandler, FileHandler, Path]). 16 | 17 | init([EventHandler, FileHandler, Path]) -> 18 | Backend = case os:type() of 19 | {unix, darwin} -> fsevents; 20 | {unix, linux} -> inotifywait; 21 | {unix, freebsd} -> inotifywait; 22 | {unix, openbsd} -> inotifywait; 23 | {unix, sunos} -> undefined; 24 | {unix, _} -> kqueue; 25 | {win32, nt} -> inotifywait_win32; 26 | _ -> undefined 27 | end, 28 | Children = case has_executable(Backend) of 29 | false -> []; 30 | true -> 31 | [?CHILD(fs_server, worker, 32 | [FileHandler, EventHandler, Backend, Path, Path])] 33 | end, 34 | {ok, 35 | {{one_for_one, 5, 10}, 36 | Children ++ 37 | [?CHILD(gen_event, worker, [{local, EventHandler}])]}}. 38 | 39 | has_executable(undefined) -> 40 | os_not_supported(), 41 | false; 42 | has_executable(Backend) -> 43 | case Backend:find_executable() of 44 | false -> 45 | backend_port_not_found(Backend), 46 | false; 47 | _ -> true 48 | end. 49 | 50 | os_not_supported() -> 51 | error_logger:warning_msg("fs does not support the current operating " 52 | "system: auto-reloading might not work~n", 53 | []). 54 | 55 | backend_port_not_found(Backend) -> 56 | error_logger:error_msg("backend port not found: ~p~n", 57 | [Backend]). 58 | -------------------------------------------------------------------------------- /src/sys/fsevents.erl: -------------------------------------------------------------------------------- 1 | -module(fsevents). 2 | -include("api.hrl"). 3 | -export([find_executable/0, start_port/2, known_events/0, line_to_event/1]). 4 | 5 | find_executable() -> 6 | fs:find_executable("mac_listener", "deps/fs/priv/mac_listener"). 7 | 8 | known_events() -> 9 | [mustscansubdirs,userdropped,kerneldropped,eventidswrapped,historydone,rootchanged, 10 | mount,unmount,created,removed,inodemetamod,renamed,modified,finderinfomod,changeowner, 11 | xattrmod,isfile,isdir,issymlink,ownevent]. 12 | 13 | start_port(Path, Cwd) -> 14 | erlang:open_port({spawn_executable, find_executable()}, 15 | [stream, exit_status, binary, {line, 16384}, {args, ["-F", Path]}, {cd, Cwd}]). 16 | 17 | line_to_event(Line0) -> 18 | Line = unicode:characters_to_list(Line0, utf8), 19 | [_EventId, Flags1, Path] = string:tokens(Line, [$\t]), 20 | [_, Flags2] = string:tokens(Flags1, [$=]), 21 | {ok, T, _} = erl_scan:string(Flags2 ++ "."), 22 | {ok, Flags} = erl_parse:parse_term(T), 23 | {Path, Flags}. 24 | -------------------------------------------------------------------------------- /src/sys/inotifywait.erl: -------------------------------------------------------------------------------- 1 | -module(inotifywait). 2 | -include("api.hrl"). 3 | -export([find_executable/0, start_port/2, known_events/0, line_to_event/1]). 4 | 5 | find_executable() -> os:find_executable("inotifywait"). 6 | known_events() -> [created, deleted, renamed, closed, modified, isdir, attribute, undefined]. 7 | 8 | start_port(Path, Cwd) -> 9 | Path1 = filename:absname(Path), 10 | Args = ["-c", "inotifywait \"$0\" \"$@\" & PID=$!; read a; kill $PID", 11 | "-m", "-e", "modify", "-e", "close_write", "-e", "moved_to", "-e", "moved_from", "-e", "create", "-e", "delete", 12 | "-e", "attrib", "--quiet", "-r", Path1], 13 | erlang:open_port({spawn_executable, os:find_executable("sh")}, 14 | [stream, exit_status, binary, {line, 16384}, {args, Args}, {cd, Cwd}]). 15 | 16 | line_to_event(Line0) -> 17 | Line = unicode:characters_to_list(Line0, utf8), 18 | {match, [Dir, Flags1, DirEntry]} = re:run(Line, re(), [{capture, all_but_first, list}]), 19 | Flags = [convert_flag(F) || F <- string:tokens(Flags1, ",")], 20 | Path = Dir ++ DirEntry, 21 | {Path, Flags}. 22 | 23 | convert_flag("CREATE") -> created; 24 | convert_flag("DELETE") -> deleted; 25 | convert_flag("ISDIR") -> isdir; 26 | convert_flag("MODIFY") -> modified; 27 | convert_flag("CLOSE_WRITE") -> modified; 28 | convert_flag("CLOSE") -> closed; 29 | convert_flag("MOVED_TO") -> renamed; 30 | convert_flag("MOVED_FROM") -> removed; 31 | convert_flag("ATTRIB") -> attribute; 32 | convert_flag(_) -> undefined. 33 | 34 | re() -> 35 | case get(inotifywait_re) of 36 | undefined -> 37 | {ok, R} = re:compile("^(.*/) ([A-Z_,]+) (.*)$", [unicode]), 38 | put(inotifywait_re, R), 39 | R; 40 | V -> V 41 | end. 42 | 43 | -------------------------------------------------------------------------------- /src/sys/inotifywait_win32.erl: -------------------------------------------------------------------------------- 1 | -module(inotifywait_win32). 2 | -include("api.hrl"). 3 | -export([find_executable/0, start_port/2, known_events/0, line_to_event/1]). 4 | 5 | find_executable() -> 6 | fs:find_executable("inotifywait.exe", "deps/fs/priv/inotifywait.exe"). 7 | 8 | known_events() -> [created, modified, removed, renamed, undefined]. 9 | 10 | start_port(Path, Cwd) -> 11 | Path1 = filename:absname(Path), 12 | Args = ["-m", "-r", Path1], 13 | erlang:open_port({spawn_executable, find_executable()}, 14 | [stream, exit_status, {line, 16384}, {args, Args}, {cd, Cwd}]). 15 | 16 | line_to_event(Line) -> 17 | {match, [Dir, Flags1, DirEntry]} = re:run(Line, re(), [{capture, all_but_first, list}]), 18 | Flags = [convert_flag(F) || F <- string:tokens(Flags1, ",")], 19 | Path = filename:join(Dir,DirEntry), 20 | {Path, Flags}. 21 | 22 | 23 | convert_flag("CREATE") -> created; 24 | convert_flag("MODIFY") -> modified; 25 | convert_flag("DELETE") -> removed; 26 | convert_flag("MOVED_TO") -> renamed; 27 | convert_flag(_) -> undefined. 28 | 29 | re() -> 30 | case get(inotifywait_re) of 31 | undefined -> 32 | {ok, R} = re:compile("^(.*\\\\.*) ([A-Z_,]+) (.*)$", [unicode]), 33 | put(inotifywait_re, R), 34 | R; 35 | V -> V 36 | end. 37 | 38 | -------------------------------------------------------------------------------- /src/sys/kqueue.erl: -------------------------------------------------------------------------------- 1 | -module(kqueue). 2 | -include("api.hrl"). 3 | -export([find_executable/0, start_port/2, known_events/0, line_to_event/1]). 4 | 5 | known_events() -> [created, deleted, renamed, closed, modified, isdir, undefined]. 6 | line_to_event(Line) -> 7 | io:format("Line: ~p~n",[Line]), 8 | {".",Line}. 9 | find_executable() -> fs:find_executable("kqueue", "deps/fs/priv/kqueue"). 10 | start_port(Path, Cwd) -> 11 | erlang:open_port({spawn_executable, find_executable()}, 12 | [stream, exit_status, {line, 16384}, {args, [Path]}, {cd, Cwd}]). 13 | --------------------------------------------------------------------------------