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