1/* MiniDLNA media server 2 * Copyright (C) 2008-2010 Justin Maggard 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation; either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, write to the Free Software 16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 */ 18#include "config.h" 19#include <stdio.h> 20#include <string.h> 21#include <stdlib.h> 22#include <errno.h> 23#include <unistd.h> 24#include <dirent.h> 25#include <libgen.h> 26#include <errno.h> 27#include <sys/types.h> 28#include <sys/stat.h> 29#include <sys/time.h> 30#include <sys/resource.h> 31#include <poll.h> 32#ifdef HAVE_INOTIFY_H 33#include <sys/inotify.h> 34#else 35#include "linux/inotify.h" 36#include "linux/inotify-syscalls.h" 37#endif 38 39#include "upnpglobalvars.h" 40#include "inotify.h" 41#include "utils.h" 42#include "sql.h" 43#include "scanner.h" 44#include "metadata.h" 45#include "albumart.h" 46#include "playlist.h" 47#include "log.h" 48 49#define EVENT_SIZE ( sizeof (struct inotify_event) ) 50#define BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) ) 51#define DESIRED_WATCH_LIMIT 65536 52 53#define PATH_BUF_SIZE PATH_MAX 54 55struct watch 56{ 57 int wd; /* watch descriptor */ 58 char *path; /* watched path */ 59 struct watch *next; 60}; 61 62static struct watch *watches; 63static struct watch *lastwatch = NULL; 64static time_t next_pl_fill = 0; 65 66char *get_path_from_wd(int wd) 67{ 68 struct watch *w = watches; 69 70 while( w != NULL ) 71 { 72 if( w->wd == wd ) 73 return w->path; 74 w = w->next; 75 } 76 77 return NULL; 78} 79 80int 81add_watch(int fd, const char * path) 82{ 83 struct watch *nw; 84 int wd; 85 86 wd = inotify_add_watch(fd, path, IN_CREATE|IN_CLOSE_WRITE|IN_DELETE|IN_MOVE); 87 if( wd < 0 ) 88 { 89 DPRINTF(E_ERROR, L_INOTIFY, "inotify_add_watch(%s) [%s]\n", path, strerror(errno)); 90 return -1; 91 } 92 93 nw = malloc(sizeof(struct watch)); 94 if( nw == NULL ) 95 { 96 DPRINTF(E_ERROR, L_INOTIFY, "malloc() error\n"); 97 return -1; 98 } 99 nw->wd = wd; 100 nw->next = NULL; 101 nw->path = strdup(path); 102 103 if( watches == NULL ) 104 { 105 watches = nw; 106 } 107 108 if( lastwatch != NULL ) 109 { 110 lastwatch->next = nw; 111 } 112 lastwatch = nw; 113 114 return wd; 115} 116 117int 118remove_watch(int fd, const char * path) 119{ 120 struct watch *w; 121 122 for( w = watches; w; w = w->next ) 123 { 124 if( strcmp(path, w->path) == 0 ) 125 return(inotify_rm_watch(fd, w->wd)); 126 } 127 128 return 1; 129} 130 131unsigned int 132next_highest(unsigned int num) 133{ 134 num |= num >> 1; 135 num |= num >> 2; 136 num |= num >> 4; 137 num |= num >> 8; 138 num |= num >> 16; 139 return(++num); 140} 141 142int 143inotify_create_watches(int fd) 144{ 145 FILE * max_watches; 146 unsigned int num_watches, watch_limit = 8192; 147 char **result; 148 int i, rows = 0; 149 struct media_dir_s * media_path; 150 151 i = sql_get_int_field(db, "SELECT count(*) from DETAILS where SIZE is NULL and PATH is not NULL"); 152 num_watches = (i < 0) ? 0 : i; 153 154 max_watches = fopen("/proc/sys/fs/inotify/max_user_watches", "r"); 155 if( max_watches ) 156 { 157 fscanf(max_watches, "%u", &watch_limit); 158 fclose(max_watches); 159 if( (watch_limit < DESIRED_WATCH_LIMIT) || (watch_limit < (num_watches*3/4)) ) 160 { 161 max_watches = fopen("/proc/sys/fs/inotify/max_user_watches", "w"); 162 if( max_watches ) 163 { 164 if( DESIRED_WATCH_LIMIT >= (num_watches*3/4) ) 165 { 166 fprintf(max_watches, "%u", DESIRED_WATCH_LIMIT); 167 } 168 else if( next_highest(num_watches) >= (num_watches*3/4) ) 169 { 170 fprintf(max_watches, "%u", next_highest(num_watches)); 171 } 172 else 173 { 174 fprintf(max_watches, "%u", next_highest(next_highest(num_watches))); 175 } 176 fclose(max_watches); 177 } 178 else 179 { 180 DPRINTF(E_WARN, L_INOTIFY, "WARNING: Inotify max_user_watches [%u] is low or close to the number of used watches [%u] " 181 "and I do not have permission to increase this limit. Please do so manually by " 182 "writing a higher value into /proc/sys/fs/inotify/max_user_watches.\n", watch_limit, num_watches); 183 } 184 } 185 } 186 else 187 { 188 DPRINTF(E_WARN, L_INOTIFY, "WARNING: Could not read inotify max_user_watches! " 189 "Hopefully it is enough to cover %u current directories plus any new ones added.\n", num_watches); 190 } 191 192 media_path = media_dirs; 193 while( media_path ) 194 { 195 add_watch(fd, media_path->path); 196 media_path = media_path->next; 197 } 198 sql_get_table(db, "SELECT PATH from DETAILS where SIZE is NULL and PATH is not NULL", &result, &rows, NULL); 199 for( i=1; i <= rows; i++ ) 200 { 201 DPRINTF(E_DEBUG, L_INOTIFY, "Add watch to %s\n", result[i]); 202 add_watch(fd, result[i]); 203 } 204 sqlite3_free_table(result); 205 206 return rows; 207} 208 209int 210inotify_remove_watches(int fd) 211{ 212 struct watch *w = watches; 213 struct watch *last_w; 214 int rm_watches = 0; 215 216 while( w ) 217 { 218 last_w = w; 219 inotify_rm_watch(fd, w->wd); 220 if( w->path ) 221 free(w->path); 222 rm_watches++; 223 w = w->next; 224 free(last_w); 225 } 226 227 return rm_watches; 228} 229 230int add_dir_watch(int fd, char * path, char * filename) 231{ 232 DIR *ds; 233 struct dirent *e; 234 char *buf; 235 int wd; 236 int i = 0; 237 238 if( filename ) 239 asprintf(&buf, "%s/%s", path, filename); 240 else 241 buf = strdup(path); 242 243 wd = add_watch(fd, buf); 244 if( wd == -1 ) 245 { 246 DPRINTF(E_ERROR, L_INOTIFY, "add_watch() [%s]\n", strerror(errno)); 247 } 248 else 249 { 250 DPRINTF(E_INFO, L_INOTIFY, "Added watch to %s [%d]\n", buf, wd); 251 } 252 253 ds = opendir(buf); 254 if( ds != NULL ) 255 { 256 while( (e = readdir(ds)) ) 257 { 258 if( strcmp(e->d_name, ".") == 0 || 259 strcmp(e->d_name, "..") == 0 ) 260 continue; 261 if( (e->d_type == DT_DIR) || 262 (e->d_type == DT_UNKNOWN && resolve_unknown_type(buf, NO_MEDIA) == TYPE_DIR) ) 263 i += add_dir_watch(fd, buf, e->d_name); 264 } 265 } 266 else 267 { 268 DPRINTF(E_ERROR, L_INOTIFY, "Opendir error! [%s]\n", strerror(errno)); 269 } 270 closedir(ds); 271 i++; 272 free(buf); 273 274 return(i); 275} 276 277int 278inotify_insert_file(char * name, const char * path) 279{ 280 char * sql; 281 char **result; 282 int rows; 283 char * last_dir = strdup(path); 284 char * path_buf = strdup(path); 285 char * base_name = malloc(strlen(path)); 286 char * base_copy = base_name; 287 char * parent_buf = NULL; 288 char * id = NULL; 289 int depth = 1; 290 enum media_types type = ALL_MEDIA; 291 struct media_dir_s * media_path = media_dirs; 292 struct stat st; 293 294 /* Is it cover art for another file? */ 295 if( is_image(path) ) 296 update_if_album_art(path); 297 else if( ends_with(path, ".srt") ) 298 check_for_captions(path, 0); 299 300 /* Check if we're supposed to be scanning for this file type in this directory */ 301 while( media_path ) 302 { 303 if( strncmp(path, media_path->path, strlen(media_path->path)) == 0 ) 304 { 305 type = media_path->type; 306 break; 307 } 308 media_path = media_path->next; 309 } 310 switch( type ) 311 { 312 case ALL_MEDIA: 313 if( !is_image(path) && 314 !is_audio(path) && 315 !is_video(path) && 316 !is_playlist(path) ) 317 return -1; 318 break; 319 case AUDIO_ONLY: 320 if( !is_audio(path) && 321 !is_playlist(path) ) 322 return -1; 323 break; 324 case VIDEO_ONLY: 325 if( !is_video(path) ) 326 return -1; 327 break; 328 case IMAGES_ONLY: 329 if( !is_image(path) ) 330 return -1; 331 break; 332 default: 333 return -1; 334 break; 335 } 336 337 /* If it's already in the database and hasn't been modified, skip it. */ 338 if( stat(path, &st) != 0 ) 339 return -1; 340 341 sql = sqlite3_mprintf("SELECT TIMESTAMP from DETAILS where PATH = '%q'", path); 342 if( sql_get_table(db, sql, &result, &rows, NULL) == SQLITE_OK ) 343 { 344 if( rows ) 345 { 346 if( atoi(result[1]) < st.st_mtime ) 347 { 348 DPRINTF(E_DEBUG, L_INOTIFY, "%s is newer than the last db entry.\n", path); 349 inotify_remove_file(path_buf); 350 } 351 else 352 { 353 free(last_dir); 354 free(path_buf); 355 free(base_name); 356 sqlite3_free(sql); 357 sqlite3_free_table(result); 358 return -1; 359 } 360 } 361 else if( is_playlist(path) && (sql_get_int_field(db, "SELECT ID from PLAYLISTS where PATH = '%q'", path) > 0) ) 362 { 363 DPRINTF(E_DEBUG, L_INOTIFY, "Re-reading modified playlist.\n", path); 364 inotify_remove_file(path_buf); 365 next_pl_fill = 1; 366 } 367 sqlite3_free_table(result); 368 } 369 sqlite3_free(sql); 370 /* Find the parentID. If it's not found, create all necessary parents. */ 371 while( depth ) 372 { 373 depth = 0; 374 strcpy(path_buf, path); 375 parent_buf = dirname(path_buf); 376 strcpy(last_dir, path_buf); 377 378 do 379 { 380 //DEBUG DPRINTF(E_DEBUG, L_INOTIFY, "Checking %s\n", parent_buf); 381 sql = sqlite3_mprintf("SELECT OBJECT_ID from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" 382 " where d.PATH = '%q' and REF_ID is NULL", parent_buf); 383 if( (sql_get_table(db, sql, &result, &rows, NULL) == SQLITE_OK) && rows ) 384 { 385 id = strdup(result[1]); 386 sqlite3_free_table(result); 387 sqlite3_free(sql); 388 if( !depth ) 389 break; 390 DPRINTF(E_DEBUG, L_INOTIFY, "Found first known parentID: %s\n", id); 391 /* Insert newly-found directory */ 392 strcpy(base_name, last_dir); 393 base_copy = basename(base_name); 394 insert_directory(base_copy, last_dir, BROWSEDIR_ID, id+2, get_next_available_id("OBJECTS", id)); 395 free(id); 396 break; 397 } 398 depth++; 399 strcpy(last_dir, path_buf); 400 parent_buf = dirname(parent_buf); 401 sqlite3_free_table(result); 402 sqlite3_free(sql); 403 } 404 while( strcmp(parent_buf, "/") != 0 ); 405 406 if( strcmp(parent_buf, "/") == 0 ) 407 { 408 id = strdup(BROWSEDIR_ID); 409 depth = 0; 410 break; 411 } 412 strcpy(path_buf, path); 413 } 414 free(last_dir); 415 free(path_buf); 416 free(base_name); 417 418 if( !depth ) 419 { 420 //DEBUG DPRINTF(E_DEBUG, L_INOTIFY, "Inserting %s\n", name); 421 insert_file(name, path, id+2, get_next_available_id("OBJECTS", id)); 422 free(id); 423 if( (is_audio(path) || is_playlist(path)) && next_pl_fill != 1 ) 424 { 425 next_pl_fill = time(NULL) + 120; // Schedule a playlist scan for 2 minutes from now. 426 //DEBUG DPRINTF(E_WARN, L_INOTIFY, "Playlist scan scheduled for %s", ctime(&next_pl_fill)); 427 } 428 } 429 return depth; 430} 431 432int 433inotify_insert_directory(int fd, char *name, const char * path) 434{ 435 DIR * ds; 436 struct dirent * e; 437 char * sql; 438 char **result; 439 char *id=NULL, *path_buf, *parent_buf, *esc_name; 440 int wd; 441 int rows; 442 enum file_types type = TYPE_UNKNOWN; 443 enum media_types dir_type = ALL_MEDIA; 444 struct media_dir_s * media_path; 445 struct stat st; 446 447 parent_buf = dirname(strdup(path)); 448 sql = sqlite3_mprintf("SELECT OBJECT_ID from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" 449 " where d.PATH = '%q' and REF_ID is NULL", parent_buf); 450 if( sql_get_table(db, sql, &result, &rows, NULL) == SQLITE_OK ) 451 { 452 id = strdup(rows?result[1]:BROWSEDIR_ID); 453 insert_directory(name, path, BROWSEDIR_ID, id+2, get_next_available_id("OBJECTS", id)); 454 free(id); 455 } 456 free(parent_buf); 457 sqlite3_free(sql); 458 459 wd = add_watch(fd, path); 460 if( wd == -1 ) 461 { 462 DPRINTF(E_ERROR, L_INOTIFY, "add_watch() failed"); 463 } 464 else 465 { 466 DPRINTF(E_INFO, L_INOTIFY, "Added watch to %s [%d]\n", path, wd); 467 } 468 469 media_path = media_dirs; 470 while( media_path ) 471 { 472 if( strncmp(path, media_path->path, strlen(media_path->path)) == 0 ) 473 { 474 dir_type = media_path->type; 475 break; 476 } 477 media_path = media_path->next; 478 } 479 480 ds = opendir(path); 481 if( !ds ) 482 { 483 DPRINTF(E_ERROR, L_INOTIFY, "opendir failed! [%s]\n", strerror(errno)); 484 return -1; 485 } 486 while( (e = readdir(ds)) ) 487 { 488 if( e->d_name[0] == '.' ) 489 continue; 490 esc_name = escape_tag(e->d_name); 491 if( !esc_name ) 492 esc_name = strdup(e->d_name); 493 asprintf(&path_buf, "%s/%s", path, e->d_name); 494 switch( e->d_type ) 495 { 496 case DT_DIR: 497 case DT_REG: 498 case DT_LNK: 499 case DT_UNKNOWN: 500 type = resolve_unknown_type(path_buf, dir_type); 501 default: 502 break; 503 } 504 if( type == TYPE_DIR ) 505 { 506 inotify_insert_directory(fd, esc_name, path_buf); 507 } 508 else if( type == TYPE_FILE ) 509 { 510 if( (stat(path_buf, &st) == 0) && (st.st_blocks<<9 >= st.st_size) ) 511 { 512 inotify_insert_file(esc_name, path_buf); 513 } 514 } 515 free(esc_name); 516 free(path_buf); 517 } 518 closedir(ds); 519 520 return 0; 521} 522 523int 524inotify_remove_file(const char * path) 525{ 526 char * sql; 527 char **result; 528 char * art_cache; 529 sqlite_int64 detailID = 0; 530 int i, rows, children, playlist, ret = 1; 531 532 /* Invalidate the scanner cache so we don't insert files into non-existent containers */ 533 valid_cache = 0; 534 playlist = is_playlist(path); 535 sql = sqlite3_mprintf("SELECT ID from %s where PATH = '%q'", playlist?"PLAYLISTS":"DETAILS", path); 536 if( (sql_get_table(db, sql, &result, &rows, NULL) == SQLITE_OK) ) 537 { 538 if( rows ) 539 { 540 detailID = strtoll(result[1], NULL, 10); 541 ret = 0; 542 } 543 sqlite3_free_table(result); 544 } 545 sqlite3_free(sql); 546 if( playlist && detailID ) 547 { 548 sql_exec(db, "DELETE from PLAYLISTS where ID = %lld", detailID); 549 sql_exec(db, "DELETE from DETAILS where ID =" 550 " (SELECT DETAIL_ID from OBJECTS where OBJECT_ID = '%s$%lld')", 551 MUSIC_PLIST_ID, detailID); 552 sql_exec(db, "DELETE from OBJECTS where OBJECT_ID = '%s$%lld' or PARENT_ID = '%s$%lld'", 553 MUSIC_PLIST_ID, detailID, MUSIC_PLIST_ID, detailID); 554 } 555 else if( detailID ) 556 { 557 /* Delete the parent containers if we are about to empty them. */ 558 asprintf(&sql, "SELECT PARENT_ID from OBJECTS where DETAIL_ID = %lld", detailID); 559 if( (sql_get_table(db, sql, &result, &rows, NULL) == SQLITE_OK) ) 560 { 561 for( i=1; i <= rows; i++ ) 562 { 563 /* If it's a playlist item, adjust the item count of the playlist */ 564 if( strncmp(result[i], MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) 565 { 566 sql_exec(db, "UPDATE PLAYLISTS set FOUND = (FOUND-1) where ID = %d", 567 atoi(strrchr(result[i], '$') + 1)); 568 } 569 570 children = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", result[i]); 571 if( children < 0 ) 572 continue; 573 if( children < 2 ) 574 { 575 sql_exec(db, "DELETE from DETAILS where ID =" 576 " (SELECT DETAIL_ID from OBJECTS where OBJECT_ID = '%s')", result[i]); 577 sql_exec(db, "DELETE from OBJECTS where OBJECT_ID = '%s'", result[i]); 578 579 *rindex(result[i], '$') = '\0'; 580 if( sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", result[i]) == 0 ) 581 { 582 sql_exec(db, "DELETE from DETAILS where ID =" 583 " (SELECT DETAIL_ID from OBJECTS where OBJECT_ID = '%s')", result[i]); 584 sql_exec(db, "DELETE from OBJECTS where OBJECT_ID = '%s'", result[i]); 585 } 586 } 587 } 588 sqlite3_free_table(result); 589 } 590 free(sql); 591 /* Now delete the actual objects */ 592 sql_exec(db, "DELETE from DETAILS where ID = %lld", detailID); 593 sql_exec(db, "DELETE from OBJECTS where DETAIL_ID = %lld", detailID); 594 } 595 asprintf(&art_cache, "%s/art_cache%s", db_path, path); 596 remove(art_cache); 597 free(art_cache); 598 599 return ret; 600} 601 602int 603inotify_remove_directory(int fd, const char * path) 604{ 605 char * sql; 606 char **result; 607 sqlite_int64 detailID = 0; 608 int rows, i, ret = 1; 609 610 remove_watch(fd, path); 611 sql = sqlite3_mprintf("SELECT ID from DETAILS where PATH glob '%q/*'" 612 " UNION ALL SELECT ID from DETAILS where PATH = '%q'", path, path); 613 if( (sql_get_table(db, sql, &result, &rows, NULL) == SQLITE_OK) ) 614 { 615 if( rows ) 616 { 617 for( i=1; i <= rows; i++ ) 618 { 619 detailID = strtoll(result[i], NULL, 10); 620 sql_exec(db, "DELETE from DETAILS where ID = %lld", detailID); 621 sql_exec(db, "DELETE from OBJECTS where DETAIL_ID = %lld", detailID); 622 } 623 ret = 0; 624 } 625 sqlite3_free_table(result); 626 } 627 sqlite3_free(sql); 628 /* Clean up any album art entries in the deleted directory */ 629 sql_exec(db, "DELETE from ALBUM_ART where PATH glob '%q/*'", path); 630 631 return ret; 632} 633 634void * 635start_inotify() 636{ 637 struct pollfd pollfds[1]; 638 int timeout = 1000; 639 char buffer[BUF_LEN]; 640 char path_buf[PATH_MAX]; 641 int length, i = 0; 642 char * esc_name = NULL; 643 struct stat st; 644 645 pollfds[0].fd = inotify_init(); 646 pollfds[0].events = POLLIN; 647 648 if ( pollfds[0].fd < 0 ) { 649 DPRINTF(E_ERROR, L_INOTIFY, "inotify_init() failed!\n"); 650 } 651 652 while( scanning ) 653 { 654 if( quitting ) 655 goto quitting; 656 sleep(1); 657 } 658 inotify_create_watches(pollfds[0].fd); 659 if (setpriority(PRIO_PROCESS, 0, 19) == -1) 660 DPRINTF(E_WARN, L_INOTIFY, "Failed to reduce inotify thread priority\n"); 661 662 while( !quitting ) 663 { 664 length = poll(pollfds, 1, timeout); 665 if( !length ) 666 { 667 if( next_pl_fill && (time(NULL) >= next_pl_fill) ) 668 { 669 fill_playlists(); 670 next_pl_fill = 0; 671 } 672 continue; 673 } 674 else if( length < 0 ) 675 { 676 if( (errno == EINTR) || (errno == EAGAIN) ) 677 continue; 678 else 679 DPRINTF(E_ERROR, L_INOTIFY, "read failed!\n"); 680 } 681 else 682 { 683 length = read(pollfds[0].fd, buffer, BUF_LEN); 684 } 685 686 i = 0; 687 while( i < length ) 688 { 689 struct inotify_event * event = (struct inotify_event *) &buffer[i]; 690 if( event->len ) 691 { 692 if( *(event->name) == '.' ) 693 { 694 i += EVENT_SIZE + event->len; 695 continue; 696 } 697 esc_name = modifyString(strdup(event->name), "&", "&amp;", 0); 698 sprintf(path_buf, "%s/%s", get_path_from_wd(event->wd), event->name); 699 if ( (event->mask & IN_CREATE && event->mask & IN_ISDIR) || 700 (event->mask & IN_MOVED_TO && event->mask & IN_ISDIR) ) 701 { 702 DPRINTF(E_DEBUG, L_INOTIFY, "The directory %s was %s.\n", path_buf, (event->mask & IN_MOVED_TO ? "moved here" : "created")); 703 inotify_insert_directory(pollfds[0].fd, esc_name, path_buf); 704 } 705 else if ( event->mask & IN_CLOSE_WRITE || event->mask & IN_MOVED_TO ) 706 { 707 if( stat(path_buf, &st) == 0 && st.st_size > 0 ) 708 { 709 DPRINTF(E_DEBUG, L_INOTIFY, "The file %s was %s.\n", path_buf, (event->mask & IN_MOVED_TO ? "moved here" : "changed")); 710 inotify_insert_file(esc_name, path_buf); 711 } 712 } 713 else if ( event->mask & IN_DELETE || event->mask & IN_MOVED_FROM ) 714 { 715 if ( event->mask & IN_ISDIR ) 716 { 717 DPRINTF(E_DEBUG, L_INOTIFY, "The directory %s was %s.\n", path_buf, (event->mask & IN_MOVED_FROM ? "moved away" : "deleted")); 718 inotify_remove_directory(pollfds[0].fd, path_buf); 719 } 720 else 721 { 722 DPRINTF(E_DEBUG, L_INOTIFY, "The file %s was %s.\n", path_buf, (event->mask & IN_MOVED_FROM ? "moved away" : "deleted")); 723 inotify_remove_file(path_buf); 724 } 725 } 726 free(esc_name); 727 } 728 i += EVENT_SIZE + event->len; 729 } 730 } 731 inotify_remove_watches(pollfds[0].fd); 732quitting: 733 close(pollfds[0].fd); 734 735 return 0; 736} 737