/* Thread: scan */ static void bulk_scan(void) { cfg_t *lib; int ndirs; char *path; char *deref; time_t start; int i; start = time(NULL); playlists = NULL; dirstack = NULL; lib = cfg_getsec(cfg, "library"); ndirs = cfg_size(lib, "directories"); for (i = 0; i < ndirs; i++) { path = cfg_getnstr(lib, "directories", i); deref = m_realpath(path); if (!deref) { DPRINTF(E_LOG, L_SCAN, "Skipping library directory %s, could not dereference: %s\n", path, strerror(errno)); continue; } process_directories(deref, F_SCAN_BULK); free(deref); if (scan_exit) return; } if (playlists) process_deferred_playlists(); if (scan_exit) return; if (dirstack) DPRINTF(E_LOG, L_SCAN, "WARNING: unhandled leftover directories\n"); DPRINTF(E_DBG, L_SCAN, "Purging old database content\n"); db_purge_cruft(start); }
/* Thread: httpd */ static void serve_file(struct evhttp_request *req, char *uri) { const char *host; char *ext; char path[PATH_MAX]; char *deref; char *ctype; char *passwd; struct evbuffer *evbuf; struct evkeyvalq *headers; struct stat sb; int fd; int i; int ret; /* Check authentication */ passwd = cfg_getstr(cfg_getsec(cfg, "general"), "admin_password"); if (passwd) { DPRINTF(E_DBG, L_HTTPD, "Checking web interface authentication\n"); ret = httpd_basic_auth(req, "admin", passwd, PACKAGE " web interface"); if (ret != 0) return; DPRINTF(E_DBG, L_HTTPD, "Authentication successful\n"); } else { host = evhttp_request_get_host(req); if ((strcmp(host, "::1") != 0) && (strcmp(host, "127.0.0.1") != 0)) { DPRINTF(E_LOG, L_HTTPD, "Remote web interface request denied; no password set\n"); evhttp_send_error(req, 403, "Forbidden"); return; } } ret = snprintf(path, sizeof(path), "%s%s", WEBFACE_ROOT, uri + 1); /* skip starting '/' */ if ((ret < 0) || (ret >= sizeof(path))) { DPRINTF(E_LOG, L_HTTPD, "Request exceeds PATH_MAX: %s\n", uri); evhttp_send_error(req, HTTP_NOTFOUND, "Not Found"); return; } ret = lstat(path, &sb); if (ret < 0) { DPRINTF(E_LOG, L_HTTPD, "Could not lstat() %s: %s\n", path, strerror(errno)); evhttp_send_error(req, HTTP_NOTFOUND, "Not Found"); return; } if (S_ISDIR(sb.st_mode)) { redirect_to_index(req, uri); return; } else if (S_ISLNK(sb.st_mode)) { deref = m_realpath(path); if (!deref) { DPRINTF(E_LOG, L_HTTPD, "Could not dereference %s: %s\n", path, strerror(errno)); evhttp_send_error(req, HTTP_NOTFOUND, "Not Found"); return; } if (strlen(deref) + 1 > PATH_MAX) { DPRINTF(E_LOG, L_HTTPD, "Dereferenced path exceeds PATH_MAX: %s\n", path); evhttp_send_error(req, HTTP_NOTFOUND, "Not Found"); free(deref); return; } strcpy(path, deref); free(deref); ret = stat(path, &sb); if (ret < 0) { DPRINTF(E_LOG, L_HTTPD, "Could not stat() %s: %s\n", path, strerror(errno)); evhttp_send_error(req, HTTP_NOTFOUND, "Not Found"); return; } if (S_ISDIR(sb.st_mode)) { redirect_to_index(req, uri); return; } } if (path_is_legal(path) != 0) { evhttp_send_error(req, 403, "Forbidden"); return; } evbuf = evbuffer_new(); if (!evbuf) { DPRINTF(E_LOG, L_HTTPD, "Could not create evbuffer\n"); evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal error"); return; } fd = open(path, O_RDONLY); if (fd < 0) { DPRINTF(E_LOG, L_HTTPD, "Could not open %s: %s\n", path, strerror(errno)); evhttp_send_error(req, HTTP_NOTFOUND, "Not Found"); return; } /* FIXME: this is broken, if we ever need to serve files here, * this must be fixed. */ ret = evbuffer_read(evbuf, fd, sb.st_size); close(fd); if (ret < 0) { DPRINTF(E_LOG, L_HTTPD, "Could not read file into evbuffer\n"); evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal error"); return; } ctype = "application/octet-stream"; ext = strrchr(path, '.'); if (ext) { for (i = 0; ext2ctype[i].ext; i++) { if (strcmp(ext, ext2ctype[i].ext) == 0) { ctype = ext2ctype[i].ctype; break; } } } headers = evhttp_request_get_output_headers(req); evhttp_add_header(headers, "Content-Type", ctype); evhttp_send_reply(req, HTTP_OK, "OK", evbuf); evbuffer_free(evbuf); }
/* Thread: scan */ static void bulk_scan(int flags) { cfg_t *lib; int ndirs; char *path; char *deref; time_t start; time_t end; int i; start = time(NULL); playlists = NULL; dirstack = NULL; lib = cfg_getsec(cfg, "library"); ndirs = cfg_size(lib, "directories"); for (i = 0; i < ndirs; i++) { path = cfg_getnstr(lib, "directories", i); deref = m_realpath(path); if (!deref) { DPRINTF(E_LOG, L_SCAN, "Skipping library directory %s, could not dereference: %s\n", path, strerror(errno)); /* Assume dir is mistakenly not mounted, so just disable everything and update timestamps */ db_file_disable_bymatch(path, "", 0); db_pl_disable_bymatch(path, "", 0); db_file_ping_bymatch(path, 1); db_pl_ping_bymatch(path, 1); continue; } counter = 0; db_transaction_begin(); process_directories(deref, flags); db_transaction_end(); free(deref); if (scan_exit) return; } if (!(flags & F_SCAN_FAST) && playlists) process_deferred_playlists(); if (scan_exit) return; if (dirstack) DPRINTF(E_LOG, L_SCAN, "WARNING: unhandled leftover directories\n"); end = time(NULL); if (flags & F_SCAN_FAST) { DPRINTF(E_LOG, L_SCAN, "Bulk library scan completed in %.f sec (with file scan disabled)\n", difftime(end, start)); } else { /* Protect spotify from the imminent purge if rescanning */ if (flags & F_SCAN_RESCAN) { db_file_ping_bymatch("spotify:", 0); db_pl_ping_bymatch("spotify:", 0); } DPRINTF(E_DBG, L_SCAN, "Purging old database content\n"); db_purge_cruft(start); DPRINTF(E_LOG, L_SCAN, "Bulk library scan completed in %.f sec\n", difftime(end, start)); DPRINTF(E_DBG, L_SCAN, "Running post library scan jobs\n"); db_hook_post_scan(); } }
/* Thread: scan */ static void process_directory(char *path, int flags) { DIR *dirp; struct dirent buf; struct dirent *de; char entry[PATH_MAX]; char *deref; struct stat sb; struct watch_info wi; #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) struct kevent kev; #endif int type; int ret; DPRINTF(E_DBG, L_SCAN, "Processing directory %s (flags = 0x%x)\n", path, flags); dirp = opendir(path); if (!dirp) { DPRINTF(E_LOG, L_SCAN, "Could not open directory %s: %s\n", path, strerror(errno)); return; } /* Check if compilation and/or podcast directory */ type = 0; if (check_speciallib(path, "compilations")) type |= F_SCAN_TYPE_COMPILATION; if (check_speciallib(path, "podcasts")) type |= F_SCAN_TYPE_PODCAST; if (check_speciallib(path, "audiobooks")) type |= F_SCAN_TYPE_AUDIOBOOK; for (;;) { if (scan_exit) break; ret = readdir_r(dirp, &buf, &de); if (ret != 0) { DPRINTF(E_LOG, L_SCAN, "readdir_r error in %s: %s\n", path, strerror(errno)); break; } if (de == NULL) break; if (buf.d_name[0] == '.') continue; ret = snprintf(entry, sizeof(entry), "%s/%s", path, buf.d_name); if ((ret < 0) || (ret >= sizeof(entry))) { DPRINTF(E_LOG, L_SCAN, "Skipping %s/%s, PATH_MAX exceeded\n", path, buf.d_name); continue; } ret = lstat(entry, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Skipping %s, lstat() failed: %s\n", entry, strerror(errno)); continue; } if (S_ISLNK(sb.st_mode)) { deref = m_realpath(entry); if (!deref) { DPRINTF(E_LOG, L_SCAN, "Skipping %s, could not dereference symlink: %s\n", entry, strerror(errno)); continue; } ret = stat(deref, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Skipping %s, stat() failed: %s\n", deref, strerror(errno)); free(deref); continue; } ret = snprintf(entry, sizeof(entry), "%s", deref); free(deref); if ((ret < 0) || (ret >= sizeof(entry))) { DPRINTF(E_LOG, L_SCAN, "Skipping %s, PATH_MAX exceeded\n", deref); continue; } } if (S_ISREG(sb.st_mode)) { if (!(flags & F_SCAN_FAST)) process_file(entry, sb.st_mtime, sb.st_size, F_SCAN_TYPE_FILE | type, flags); } else if (S_ISFIFO(sb.st_mode)) { if (!(flags & F_SCAN_FAST)) process_file(entry, sb.st_mtime, sb.st_size, F_SCAN_TYPE_PIPE | type, flags); } else if (S_ISDIR(sb.st_mode)) push_dir(&dirstack, entry); else DPRINTF(E_LOG, L_SCAN, "Skipping %s, not a directory, symlink, pipe nor regular file\n", entry); } closedir(dirp); memset(&wi, 0, sizeof(struct watch_info)); #if defined(__linux__) /* Add inotify watch */ wi.wd = inotify_add_watch(inofd, path, IN_ATTRIB | IN_CREATE | IN_DELETE | IN_CLOSE_WRITE | IN_MOVE | IN_DELETE | IN_MOVE_SELF); if (wi.wd < 0) { DPRINTF(E_WARN, L_SCAN, "Could not create inotify watch for %s: %s\n", path, strerror(errno)); return; } if (!(flags & F_SCAN_MOVED)) { wi.cookie = 0; wi.path = path; db_watch_add(&wi); } #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) memset(&kev, 0, sizeof(struct kevent)); wi.wd = open(path, O_RDONLY | O_NONBLOCK); if (wi.wd < 0) { DPRINTF(E_WARN, L_SCAN, "Could not open directory %s for watching: %s\n", path, strerror(errno)); return; } /* Add kevent */ EV_SET(&kev, wi.wd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_DELETE | NOTE_WRITE | NOTE_RENAME, 0, NULL); ret = kevent(inofd, &kev, 1, NULL, 0, NULL); if (ret < 0) { DPRINTF(E_WARN, L_SCAN, "Could not add kevent for %s: %s\n", path, strerror(errno)); close(wi.wd); return; } wi.cookie = 0; wi.path = path; db_watch_add(&wi); #endif }
/* Thread: scan */ static void process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie) { struct stat sb; char *deref = NULL; char *file = path; int type; int ret; DPRINTF(E_SPAM, L_SCAN, "File event: 0x%x, cookie 0x%x, wd %d\n", ie->mask, ie->cookie, wi->wd); if (ie->mask & IN_DELETE) { DPRINTF(E_DBG, L_SCAN, "File deleted: %s\n", path); db_file_delete_bypath(path); db_pl_delete_bypath(path); } if (ie->mask & IN_MOVED_FROM) { DPRINTF(E_DBG, L_SCAN, "File moved from: %s\n", path); db_file_disable_bypath(path, path, ie->cookie); db_pl_disable_bypath(path, path, ie->cookie); } if (ie->mask & IN_ATTRIB) { DPRINTF(E_DBG, L_SCAN, "File permissions changed: %s\n", path); #ifdef HAVE_EUIDACCESS if (euidaccess(path, R_OK) < 0) #else if (access(path, R_OK) < 0) #endif { DPRINTF(E_LOG, L_SCAN, "File access to '%s' failed: %s\n", path, strerror(errno)); db_file_delete_bypath(path);; } else if ((file_type_get(path) == FILE_REGULAR) && (db_file_id_bypath(path) <= 0)) // TODO Playlists { DPRINTF(E_LOG, L_SCAN, "File access to '%s' achieved\n", path); ie->mask |= IN_CLOSE_WRITE; } } if (ie->mask & IN_MOVED_TO) { DPRINTF(E_DBG, L_SCAN, "File moved to: %s\n", path); ret = db_file_enable_bycookie(ie->cookie, path); if (ret <= 0) { /* It's not a known media file, so it's either a new file * or a playlist, known or not. * We want to scan the new file and we want to rescan the * playlist to update playlist items (relative items). */ ie->mask |= IN_CLOSE_WRITE; db_pl_enable_bycookie(ie->cookie, path); } } if (ie->mask & IN_CREATE) { DPRINTF(E_DBG, L_SCAN, "File created: %s\n", path); ret = lstat(path, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Could not lstat() '%s': %s\n", path, strerror(errno)); return; } if (S_ISFIFO(sb.st_mode)) ie->mask |= IN_CLOSE_WRITE; } if (ie->mask & IN_CLOSE_WRITE) { DPRINTF(E_DBG, L_SCAN, "File closed: %s\n", path); ret = lstat(path, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Could not lstat() '%s': %s\n", path, strerror(errno)); return; } if (S_ISLNK(sb.st_mode)) { deref = m_realpath(path); if (!deref) { DPRINTF(E_LOG, L_SCAN, "Could not dereference symlink '%s': %s\n", path, strerror(errno)); return; } file = deref; ret = stat(deref, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Could not stat() '%s': %s\n", file, strerror(errno)); free(deref); return; } if (S_ISDIR(sb.st_mode)) { process_inotify_dir(wi, deref, ie); free(deref); return; } } type = 0; if (check_speciallib(path, "compilations")) type |= F_SCAN_TYPE_COMPILATION; if (check_speciallib(path, "podcasts")) type |= F_SCAN_TYPE_PODCAST; if (check_speciallib(path, "audiobooks")) type |= F_SCAN_TYPE_AUDIOBOOK; if (S_ISREG(sb.st_mode)) process_file(file, sb.st_mtime, sb.st_size, F_SCAN_TYPE_FILE | type, 0); else if (S_ISFIFO(sb.st_mode)) process_file(file, sb.st_mtime, sb.st_size, F_SCAN_TYPE_PIPE | type, 0); if (deref) free(deref); } }
/* Thread: scan */ static void process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie) { struct stat sb; char *deref = NULL; char *file = path; int compilation; int ret; DPRINTF(E_DBG, L_SCAN, "File event: 0x%x, cookie 0x%x, wd %d\n", ie->mask, ie->cookie, wi->wd); if (ie->mask & IN_DELETE) { db_file_delete_bypath(path); db_pl_delete_bypath(path); } if (ie->mask & IN_MOVED_FROM) { db_file_disable_bypath(path, wi->path, ie->cookie); db_pl_disable_bypath(path, wi->path, ie->cookie); } if (ie->mask & IN_MOVED_TO) { ret = db_file_enable_bycookie(ie->cookie, wi->path); if (ret <= 0) { /* It's not a known media file, so it's either a new file * or a playlist, known or not. * We want to scan the new file and we want to rescan the * playlist to update playlist items (relative items). */ ie->mask |= IN_CREATE; db_pl_enable_bycookie(ie->cookie, wi->path); } } if (ie->mask & (IN_MODIFY | IN_CREATE | IN_CLOSE_WRITE)) { ret = lstat(path, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Could not lstat() '%s': %s\n", path, strerror(errno)); return; } if (S_ISLNK(sb.st_mode)) { deref = m_realpath(path); if (!deref) { DPRINTF(E_LOG, L_SCAN, "Could not dereference symlink '%s': %s\n", path, strerror(errno)); return; } file = deref; ret = stat(deref, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Could not stat() '%s': %s\n", file, strerror(errno)); free(deref); return; } if (S_ISDIR(sb.st_mode)) { process_inotify_dir(wi, deref, ie); free(deref); return; } } compilation = check_compilation(path); process_file(file, sb.st_mtime, sb.st_size, compilation, 0); if (deref) free(deref); } }
/* Thread: scan */ static void process_directory(char *path, int flags) { struct stacked_dir *bulkstack; DIR *dirp; struct dirent buf; struct dirent *de; char entry[PATH_MAX]; char *deref; struct stat sb; struct watch_info wi; #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) struct kevent kev; #endif int compilation; int ret; if (flags & F_SCAN_BULK) { /* Save our directory stack so it won't get handled inside * the event loop - not its business, we're in bulk mode here. */ bulkstack = dirstack; dirstack = NULL; /* Run the event loop */ event_base_loop(evbase_scan, EVLOOP_ONCE | EVLOOP_NONBLOCK); /* Restore our directory stack */ dirstack = bulkstack; if (scan_exit) return; } DPRINTF(E_DBG, L_SCAN, "Processing directory %s (flags = 0x%x)\n", path, flags); dirp = opendir(path); if (!dirp) { DPRINTF(E_LOG, L_SCAN, "Could not open directory %s: %s\n", path, strerror(errno)); return; } /* Check for a compilation directory */ compilation = check_compilation(path); for (;;) { ret = readdir_r(dirp, &buf, &de); if (ret != 0) { DPRINTF(E_LOG, L_SCAN, "readdir_r error in %s: %s\n", path, strerror(errno)); break; } if (de == NULL) break; if (buf.d_name[0] == '.') continue; ret = snprintf(entry, sizeof(entry), "%s/%s", path, buf.d_name); if ((ret < 0) || (ret >= sizeof(entry))) { DPRINTF(E_LOG, L_SCAN, "Skipping %s/%s, PATH_MAX exceeded\n", path, buf.d_name); continue; } ret = lstat(entry, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Skipping %s, lstat() failed: %s\n", entry, strerror(errno)); continue; } if (S_ISLNK(sb.st_mode)) { deref = m_realpath(entry); if (!deref) { DPRINTF(E_LOG, L_SCAN, "Skipping %s, could not dereference symlink: %s\n", entry, strerror(errno)); continue; } ret = stat(deref, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Skipping %s, stat() failed: %s\n", deref, strerror(errno)); free(deref); continue; } ret = snprintf(entry, sizeof(entry), "%s", deref); free(deref); if ((ret < 0) || (ret >= sizeof(entry))) { DPRINTF(E_LOG, L_SCAN, "Skipping %s, PATH_MAX exceeded\n", deref); continue; } } if (S_ISREG(sb.st_mode)) process_file(entry, sb.st_mtime, sb.st_size, compilation, flags); else if (S_ISDIR(sb.st_mode)) push_dir(&dirstack, entry); else DPRINTF(E_LOG, L_SCAN, "Skipping %s, not a directory, symlink nor regular file\n", entry); } closedir(dirp); memset(&wi, 0, sizeof(struct watch_info)); #if defined(__linux__) /* Add inotify watch */ wi.wd = inotify_add_watch(inofd, path, IN_CREATE | IN_DELETE | IN_MODIFY | IN_CLOSE_WRITE | IN_MOVE | IN_DELETE | IN_MOVE_SELF); if (wi.wd < 0) { DPRINTF(E_WARN, L_SCAN, "Could not create inotify watch for %s: %s\n", path, strerror(errno)); return; } if (!(flags & F_SCAN_RESCAN)) { wi.cookie = 0; wi.path = path; db_watch_add(&wi); } #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) memset(&kev, 0, sizeof(struct kevent)); wi.wd = open(path, O_RDONLY | O_NONBLOCK); if (wi.wd < 0) { DPRINTF(E_WARN, L_SCAN, "Could not open directory %s for watching: %s\n", path, strerror(errno)); return; } /* Add kevent */ EV_SET(&kev, wi.wd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_DELETE | NOTE_WRITE | NOTE_RENAME, 0, NULL); ret = kevent(inofd, &kev, 1, NULL, 0, NULL); if (ret < 0) { DPRINTF(E_WARN, L_SCAN, "Could not add kevent for %s: %s\n", path, strerror(errno)); close(wi.wd); return; } wi.cookie = 0; wi.path = path; db_watch_add(&wi); #endif }
static void process_directory(char *path, int parent_id, int flags) { DIR *dirp; struct dirent buf; struct dirent *de; char entry[PATH_MAX]; char *deref; struct stat sb; struct watch_info wi; int type; char virtual_path[PATH_MAX]; int dir_id; int ret; DPRINTF(E_DBG, L_SCAN, "Processing directory %s (flags = 0x%x)\n", path, flags); dirp = opendir(path); if (!dirp) { DPRINTF(E_LOG, L_SCAN, "Could not open directory %s: %s\n", path, strerror(errno)); return; } /* Add/update directories table */ ret = create_virtual_path(path, virtual_path, sizeof(virtual_path)); if (ret < 0) return; dir_id = db_directory_addorupdate(virtual_path, 0, parent_id); if (dir_id <= 0) { DPRINTF(E_LOG, L_SCAN, "Insert or update of directory failed '%s'\n", virtual_path); } /* Check if compilation and/or podcast directory */ type = 0; if (check_speciallib(path, "compilations")) type |= F_SCAN_TYPE_COMPILATION; if (check_speciallib(path, "podcasts")) type |= F_SCAN_TYPE_PODCAST; if (check_speciallib(path, "audiobooks")) type |= F_SCAN_TYPE_AUDIOBOOK; for (;;) { if (scan_exit) break; ret = readdir_r(dirp, &buf, &de); if (ret != 0) { DPRINTF(E_LOG, L_SCAN, "readdir_r error in %s: %s\n", path, strerror(errno)); break; } if (de == NULL) break; if (buf.d_name[0] == '.') continue; ret = snprintf(entry, sizeof(entry), "%s/%s", path, buf.d_name); if ((ret < 0) || (ret >= sizeof(entry))) { DPRINTF(E_LOG, L_SCAN, "Skipping %s/%s, PATH_MAX exceeded\n", path, buf.d_name); continue; } ret = lstat(entry, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Skipping %s, lstat() failed: %s\n", entry, strerror(errno)); continue; } if (S_ISLNK(sb.st_mode)) { deref = m_realpath(entry); if (!deref) { DPRINTF(E_LOG, L_SCAN, "Skipping %s, could not dereference symlink: %s\n", entry, strerror(errno)); continue; } ret = stat(deref, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Skipping %s, stat() failed: %s\n", deref, strerror(errno)); free(deref); continue; } ret = snprintf(entry, sizeof(entry), "%s", deref); free(deref); if ((ret < 0) || (ret >= sizeof(entry))) { DPRINTF(E_LOG, L_SCAN, "Skipping %s, PATH_MAX exceeded\n", deref); continue; } } if (S_ISREG(sb.st_mode)) { if (!(flags & F_SCAN_FAST)) process_file(entry, sb.st_mtime, sb.st_size, F_SCAN_TYPE_FILE | type, flags, dir_id); } else if (S_ISFIFO(sb.st_mode)) { if (!(flags & F_SCAN_FAST)) process_file(entry, sb.st_mtime, sb.st_size, F_SCAN_TYPE_PIPE | type, flags, dir_id); } else if (S_ISDIR(sb.st_mode)) push_dir(&dirstack, entry, dir_id); else DPRINTF(E_LOG, L_SCAN, "Skipping %s, not a directory, symlink, pipe nor regular file\n", entry); } closedir(dirp); memset(&wi, 0, sizeof(struct watch_info)); // Add inotify watch (for FreeBSD we limit the flags so only dirs will be // opened, otherwise we will be opening way too many files) #if defined(__linux__) wi.wd = inotify_add_watch(inofd, path, IN_ATTRIB | IN_CREATE | IN_DELETE | IN_CLOSE_WRITE | IN_MOVE | IN_DELETE | IN_MOVE_SELF); #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) wi.wd = inotify_add_watch(inofd, path, IN_CREATE | IN_DELETE | IN_MOVE); #endif if (wi.wd < 0) { DPRINTF(E_WARN, L_SCAN, "Could not create inotify watch for %s: %s\n", path, strerror(errno)); return; } if (!(flags & F_SCAN_MOVED)) { wi.cookie = 0; wi.path = path; db_watch_add(&wi); } }
/* Thread: scan */ static void process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie) { struct stat sb; uint32_t path_hash; char *deref = NULL; char *file = path; char *dir; char dir_vpath[PATH_MAX]; int type; int i; int dir_id; char *ptr; int ret; DPRINTF(E_DBG, L_SCAN, "File event: 0x%x, cookie 0x%x, wd %d\n", ie->mask, ie->cookie, wi->wd); path_hash = djb_hash(path, strlen(path)); if (ie->mask & IN_DELETE) { DPRINTF(E_DBG, L_SCAN, "File deleted: %s\n", path); db_file_delete_bypath(path); db_pl_delete_bypath(path); cache_artwork_delete_by_path(path); } if (ie->mask & IN_MOVED_FROM) { DPRINTF(E_DBG, L_SCAN, "File moved from: %s\n", path); db_file_disable_bypath(path, path, ie->cookie); db_pl_disable_bypath(path, path, ie->cookie); } if (ie->mask & IN_ATTRIB) { DPRINTF(E_DBG, L_SCAN, "File attributes changed: %s\n", path); // Ignore the IN_ATTRIB if we just got an IN_CREATE for (i = 0; i < INCOMINGFILES_BUFFER_SIZE; i++) { if (incomingfiles_buffer[i] == path_hash) return; } #ifdef HAVE_EUIDACCESS if (euidaccess(path, R_OK) < 0) #else if (access(path, R_OK) < 0) #endif { DPRINTF(E_LOG, L_SCAN, "File access to '%s' failed: %s\n", path, strerror(errno)); db_file_delete_bypath(path); cache_artwork_delete_by_path(path); } else if ((file_type_get(path) == FILE_REGULAR) && (db_file_id_bypath(path) <= 0)) // TODO Playlists { DPRINTF(E_LOG, L_SCAN, "File access to '%s' achieved\n", path); ie->mask |= IN_CLOSE_WRITE; } } if (ie->mask & IN_MOVED_TO) { DPRINTF(E_DBG, L_SCAN, "File moved to: %s\n", path); ret = db_file_enable_bycookie(ie->cookie, path); if (ret > 0) { // If file was successfully enabled, update the directory id dir = strdup(path); ptr = strrchr(dir, '/'); dir[(ptr - dir)] = '\0'; ret = create_virtual_path(dir, dir_vpath, sizeof(dir_vpath)); if (ret >= 0) { dir_id = db_directory_id_byvirtualpath(dir_vpath); if (dir_id > 0) { ret = db_file_update_directoryid(path, dir_id); if (ret < 0) DPRINTF(E_LOG, L_SCAN, "Error updating directory id for file: %s\n", path); } } free(dir); } else { /* It's not a known media file, so it's either a new file * or a playlist, known or not. * We want to scan the new file and we want to rescan the * playlist to update playlist items (relative items). */ ie->mask |= IN_CLOSE_WRITE; db_pl_enable_bycookie(ie->cookie, path); } } if (ie->mask & IN_CREATE) { DPRINTF(E_DBG, L_SCAN, "File created: %s\n", path); ret = lstat(path, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Could not lstat() '%s': %s\n", path, strerror(errno)); return; } // Add to the list of files where we ignore IN_ATTRIB until the file is closed again if (S_ISREG(sb.st_mode)) { DPRINTF(E_SPAM, L_SCAN, "Incoming file created '%s' (%d), index %d\n", path, (int)path_hash, incomingfiles_idx); incomingfiles_buffer[incomingfiles_idx] = path_hash; incomingfiles_idx = (incomingfiles_idx + 1) % INCOMINGFILES_BUFFER_SIZE; } else if (S_ISFIFO(sb.st_mode)) ie->mask |= IN_CLOSE_WRITE; } if (ie->mask & IN_CLOSE_WRITE) { DPRINTF(E_DBG, L_SCAN, "File closed: %s\n", path); // File has been closed so remove from the IN_ATTRIB ignore list for (i = 0; i < INCOMINGFILES_BUFFER_SIZE; i++) if (incomingfiles_buffer[i] == path_hash) { DPRINTF(E_SPAM, L_SCAN, "Incoming file closed '%s' (%d), index %d\n", path, (int)path_hash, i); incomingfiles_buffer[i] = 0; } ret = lstat(path, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Could not lstat() '%s': %s\n", path, strerror(errno)); return; } if (S_ISLNK(sb.st_mode)) { deref = m_realpath(path); if (!deref) { DPRINTF(E_LOG, L_SCAN, "Could not dereference symlink '%s': %s\n", path, strerror(errno)); return; } file = deref; ret = stat(deref, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Could not stat() '%s': %s\n", file, strerror(errno)); free(deref); return; } if (S_ISDIR(sb.st_mode)) { process_inotify_dir(wi, deref, ie); free(deref); return; } } type = 0; if (check_speciallib(path, "compilations")) type |= F_SCAN_TYPE_COMPILATION; if (check_speciallib(path, "podcasts")) type |= F_SCAN_TYPE_PODCAST; if (check_speciallib(path, "audiobooks")) type |= F_SCAN_TYPE_AUDIOBOOK; dir_id = get_parent_dir_id(file); if (S_ISREG(sb.st_mode)) { process_file(file, sb.st_mtime, sb.st_size, F_SCAN_TYPE_FILE | type, 0, dir_id); } else if (S_ISFIFO(sb.st_mode)) process_file(file, sb.st_mtime, sb.st_size, F_SCAN_TYPE_PIPE | type, 0, dir_id); if (deref) free(deref); } }