/** * dirnotify - a command-line utility for directory notifications. * * Fredrik Fornwall (fredrikfornwall@gmail.com) */ #include #include #include #include #include #include #include #include #include #include #include #include using std::cout; using std::endl; using std::string; struct stat scratchStat; struct Settings { unsigned long eventMask; bool justDie; bool timestamp; bool silent; void initialize() { eventMask = DN_MULTISHOT; justDie = false; timestamp = false; silent = false; }; } settings; std::ostream& timestamp(std::ostream& out) { if (settings.timestamp) { static char buffer[100]; std::time_t time = std::time(NULL); std::tm* localTime = std::localtime(&time); std::strftime(buffer, sizeof(buffer), "%H:%M:%S", localTime); out << buffer << " "; } return out; } struct File { File() { } File(struct stat fileStat, std::string name, std::string fullPath) : fileStat(fileStat), name(name), fullPath(fullPath) { } struct stat fileStat; std::string name; std::string fullPath; void update() { bool resized = false; std::stringstream ss; if ((scratchStat.st_atim.tv_sec != fileStat.st_atim.tv_sec) or (scratchStat.st_atim.tv_nsec != fileStat.st_atim.tv_nsec)) { ss << " read"; } if (scratchStat.st_mode != fileStat.st_mode) { ss << " mode[" << std::setbase(8) << std::setw(6) << std::setfill('0') << fileStat.st_mode << "=>" << scratchStat.st_mode << "]"; } if (scratchStat.st_size != fileStat.st_size) { resized = true; ss << " resized[" << fileStat.st_size << "=>" << scratchStat.st_size << "]"; } if (scratchStat.st_uid != fileStat.st_uid) { ss << " owner[" << fileStat.st_uid << "=>" << scratchStat.st_uid << "]"; } if (scratchStat.st_mtime != fileStat.st_mtime) { if (not resized) { ss << " changedcontent"; } } if (ss.str().size() != 0U) { if (settings.silent) { cout << name << endl; } else { timestamp(cout) << name << ss.str() << endl; } } fileStat = scratchStat; } }; std::map fileMap; static void signalHandler(int sig, siginfo_t* si, void* data) { } void dieExplaining() { std::cout << "Usage: dirnotify [-e event_mask] [-h] [-r] [-t] [directory]" << std::endl << " -e event_mask: Event mask of possible values \"acdrnm\" - see the man page" << std::endl << " -h: This help" << std::endl << " -r: Return directly after first event" << std::endl << " -s: Be silent and just output the file name being the source of the event" << std::endl << " -t: Prefix each output with a timestamp" << std::endl << "The directory defaults to the current directory" << std::endl; std::exit(0); } void checkSystemCall(int returnCode) { if (returnCode == -1) { std::cerr << strerror(errno) << std::endl; std::exit(1); } } int main(int argc, char* argv[]) { settings.initialize(); while (true) { char c = getopt(argc, argv, "e:hrst"); if (c == -1) break; switch (c) { case 'e': std::cout << "EVENTS: " << optarg << std::endl; for (unsigned int i = 0; i < std::strlen(optarg); i++) { switch (optarg[i]) { case 'a': settings.eventMask |= DN_ATTRIB; break; case 'c': settings.eventMask |= DN_CREATE; break; case 'd': settings.eventMask |= DN_DELETE; break; case 'r': settings.eventMask |= DN_ACCESS; break; case 'n': settings.eventMask |= DN_RENAME; break; case 'm': settings.eventMask |= DN_MODIFY; break; default: std::cerr << "Unknown event attribute '" << optarg[i] << "'" << std::endl; return 1; } } break; case 'h': dieExplaining(); break; case 'r': settings.justDie = true; break; case 's': settings.silent = true; break; case 't': settings.timestamp = true; break; }; } if (settings.eventMask == DN_MULTISHOT) { settings.eventMask |= DN_ACCESS | DN_ATTRIB | DN_CREATE | DN_DELETE | DN_MODIFY | DN_RENAME; } std::string directory; if (optind == argc) { directory = "."; } else if (optind == argc - 1) { directory = argv[optind]; } else { dieExplaining(); } struct sigaction act; act.sa_sigaction = signalHandler; act.sa_flags = SA_RESTART; checkSystemCall(sigemptyset( &act.sa_mask )); checkSystemCall(sigaction( SIGRTMIN + 1, &act, NULL )); int fd = open( directory.c_str(), O_DIRECTORY | O_RDONLY ); checkSystemCall(fd); struct dirent* directoryEntry; DIR* dir = opendir(directory.c_str()); checkSystemCall((dir == NULL) ? -1 : 0); while ((directoryEntry = readdir(dir)) != NULL) { struct stat buf; std::string fileName = std::string(directoryEntry->d_name); if (fileName == ".") continue; std::string fullPath = directory + "/" + fileName; checkSystemCall(stat(fullPath.c_str(), &buf)); fileMap[directoryEntry->d_fileno] = File(buf, fileName, fullPath); } (void) closedir(dir); fcntl(fd, F_SETSIG, SIGRTMIN + 1); fcntl(fd, F_NOTIFY, settings.eventMask); if (settings.justDie) { pause(); return 0; } while (true) { pause(); struct dirent* directoryEntry; DIR* dir = opendir(directory.c_str()); if (dir == NULL) { std::cerr << strerror(errno) << std::endl; return 1; } while ((directoryEntry = readdir(dir)) != NULL) { struct stat buf; std::string fileName = std::string(directoryEntry->d_name); if (fileName == ".") continue; std::string fullPath = directory + "/" + fileName; checkSystemCall(stat(fullPath.c_str(), &buf)); std::map::iterator it = fileMap.find(directoryEntry->d_fileno); if (it == fileMap.end()) { fileMap[directoryEntry->d_fileno] = File(buf, fileName, fullPath); timestamp(cout) << directoryEntry->d_name << (settings.silent ? "" : " created") << endl; } else { if (it->second.name != directoryEntry->d_name) { timestamp(cout) << it->second.name; if (not settings.silent) { cout << " renamed[" << directoryEntry->d_name << "]"; } cout << endl; it->second.name = directoryEntry->d_name; it->second.fullPath = directory + "/" + it->second.name; } } } (void) closedir(dir); for (std::map::iterator it = fileMap.begin(); it != fileMap.end(); it++) { File f = it->second; if (stat(f.fullPath.c_str(), &scratchStat) == -1) { if (errno == ENOENT) { timestamp(cout) << f.name << (settings.silent ? "" : " deleted") << endl; fileMap.erase(it); continue; } } else { it->second.update(); } } } }