├── demo.gif ├── .travis.yml ├── Makefile ├── LICENSE ├── README.md └── main.c /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeplea/fsghost/HEAD/demo.gif -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | compiler: 4 | - clang 5 | - gcc 6 | 7 | script: make 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | cc ?= gcc 2 | 3 | ccflags = -Wall -Wshadow -O2 4 | lflags = 5 | 6 | 7 | all: fsghost 8 | 9 | 10 | fsghost: main.o 11 | $(cc) $(ccflags) -o $@ $^ $(lflags) 12 | 13 | 14 | .c.o: 15 | $(cc) -c $(ccflags) $< -o $@ 16 | 17 | 18 | clean: 19 | rm *.o 20 | rm *.exe 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | zlib License 2 | 3 | Copyright (C) 2016 Lewis Van Winkle 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgement in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/codeplea/fsghost.svg?branch=master)](https://travis-ci.org/codeplea/fsghost) 2 | 3 | # FsGhost - Cross-Platform File Change Monitoring in C 4 | 5 | FsGhost logo 6 | 7 | FsGhost is a *tiny* command-line tool to monitor and notify of file changes in a 8 | given directory. FsGhost works natively on both Windows and Linux and is 9 | self-contained in only 1 C file. Its only dependencies are the operating system 10 | headers for your platform. 11 | 12 | ## Features 13 | 14 | - **ANSI C with no external dependencies**. 15 | - Contained in a single source file. 16 | - Simple. 17 | - Released under the zlib license - free for nearly any use. 18 | 19 | 20 | ## Example Usage 21 | 22 | Call `fsghost` with a single argument, the path of the directory you'd like to monitor. 23 | 24 | ``` 25 | mkdir test-dir 26 | fsghost test-dir 27 | ``` 28 | 29 | FsGhost will then monitor that directory. If any files are created, written, or 30 | deleted, FsGhost will write that filename to the standard output. 31 | 32 | 33 | ![Example Usage](./demo.gif) 34 | 35 | ## Building 36 | You can probably build by simply running `make`. If that doesn't work, try to compile 37 | `main.c`. It should be pretty easy. 38 | 39 | ## FAQ 40 | ### Does FsGhost do polling. 41 | No. FsGhost uses the proper file-system APIs on each platform to get notified when changes take place. This means 42 | that it uses very few system resources when running, and will not bog down your machine. It's made to be run in the background 43 | for long periods of time. 44 | 45 | For Windows, FsGhost uses the `ReadDirectoryChangesW` API. For Linux, it uses the `inotify` API. 46 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * FsGhost - Simple file change monitoring tool 3 | * 4 | * Copyright (c) 2016 Lewis Van Winkle 5 | * 6 | * http://CodePlea.com 7 | * 8 | * This software is provided 'as-is', without any express or implied 9 | * warranty. In no event will the authors be held liable for any damages 10 | * arising from the use of this software. 11 | * 12 | * Permission is granted to anyone to use this software for any purpose, 13 | * including commercial applications, and to alter it and redistribute it 14 | * freely, subject to the following restrictions: 15 | * 16 | * 1. The origin of this software must not be misrepresented; you must not 17 | * claim that you wrote the original software. If you use this software 18 | * in a product, an acknowledgement in the product documentation would be 19 | * appreciated but is not required. 20 | * 2. Altered source versions must be plainly marked as such, and must not be 21 | * misrepresented as being the original software. 22 | * 3. This notice may not be removed or altered from any source distribution. 23 | * 24 | */ 25 | 26 | #include 27 | #include 28 | 29 | #ifdef _WIN32 30 | 31 | #define WIN32_LEAN_AND_MEAN 32 | #include 33 | 34 | int watch(const char *dir) { 35 | 36 | HANDLE hdir = CreateFile( 37 | dir, 38 | FILE_LIST_DIRECTORY, 39 | FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 40 | 0, 41 | OPEN_EXISTING, 42 | FILE_FLAG_BACKUP_SEMANTICS, 43 | 0); 44 | 45 | if (hdir == INVALID_HANDLE_VALUE) { 46 | DWORD err = GetLastError(); 47 | printf("Error - CreateFile %ld\n", err); 48 | return 1; 49 | } 50 | 51 | 52 | 53 | const int buffer_size = 4096; 54 | void *buffer = malloc(buffer_size); 55 | 56 | if (!buffer) { 57 | printf("Error - Couldn't allocate %d bytes.\n", buffer_size); 58 | return 1; 59 | } 60 | 61 | 62 | while (1) { 63 | DWORD rsize; 64 | int rd = ReadDirectoryChangesW( 65 | hdir, 66 | buffer, 67 | buffer_size, 68 | 0, 69 | FILE_NOTIFY_CHANGE_FILE_NAME | 70 | FILE_NOTIFY_CHANGE_DIR_NAME | 71 | FILE_NOTIFY_CHANGE_ATTRIBUTES | 72 | FILE_NOTIFY_CHANGE_SIZE | 73 | FILE_NOTIFY_CHANGE_LAST_WRITE | 74 | FILE_NOTIFY_CHANGE_SECURITY, 75 | &rsize, 76 | 0, 77 | 0); 78 | 79 | if (!rd) { 80 | printf("Error - ReadDirectoryChangesW %d\n", rd); 81 | return 1; 82 | } 83 | 84 | 85 | FILE_NOTIFY_INFORMATION *p = buffer; 86 | 87 | while (rsize > 0) { 88 | 89 | DWORD action = p->Action; 90 | DWORD len = p->FileNameLength; 91 | WCHAR *fname = p->FileName; 92 | 93 | switch (action) { 94 | case FILE_ACTION_ADDED: printf("added "); break; 95 | case FILE_ACTION_REMOVED: printf("removed "); break; 96 | case FILE_ACTION_MODIFIED: printf("modified "); break; 97 | case FILE_ACTION_RENAMED_OLD_NAME: printf("removed "); break; 98 | case FILE_ACTION_RENAMED_NEW_NAME: printf("added "); break; 99 | default: printf("error "); break; 100 | } 101 | 102 | printf("%.*S\n", (int)(len/2), fname); 103 | 104 | if (p->NextEntryOffset) { 105 | p = (void*)p + p->NextEntryOffset; 106 | } else { 107 | break; 108 | } 109 | } 110 | } 111 | 112 | 113 | return 1; 114 | } 115 | 116 | #elif __linux__ 117 | 118 | #include 119 | #include 120 | #include 121 | #include 122 | #include 123 | 124 | 125 | int watch(const char *dir) { 126 | 127 | int fd = inotify_init(); 128 | if (fd < 0) { 129 | printf("Error - inotify_init failed.\n"); 130 | return 1; 131 | } 132 | 133 | const int buffer_size = 4096; 134 | char *buffer = malloc(buffer_size); 135 | 136 | if (!buffer) { 137 | printf("Error - Couldn't allocate %d bytes.\n", buffer_size); 138 | return 1; 139 | } 140 | 141 | int wd = inotify_add_watch(fd, dir, IN_ALL_EVENTS); 142 | if (wd < 0) { 143 | printf("Error - inotify_add_watch failed.\n"); 144 | return 1; 145 | } 146 | 147 | while (1) { 148 | int len = read(fd, buffer, buffer_size); 149 | 150 | if (len < 0) { 151 | printf("Error - read failed.\n"); 152 | return 1; 153 | } 154 | 155 | 156 | char *p = buffer; 157 | while(p < buffer + len) { 158 | struct inotify_event *evt = (struct inotify_event *)buffer; 159 | if (evt->len) { 160 | if (evt->mask & (IN_CREATE | IN_MOVED_TO)) { 161 | printf("added %s\n", evt->name); 162 | } else if (evt->mask & (IN_DELETE | IN_MOVED_FROM)) { 163 | printf("removed %s\n", evt->name); 164 | } else if (evt->mask & IN_MODIFY) { 165 | printf("modified %s\n", evt->name); 166 | } 167 | } 168 | 169 | p += (sizeof(struct inotify_event) + evt->len); 170 | } 171 | } 172 | 173 | 174 | return 1; 175 | } 176 | 177 | 178 | #elif __APPLE__ 179 | /* TODO */ 180 | #error("APPLE IS UNSUPPORTED PLATFORM") 181 | #else 182 | #error("UNSUPPORTED PLATFORM") 183 | #endif 184 | 185 | int main(int argc, char *argv[]) { 186 | if (argc != 2) { 187 | printf("Usage: fsghost directory\n"); 188 | return 0; 189 | } 190 | 191 | 192 | return watch(argv[1]); 193 | } 194 | --------------------------------------------------------------------------------