Rev 79 | Rev 100 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
45 | luk | 1 | |
2 | /// inotify cron table manipulator main file |
||
3 | /** |
||
4 | * \file ict-main.cpp |
||
5 | * |
||
6 | * inotify cron system |
||
7 | * |
||
63 | luk | 8 | * Copyright (C) 2006, 2007 Lukas Jelinek, <lukas@aiken.cz> |
45 | luk | 9 | * |
10 | * This program is free software; you can use it, redistribute |
||
11 | * it and/or modify it under the terms of the GNU General Public |
||
12 | * License, version 2 (see LICENSE-GPL). |
||
13 | * |
||
14 | */ |
||
15 | |||
16 | |||
17 | #include <argp.h> |
||
18 | #include <pwd.h> |
||
19 | #include <string> |
||
20 | #include <stdio.h> |
||
21 | #include <unistd.h> |
||
22 | #include <sys/stat.h> |
||
23 | #include <sys/wait.h> |
||
67 | luk | 24 | #include <sys/inotify.h> |
69 | luk | 25 | #include <fcntl.h> |
83 | luk | 26 | #include <stdlib.h> |
27 | #include <limits.h> |
||
45 | luk | 28 | |
69 | luk | 29 | #include "inotify-cxx.h" |
30 | #include "appargs.h" |
||
31 | |||
55 | luk | 32 | #include "incron.h" |
45 | luk | 33 | #include "incrontab.h" |
69 | luk | 34 | #include "incroncfg.h" |
45 | luk | 35 | |
69 | luk | 36 | |
37 | /// Alternative editor |
||
67 | luk | 38 | #define INCRON_ALT_EDITOR "/etc/alternatives/editor" |
45 | luk | 39 | |
69 | luk | 40 | /// Default (hard-wired) editor |
45 | luk | 41 | #define INCRON_DEFAULT_EDITOR "vim" |
42 | |||
69 | luk | 43 | /// incrontab version string |
44 | #define INCRONTAB_VERSION INCRONTAB_NAME " " INCRON_VERSION |
||
45 | luk | 45 | |
69 | luk | 46 | /// incrontab description string |
47 | #define INCRONTAB_DESCRIPTION "incrontab - inotify cron table manipulator\n" \ |
||
48 | "(c) Lukas Jelinek, 2006, 2007" |
||
45 | luk | 49 | |
69 | luk | 50 | /// incrontab help string |
51 | #define INCRONTAB_HELP INCRONTAB_DESCRIPTION "\n\n" \ |
||
52 | "usage: incrontab [<options>] <operation>\n" \ |
||
53 | " incrontab [<options>] <FILE-TO-IMPORT>\n\n" \ |
||
54 | "<operation> may be one of the following:\n" \ |
||
55 | " -?, --about gives short information about program\n" \ |
||
56 | " -h, --help prints this help text\n" \ |
||
57 | " -l, --list lists user table\n" \ |
||
58 | " -r, --remove removes user table\n" \ |
||
59 | " -e, --edit provides editting user table\n" \ |
||
60 | " -t, --types list supported event types\n" \ |
||
61 | " -d, --reload request incrond to reload user table\n" \ |
||
62 | " -V, --version prints program version\n\n" \ |
||
63 | "\n" \ |
||
64 | "These options may be used:\n" \ |
||
65 | " -u <USER>, --user=<USER> overrides current user (requires root privileges)\n" \ |
||
66 | " -f <FILE>, --config=<FILE> overrides default configuration file (requires root privileges)\n\n" \ |
||
67 | "For reporting bugs please use http:://bts.aiken.cz\n" |
||
68 | |||
45 | luk | 69 | |
70 | |||
71 | |||
49 | luk | 72 | /// Copies a file to an user table. |
73 | /** |
||
69 | luk | 74 | * \param[in] rPath path to file |
75 | * \param[in] rUser user name |
||
49 | luk | 76 | * \return true = success, false = failure |
77 | */ |
||
69 | luk | 78 | bool copy_from_file(const std::string& rPath, const std::string& rUser) |
45 | luk | 79 | { |
69 | luk | 80 | fprintf(stderr, "copying table from file '%s'\n", rPath.c_str()); |
81 | |||
82 | IncronTab tab; |
||
83 | std::string s(rPath); |
||
45 | luk | 84 | if (s == "-") |
85 | s = "/dev/stdin"; |
||
86 | if (!tab.Load(s)) { |
||
69 | luk | 87 | fprintf(stderr, "cannot load table from file '%s'\n", rPath.c_str()); |
45 | luk | 88 | return false; |
89 | } |
||
90 | |||
69 | luk | 91 | std::string out(IncronTab::GetUserTablePath(rUser)); |
45 | luk | 92 | if (!tab.Save(out)) { |
69 | luk | 93 | fprintf(stderr, "cannot create table for user '%s'\n", rUser.c_str()); |
45 | luk | 94 | return false; |
95 | } |
||
96 | |||
97 | return true; |
||
98 | } |
||
99 | |||
49 | luk | 100 | /// Removes an user table. |
101 | /** |
||
69 | luk | 102 | * \param[in] rUser user name |
49 | luk | 103 | * \return true = success, false = failure |
104 | */ |
||
69 | luk | 105 | bool remove_table(const std::string& rUser) |
45 | luk | 106 | { |
69 | luk | 107 | fprintf(stderr, "removing table for user '%s'\n", rUser.c_str()); |
108 | |||
109 | std::string tp(IncronTab::GetUserTablePath(rUser)); |
||
45 | luk | 110 | |
73 | luk | 111 | if (unlink(tp.c_str()) != 0) { |
112 | if (errno == ENOENT) { |
||
113 | fprintf(stderr, "table for user '%s' does not exist\n", rUser.c_str()); |
||
114 | return true; |
||
115 | } |
||
116 | else { |
||
117 | fprintf(stderr, "cannot remove table for user '%s': %s\n", rUser.c_str(), strerror(errno)); |
||
118 | return false; |
||
119 | } |
||
45 | luk | 120 | } |
73 | luk | 121 | |
122 | fprintf(stderr, "table for user '%s' successfully removed\n", rUser.c_str()); |
||
45 | luk | 123 | return true; |
124 | } |
||
125 | |||
49 | luk | 126 | /// Lists an user table. |
127 | /** |
||
69 | luk | 128 | * \param[in] rUser user name |
49 | luk | 129 | * \return true = success, false = failure |
130 | */ |
||
69 | luk | 131 | bool list_table(const std::string& rUser) |
45 | luk | 132 | { |
69 | luk | 133 | std::string tp(IncronTab::GetUserTablePath(rUser)); |
45 | luk | 134 | |
69 | luk | 135 | FILE* f = fopen(tp.c_str(), "r"); |
136 | if (f == NULL) { |
||
45 | luk | 137 | if (errno == ENOENT) { |
69 | luk | 138 | fprintf(stderr, "no table for %s\n", rUser.c_str()); |
45 | luk | 139 | return true; |
140 | } |
||
141 | else { |
||
69 | luk | 142 | fprintf(stderr, "cannot read table for '%s': %s\n", rUser.c_str(), strerror(errno)); |
45 | luk | 143 | return false; |
144 | } |
||
145 | } |
||
65 | luk | 146 | |
147 | char s[1024]; |
||
148 | while (fgets(s, 1024, f) != NULL) { |
||
149 | fputs(s, stdout); |
||
150 | } |
||
151 | |||
152 | fclose(f); |
||
153 | |||
154 | return true; |
||
45 | luk | 155 | } |
156 | |||
49 | luk | 157 | /// Allows to edit an user table. |
158 | /** |
||
69 | luk | 159 | * \param[in] rUser user name |
49 | luk | 160 | * \return true = success, false = failure |
161 | * |
||
162 | * \attention This function is very complex and may contain |
||
163 | * various bugs including security ones. Please keep |
||
164 | * it in mind.. |
||
165 | */ |
||
69 | luk | 166 | bool edit_table(const std::string& rUser) |
45 | luk | 167 | { |
69 | luk | 168 | std::string tp(IncronTab::GetUserTablePath(rUser)); |
45 | luk | 169 | |
69 | luk | 170 | struct passwd* ppwd = getpwnam(rUser.c_str()); |
45 | luk | 171 | if (ppwd == NULL) { |
69 | luk | 172 | fprintf(stderr, "cannot find user '%s': %s\n", rUser.c_str(), strerror(errno)); |
45 | luk | 173 | return false; |
174 | } |
||
61 | luk | 175 | |
45 | luk | 176 | uid_t uid = ppwd->pw_uid; |
61 | luk | 177 | uid_t gid = ppwd->pw_gid; |
45 | luk | 178 | |
179 | char s[NAME_MAX]; |
||
180 | strcpy(s, "/tmp/incron.table-XXXXXX"); |
||
181 | |||
182 | uid_t iu = geteuid(); |
||
61 | luk | 183 | uid_t ig = getegid(); |
55 | luk | 184 | |
63 | luk | 185 | if (setegid(gid) != 0 || seteuid(uid) != 0) { |
69 | luk | 186 | fprintf(stderr, "cannot change effective UID/GID for user '%s': %s\n", rUser.c_str(), strerror(errno)); |
45 | luk | 187 | return false; |
188 | } |
||
189 | |||
190 | int fd = mkstemp(s); |
||
191 | if (fd == -1) { |
||
192 | fprintf(stderr, "cannot create temporary file: %s\n", strerror(errno)); |
||
193 | return false; |
||
194 | } |
||
195 | |||
61 | luk | 196 | bool ok = false; |
197 | FILE* out = NULL; |
||
198 | FILE* in = NULL; |
||
199 | time_t mt = (time_t) 0; |
||
200 | const char* e = NULL; |
||
69 | luk | 201 | std::string ed; |
61 | luk | 202 | |
63 | luk | 203 | if (setegid(ig) != 0 || seteuid(iu) != 0) { |
61 | luk | 204 | fprintf(stderr, "cannot change effective UID/GID: %s\n", strerror(errno)); |
45 | luk | 205 | close(fd); |
61 | luk | 206 | goto end; |
45 | luk | 207 | } |
208 | |||
61 | luk | 209 | out = fdopen(fd, "w"); |
45 | luk | 210 | if (out == NULL) { |
211 | fprintf(stderr, "cannot write to temporary file: %s\n", strerror(errno)); |
||
212 | close(fd); |
||
61 | luk | 213 | goto end; |
45 | luk | 214 | } |
215 | |||
61 | luk | 216 | in = fopen(tp.c_str(), "r"); |
45 | luk | 217 | if (in == NULL) { |
218 | if (errno == ENOENT) { |
||
219 | in = fopen("/dev/null", "r"); |
||
220 | if (in == NULL) { |
||
69 | luk | 221 | fprintf(stderr, "cannot get empty table for '%s': %s\n", rUser.c_str(), strerror(errno)); |
45 | luk | 222 | fclose(out); |
61 | luk | 223 | goto end; |
45 | luk | 224 | } |
225 | } |
||
226 | else { |
||
69 | luk | 227 | fprintf(stderr, "cannot read old table for '%s': %s\n", rUser.c_str(), strerror(errno)); |
45 | luk | 228 | fclose(out); |
61 | luk | 229 | goto end; |
45 | luk | 230 | } |
231 | } |
||
232 | |||
233 | char buf[1024]; |
||
234 | while (fgets(buf, 1024, in) != NULL) { |
||
235 | fputs(buf, out); |
||
236 | } |
||
237 | fclose(in); |
||
238 | fclose(out); |
||
239 | |||
240 | struct stat st; |
||
241 | if (stat(s, &st) != 0) { |
||
242 | fprintf(stderr, "cannot stat temporary file: %s\n", strerror(errno)); |
||
61 | luk | 243 | goto end; |
45 | luk | 244 | } |
245 | |||
67 | luk | 246 | mt = st.st_mtime; // save modification time for detecting its change |
45 | luk | 247 | |
67 | luk | 248 | // Editor selecting algorithm: |
249 | // 1. Check EDITOR environment variable |
||
250 | // 2. Check VISUAL environment variable |
||
69 | luk | 251 | // 3. Try to get from configuration |
252 | // 4. Check presence of /etc/alternatives/editor |
||
253 | // 5. Use hard-wired editor |
||
254 | |||
61 | luk | 255 | e = getenv("EDITOR"); |
67 | luk | 256 | if (e == NULL) { |
257 | e = getenv("VISUAL"); |
||
258 | if (e == NULL) { |
||
69 | luk | 259 | |
260 | if (!IncronCfg::GetValue("editor", ed)) |
||
261 | throw InotifyException("configuration is corrupted", EINVAL); |
||
262 | |||
263 | if (!ed.empty()) { |
||
264 | e = ed.c_str(); |
||
265 | } |
||
266 | else { |
||
267 | if (access(INCRON_ALT_EDITOR, X_OK) == 0) |
||
268 | e = INCRON_ALT_EDITOR; |
||
269 | else |
||
270 | e = INCRON_DEFAULT_EDITOR; |
||
271 | } |
||
67 | luk | 272 | } |
273 | } |
||
45 | luk | 274 | |
67 | luk | 275 | // this block is explicite due to gotos' usage simplification |
61 | luk | 276 | { |
277 | pid_t pid = fork(); |
||
278 | if (pid == 0) { |
||
63 | luk | 279 | if (setgid(gid) != 0 || setuid(uid) != 0) { |
69 | luk | 280 | fprintf(stderr, "cannot set user '%s': %s\n", rUser.c_str(), strerror(errno)); |
61 | luk | 281 | goto end; |
282 | } |
||
283 | |||
75 | luk | 284 | execlp(e, e, s, (const char*) NULL); |
61 | luk | 285 | _exit(1); |
45 | luk | 286 | } |
61 | luk | 287 | else if (pid > 0) { |
288 | int status; |
||
289 | if (wait(&status) != pid) { |
||
290 | perror("error while waiting for editor"); |
||
291 | goto end; |
||
292 | } |
||
293 | if (!(WIFEXITED(status)) || WEXITSTATUS(status) != 0) { |
||
294 | perror("editor finished with error"); |
||
295 | goto end; |
||
296 | } |
||
45 | luk | 297 | } |
61 | luk | 298 | else { |
299 | perror("cannot start editor"); |
||
300 | goto end; |
||
301 | } |
||
45 | luk | 302 | } |
303 | |||
304 | if (stat(s, &st) != 0) { |
||
305 | fprintf(stderr, "cannot stat temporary file: %s\n", strerror(errno)); |
||
61 | luk | 306 | goto end; |
45 | luk | 307 | } |
308 | |||
309 | if (st.st_mtime == mt) { |
||
310 | fprintf(stderr, "table unchanged\n"); |
||
61 | luk | 311 | ok = true; |
312 | goto end; |
||
45 | luk | 313 | } |
314 | |||
61 | luk | 315 | { |
69 | luk | 316 | IncronTab ict; |
63 | luk | 317 | if (ict.Load(s) && ict.Save(tp)) { |
318 | if (chmod(tp.c_str(), S_IRUSR | S_IWUSR) != 0) { |
||
319 | fprintf(stderr, "cannot change mode of temporary file: %s\n", strerror(errno)); |
||
320 | } |
||
321 | } |
||
322 | else { |
||
61 | luk | 323 | fprintf(stderr, "cannot move temporary table: %s\n", strerror(errno)); |
324 | goto end; |
||
325 | } |
||
63 | luk | 326 | |
45 | luk | 327 | } |
328 | |||
61 | luk | 329 | ok = true; |
45 | luk | 330 | fprintf(stderr, "table updated\n"); |
331 | |||
61 | luk | 332 | end: |
333 | |||
334 | unlink(s); |
||
335 | return ok; |
||
45 | luk | 336 | } |
337 | |||
338 | |||
67 | luk | 339 | /// Prints the list of all available inotify event types. |
340 | void list_types() |
||
341 | { |
||
342 | printf( "IN_ACCESS,IN_MODIFY,IN_ATTRIB,IN_CLOSE_WRITE,"\ |
||
343 | "IN_CLOSE_NOWRITE,IN_OPEN,IN_MOVED_FROM,IN_MOVED_TO,"\ |
||
344 | "IN_CREATE,IN_DELETE,IN_DELETE_SELF,IN_CLOSE,IN_MOVE,"\ |
||
345 | "IN_ONESHOT,IN_ALL_EVENTS"); |
||
346 | |||
347 | #ifdef IN_DONT_FOLLOW |
||
348 | printf(",IN_DONT_FOLLOW"); |
||
349 | #endif // IN_DONT_FOLLOW |
||
350 | |||
351 | #ifdef IN_ONLYDIR |
||
352 | printf(",IN_ONLYDIR"); |
||
353 | #endif // IN_ONLYDIR |
||
354 | |||
355 | #ifdef IN_MOVE_SELF |
||
356 | printf(",IN_MOVE_SELF"); |
||
357 | #endif // IN_MOVE_SELF |
||
358 | |||
359 | printf("\n"); |
||
360 | } |
||
361 | |||
69 | luk | 362 | /// Reloads an user table. |
363 | /** |
||
364 | * \param[in] rUser user name |
||
365 | * \return true = success, false = otherwise |
||
366 | */ |
||
367 | bool reload_table(const std::string& rUser) |
||
368 | { |
||
369 | fprintf(stderr, "requesting table reload for user '%s'...\n", rUser.c_str()); |
||
370 | |||
371 | std::string tp(IncronTab::GetUserTablePath(rUser)); |
||
372 | |||
373 | int fd = open(tp.c_str(), O_WRONLY | O_APPEND); |
||
374 | if (fd == -1) { |
||
375 | if (errno == ENOENT) { |
||
376 | fprintf(stderr, "no table for '%s'\n", rUser.c_str()); |
||
377 | return true; |
||
378 | } |
||
379 | else { |
||
380 | fprintf(stderr, "cannot access table for '%s': %s\n", rUser.c_str(), strerror(errno)); |
||
381 | return false; |
||
382 | } |
||
383 | } |
||
384 | |||
385 | close(fd); |
||
386 | |||
387 | fprintf(stderr, "request done\n"); |
||
388 | |||
389 | return true; |
||
390 | } |
||
67 | luk | 391 | |
45 | luk | 392 | int main(int argc, char** argv) |
393 | { |
||
69 | luk | 394 | AppArgs::Init(); |
395 | |||
396 | if (!( AppArgs::AddOption("about", '?', AAT_NO_VALUE, false) |
||
397 | && AppArgs::AddOption("help", 'h', AAT_NO_VALUE, false) |
||
398 | && AppArgs::AddOption("list", 'l', AAT_NO_VALUE, false) |
||
399 | && AppArgs::AddOption("remove", 'r', AAT_NO_VALUE, false) |
||
400 | && AppArgs::AddOption("edit", 'e', AAT_NO_VALUE, false) |
||
401 | && AppArgs::AddOption("types", 't', AAT_NO_VALUE, false) |
||
402 | && AppArgs::AddOption("reload", 'd', AAT_NO_VALUE, false) |
||
403 | && AppArgs::AddOption("user", 'u', AAT_MANDATORY_VALUE, false) |
||
79 | luk | 404 | && AppArgs::AddOption("config", 'f', AAT_MANDATORY_VALUE, false) |
405 | && AppArgs::AddOption("version", 'V', AAT_NO_VALUE, false))) |
||
69 | luk | 406 | { |
407 | fprintf(stderr, "error while initializing application"); |
||
408 | return 1; |
||
409 | } |
||
45 | luk | 410 | |
69 | luk | 411 | AppArgs::Parse(argc, argv); |
45 | luk | 412 | |
69 | luk | 413 | if (AppArgs::ExistsOption("help")) { |
414 | fprintf(stderr, "%s\n", INCRONTAB_HELP); |
||
415 | return 0; |
||
416 | } |
||
45 | luk | 417 | |
69 | luk | 418 | if (AppArgs::ExistsOption("about")) { |
419 | fprintf(stderr, "%s\n", INCRONTAB_DESCRIPTION); |
||
420 | return 0; |
||
421 | } |
||
422 | |||
423 | if (AppArgs::ExistsOption("version")) { |
||
424 | fprintf(stderr, "%s\n", INCRONTAB_VERSION); |
||
425 | return 0; |
||
426 | } |
||
427 | |||
428 | bool oper = AppArgs::ExistsOption("list") |
||
429 | || AppArgs::ExistsOption("remove") |
||
430 | || AppArgs::ExistsOption("edit") |
||
431 | || AppArgs::ExistsOption("types") |
||
432 | || AppArgs::ExistsOption("reload"); |
||
433 | |||
434 | size_t vals = AppArgs::GetValueCount(); |
||
435 | |||
436 | if (!oper && vals == 0) { |
||
437 | fprintf(stderr, "invalid arguments - specify operation or source file\n"); |
||
45 | luk | 438 | return 1; |
439 | } |
||
69 | luk | 440 | |
441 | if (oper && vals > 0) { |
||
442 | fprintf(stderr, "invalid arguments - operation and source file cannot be combined\n"); |
||
45 | luk | 443 | return 1; |
444 | } |
||
445 | |||
446 | uid_t uid = getuid(); |
||
447 | |||
69 | luk | 448 | std::string user; |
449 | bool chuser = AppArgs::GetOption("user", user); |
||
450 | |||
451 | if (uid != 0 && chuser) { |
||
452 | fprintf(stderr, "cannot override user to '%s': insufficient privileges\n", user.c_str()); |
||
45 | luk | 453 | return 1; |
454 | } |
||
455 | |||
456 | struct passwd pwd; |
||
457 | |||
69 | luk | 458 | if (!chuser) { |
45 | luk | 459 | struct passwd* ppwd = getpwuid(uid); |
460 | if (ppwd == NULL) { |
||
461 | fprintf(stderr, "cannot determine current user\n"); |
||
462 | return 1; |
||
463 | } |
||
464 | memcpy(&pwd, ppwd, sizeof(pwd)); |
||
69 | luk | 465 | user = pwd.pw_name; |
45 | luk | 466 | } |
69 | luk | 467 | else if (getpwnam(user.c_str()) == NULL) { |
468 | fprintf(stderr, "user '%s' not found\n", user.c_str()); |
||
45 | luk | 469 | return 1; |
470 | } |
||
79 | luk | 471 | else if ( setenv("LOGNAME", pwd.pw_name, 1) != 0 |
472 | || setenv("USER", pwd.pw_name, 1) != 0 |
||
473 | || setenv("USERNAME", pwd.pw_name, 1) != 0 |
||
474 | || setenv("HOME", pwd.pw_dir, 1) != 0 |
||
475 | || setenv("SHELL", pwd.pw_shell, 1) != 0) |
||
476 | { |
||
477 | perror("cannot set environment variables"); |
||
478 | return 1; |
||
479 | } |
||
45 | luk | 480 | |
69 | luk | 481 | try { |
45 | luk | 482 | |
69 | luk | 483 | IncronCfg::Init(); |
484 | |||
485 | std::string cfg(INCRON_CONFIG); |
||
486 | if (AppArgs::GetOption("config", cfg)) { |
||
487 | if (uid != 0) { |
||
488 | fprintf(stderr, "insufficient privileges to use custom configuration (will use default)\n"); |
||
489 | } |
||
490 | else if (euidaccess(cfg.c_str(), R_OK) != 0) { |
||
491 | perror("cannot read configuration file (will use default)"); |
||
492 | } |
||
493 | } |
||
494 | |||
495 | IncronCfg::Load(cfg); |
||
496 | |||
497 | if (!IncronTab::CheckUser(user)) { |
||
498 | fprintf(stderr, "user '%s' is not allowed to use incron\n", user.c_str()); |
||
499 | return 1; |
||
500 | } |
||
501 | |||
502 | if (!oper) { |
||
503 | std::string file; |
||
504 | if (!AppArgs::GetValue(0, file) |
||
505 | || !copy_from_file(file, user)) |
||
506 | { |
||
45 | luk | 507 | return 1; |
69 | luk | 508 | } |
509 | } |
||
510 | else { |
||
511 | if (AppArgs::ExistsOption("list")) { |
||
512 | if (!list_table(user)) |
||
513 | return 1; |
||
514 | } |
||
515 | else if (AppArgs::ExistsOption("remove")) { |
||
516 | if (!remove_table(user)) |
||
517 | return 1; |
||
518 | } |
||
519 | else if (AppArgs::ExistsOption("edit")) { |
||
520 | if (!edit_table(user)) |
||
521 | return 1; |
||
522 | } |
||
523 | else if (AppArgs::ExistsOption("types")) { |
||
524 | list_types(); |
||
525 | } |
||
526 | else if (AppArgs::ExistsOption("reload")) { |
||
527 | if (!reload_table(user)) |
||
528 | return 1; |
||
529 | } |
||
530 | else { |
||
531 | fprintf(stderr, "invalid usage\n"); |
||
45 | luk | 532 | return 1; |
69 | luk | 533 | } |
534 | } |
||
535 | |||
536 | return 0; |
||
537 | |||
538 | } catch (InotifyException e) { |
||
539 | fprintf(stderr, "*** unhandled exception occurred ***\n"); |
||
540 | fprintf(stderr, "%s\n", e.GetMessage().c_str()); |
||
541 | fprintf(stderr, "error: (%i) %s\n", e.GetErrorNumber(), strerror(e.GetErrorNumber())); |
||
542 | |||
543 | return 1; |
||
45 | luk | 544 | } |
545 | } |