ewmscp  ..
Enumerations | Functions
The inotify_watch command.

The inotify_watch command watches recursively a set of directories for a set of events and creates commands in the format understood by The ewmscp command. More...

Enumerations

enum  watchDirectoryReturnType { watchDirectoryReturnType::vanished, watchDirectoryReturnType::islink, watchDirectoryReturnType::ignore, watchDirectoryReturnType::iswatched }
 special enum for the return values of watchDirectory. More...
 

Functions

watchDirectoryReturnType watchDirectory (int fd, const std::string &name, watchedDirType *parent=nullptr)
 watch a directory. More...
 
int main (int argc, const char *argv[])
 main inotify_watch function. More...
 

Detailed Description

The inotify_watch command watches recursively a set of directories for a set of events and creates commands in the format understood by The ewmscp command.

Bug:
When an anonymous tmp file is created and made visible via open(dirpath, O_WRONLY|O_TMPFILE);inkat(AT_FDCWD, "/proc/self/fd/%fd", AT_FDCWD, path, AT_SYMLINK_FOLLOW) no CLOSE_WRITE event is triggered. Not likely to happen as SL7/Centos7's kernel does not support O_TMPFILE, and also NFS does not support it (apparently)
Bug:
When a file in a watched directory has a hard link from an unwatched directory and is modified using that other link no notification is produced.

Enumeration Type Documentation

◆ watchDirectoryReturnType

special enum for the return values of watchDirectory.

Enumerator
vanished 
islink 
ignore 
iswatched 

Definition at line 481 of file inotify_watch.cpp.

481  {
482  vanished,
483  islink,
484  ignore,
485  iswatched
486 };

Function Documentation

◆ main()

int main ( int  argc,
const char *  argv[] 
)

main inotify_watch function.

This is the main executable.

Definition at line 649 of file inotify_watch.cpp.

649  {
650  std::ios::sync_with_stdio(false);
651  options::parser parser("inotify_watch", "", {});
652  options::positional<options::container<std::string>>directories(10, "directory",
653  "directories to be watched");
654  options::single<std::string> workDir('\0', "workDir",
655  "use DIRECTORY as work dir");
656  options::single<std::regex> ignore('i', "ignore",
657  "regexp for files to be ignored for IN_CLOSE_WRITE");
658  options::single<bool> verbose('v', "verbose",
659  "be verbose", false);
660  options::single<std::string> errorStream('\0', "errors",
661  "file to write errors to");
662 
663  options::container<std::string> extraEvents('e', "extraEvents",
664  "extra events to listen to");
665  maskName::addToRange(extraEvents);
666  options::container<std::string> copyTriggers('c', "copyTrigger",
667  "extra events beside IN_CLOSE_NOWRITE hat trigger a copy");
668  maskName::addToRange(copyTriggers);
669  parser.fParse(argc, argv);
670 
671  logstream::provider errStreamBase(errorStream, std::cerr);
672  errStream = &(errStreamBase.getStream());
673 
675 
676  uint32_t copyTriggerBits = IN_CLOSE_WRITE | maskName::maskFromList(copyTriggers);
677  if (verbose) {
678  std::cerr << "events of type ";
679  maskName::print(std::cerr, copyTriggerBits);
680  std::cerr << " (" << std::hex << copyTriggerBits << std::dec << ") will trigger a copy event\n";
681  }
682  try {
683 
684  if (pidFileName != "") {
685  std::ofstream pidFile(pidFileName);
686  pidFile << getpid() << "\n";
687  throwcall::good0(std::atexit(pidFileRemover), "can't register pidFileRemover");
688  std::signal(SIGTERM, sigTermHandler);
689  }
690 
691  std::signal(SIGPIPE, sigPipeHandler);
692  std::signal(SIGUSR1, sigUsrHandler);
693 
694  auto fd = throwcall::badval(inotify_init(), -1, "inotify_init");
695 
696  if (workDir.fIsSet()) {
697  throwcall::good0(chdir(workDir.c_str()), "can't change work dir to ", workDir);
698  }
699 
700  for (const auto& directory : directories) {
701  watchDirectory(fd, directory);
702  }
703 
704  *errStream << errPrefix << "watching " << watchedDirType::size() << " directories" << std::endl;
705 
706  *errStream << std::fixed;
707  char buffer[4096 * 256] __attribute__ ((aligned(__alignof__(struct inotify_event))));
708  struct pollfd pfd;
709  pfd.fd = fd;
710  pfd.events = POLLIN;
711 
712  commandSender sender;
713 
714  while (true) {
715  if (dumpRequested) {
716  dumpRequested = false;
718  }
719  {
720  {
721  // process single events that took forever to get a partner
722  commandSender::flushDomain flusher(sender);
723  while (auto copy = inotify_event_copy::getStaleSingles()) {
724  auto event = copy->getEvent();
725  std::string path;
726  watchedDirType::get(event->wd())->buildPath(path, event->name());
727 
728  if (event->mask() & IN_MOVED_TO) {
729  auto result = watchDirectory(fd, event->name(), watchedDirType::get(event->wd()));
730 
731  if (result == watchDirectoryReturnType::islink) {
732  sender.sendCommand("ln", copy->timeStamp(), path);
733  } else if (result == watchDirectoryReturnType::ignore) {
734  sender.sendCommand("cp", copy->timeStamp(), path);
735  }
736 
737  if (verbose && result == watchDirectoryReturnType::iswatched) {
738  *errStream << errPrefix << "watching " << watchedDirType::size() << " directories (added subtree)" << std::endl;
739  }
740  } else {
741  auto parent = watchedDirType::get(event->wd());
742  auto meAsDirChild = parent->getChild(event->name());
743 
744  if (meAsDirChild) {
745  delete meAsDirChild;
746  }
747 
748  sender.sendCommand("rm", copy->timeStamp(), path);
749  }
750 
752  }
753  }
754  auto result = poll(&pfd, 1, 1000);
755 
756  if (result == -1) {
757  if (errno == EINTR) {
758  continue;
759  }
760 
761  throwcall::badval(result, -1, "poll failed");
762  }
763 
764  if (result == 0) { // timeout, check continued presence of initial dirs
765  for (const auto& directory : directories) {
766  struct stat statbuf;
767  auto retval = stat(directory.c_str(), &statbuf);
768 
769  if (retval != 0 && errno == ENOENT) {
770  return 0;
771  } else {
772  throwcall::good0(retval, "can't stat ", directory);
773  }
774  }
775  sender.keepalive();
776  continue;
777  }
778  }
779  auto bytes = throwcall::badval(read(fd, buffer, sizeof(buffer)),
780  -1, "read inotification");
781  auto now = clock_type::now();
782 
783  if (verbose) {
784  *errStream << errPrefix << now << " got " << bytes << " bytes of notification" << std::endl;
785  }
786 
787  commandSender::flushDomain flusher(sender);
788 
789  for (auto event = inotify_event_wrapper::fromChar(buffer);
790  event < inotify_event_wrapper::fromChar(buffer + bytes);
791  event = event->next()) {
792  if (verbose) {
793  *errStream << errPrefix << "watch id " << event->wd() << " @ " << (void*)event << " with name " << event->name() << *event << std::endl;
794  }
795 
796  if (event->mask() & IN_IGNORED) {
797  continue;
798  }
799 
800  if (event->mask() & IN_Q_OVERFLOW) {
801  *errStream << errPrefix << "watch queue overflow" << std::endl;
802  continue; // we cannot do more than lopg this
803  }
804 
805  std::string path;
806  auto watchedDir = watchedDirType::get(event->wd());
807 
808  if (watchedDir == nullptr) {
809  *errStream << errPrefix << "unknown watch id " << event->wd() << " @ " << (void*)event << " with name " << event->name() << *event << std::endl;
810  continue;
811  }
812 
813  watchedDir->buildPath(path, event->name());
814 
815  if (event->mask() & (IN_MOVED_TO | IN_MOVED_FROM)) {
816  auto partner = inotify_event_copy::getPartner(event, now);
817 
818  if (partner) { // we have a partner event
819  std::string partnerPath;
820  watchedDirType::get(partner->getEvent()->wd())->buildPath(partnerPath, partner->getEvent()->name());
821  const inotify_event_wrapper* from;
822  const inotify_event_wrapper* to;
823  const std::string* fromPath;
824  const std::string* toPath;
825 
826  if (event->mask() & IN_MOVED_TO) {
827  from = partner->getEvent();
828  to = event;
829  fromPath = &partnerPath;
830  toPath = &path;
831  } else {
832  from = event;
833  to = partner->getEvent();
834  fromPath = &path;
835  toPath = &partnerPath;
836  }
837 
838  auto fromAsDirChild = watchedDirType::get(from->wd())->getChild(from->name());
839 
840  if (fromAsDirChild) {
841  fromAsDirChild->getAdoptedBy(to->name(), watchedDirType::get(to->wd()));
842  }
843 
844  sender.sendCommand("mv", now, *fromPath, *toPath);
846  }
847  }
848 
849  if (event->mask() & copyTriggerBits) {
850  if (!(ignore.fIsSet() && regex_match(path, ignore))) {
851  sender.sendCommand("cp", now, path);
852  }
853  }
854 
855  if (event->mask() & IN_CREATE) { // watch new directories also
856  auto result = watchDirectory(fd, event->name(), watchedDir);
857 
858  if (result == watchDirectoryReturnType::islink) {
859  sender.sendCommand("ln", now, path);
860  }
861 
862  if (verbose && result == watchDirectoryReturnType::iswatched) {
863  *errStream << errPrefix << "watching " << watchedDirType::size() << " directories (added subtree)" << std::endl;
864  }
865  }
866 
867  if (event->mask() & IN_DELETE_SELF) { // a watched directory is deleted
868  watchedDir->invalidateId();
869  delete watchedDir;
870 
871  if (verbose) {
872  *errStream << errPrefix << "watching " << watchedDirType::size() << " directories (removed subtree)" << std::endl;
873  }
874  }
875 
876  if (event->mask() & IN_DELETE) {
877  if (!(ignore.fIsSet() && regex_match(path, ignore))) {
878  sender.sendCommand("rm", now, path);
879  }
880  }
881  }
882  // make copies of all leftover half move events befor re-using buffer
884 
885  if (watchedDirType::empty()) {
886  throwcall::good0(close(fd), "can't close inotify fd");
887 
888  *errStream << errPrefix << "no more stuff to watch, finishing...\n";
889 
890  return EXIT_SUCCESS;
891  }
892  }
893  } catch (const std::exception& e) {
894  *errStream << logstream::level::crit << errPrefix << " ended badly: " << e.what() << std::endl;
895  return EXIT_FAILURE;
896  }
897 
898  return EXIT_SUCCESS;
899 }

References maskName::addToRange(), throwcall::badval(), watchedDirType::buildPath(), logstream::crit, inotify_event_copy::doCopyLeftovers(), watchedDirType::dump(), dumpRequested(), watchedDirType::empty(), errPrefix, errStream(), options::base::fIsSet(), inotify_event_copy::forget(), inotify_event_wrapper::fromChar(), watchedDirType::get(), watchedDirType::getAdoptedBy(), watchedDirType::getChild(), inotify_event_copy::getPartner(), inotify_event_copy::getStaleSingles(), logstream::provider::getStream(), throwcall::good0(), ignore, islink, iswatched, commandSender::keepalive(), maskName::maskFromList(), inotify_event_wrapper::name(), pidFileName, pidFileRemover(), maskName::print(), commandSender::sendCommand(), watchedDirType::setExtraEventMask(), sigPipeHandler(), sigTermHandler(), sigUsrHandler(), watchedDirType::size(), verbose, watchDirectory(), inotify_event_wrapper::wd(), and workDir.

Here is the call graph for this function:

◆ watchDirectory()

watchDirectoryReturnType watchDirectory ( int  fd,
const std::string &  name,
watchedDirType parent = nullptr 
)

watch a directory.

The directory indicated by name will be watched, together with all the subdirs recursively. [in] fd is the fd of the inotify object, [in] name is the basename (if parent != null) of full path (no parent) of the directory. [in] parent pointer to the parent directory, may be nullpr for initial command line args

Definition at line 494 of file inotify_watch.cpp.

496  {
497  std::string path;
498 
499  if (parent) {
500  parent->buildPath(path, name);
501  } else {
502  path = name;
503  }
504 
505  {
506  struct stat statbuf;
507  auto retval = lstat(path.c_str(), &statbuf);
508 
509  if (retval && (errno == ENOENT || errno == ENOTDIR)) {
510  return watchDirectoryReturnType::vanished; // happens for very short lived files
511  }
512 
513  throwcall::good0(retval, "can't lstat ", path);
514 
515  if (!S_ISDIR(statbuf.st_mode)) {
516  return S_ISLNK(statbuf.st_mode) ? watchDirectoryReturnType::islink : watchDirectoryReturnType::ignore;
517  }
518  }
519 
520  auto watchedDir = new watchedDirType(name, fd, parent);
521 
522  if (watchedDir->getId() == -1) { // vanmished or already watched
523  delete watchedDir;
525  }
526 
527  // with the subdir list we have only one open dir at a time
528  std::list<std::string> subdirs;
529  {
530  auto result = opendir(path.c_str());
531 
532  if (result == nullptr && (errno == ENOENT || errno == ENOTDIR)) {
533  delete watchedDir;
534  return watchDirectoryReturnType::vanished; // dir vanished before we could check the content
535  }
536 
537  scoped::Dir dir(path, result);
538 
539  while (auto entry = readdir(dir)) {
540  if (entry->d_name[entry->d_name[0] != '.' ? 0 : entry->d_name[1] != '.' ? 1 : 2] == '\0') {
541  continue; // skip . .. and empty strings
542  }
543  if (entry->d_type == DT_DIR) {
544  subdirs.emplace_back(entry->d_name);
545  }
546  }
547  }
548 
549  for (auto& subname : subdirs) {
550  watchDirectory(fd, subname, watchedDir);
551  }
552 
554 }

References watchedDirType::buildPath(), throwcall::good0(), ignore, islink, iswatched, and vanished.

Referenced by main().

Here is the call graph for this function:
Here is the caller graph for this function:
options::parser
class that contains the parser, i.e. does that option handling
Definition: Options.h:363
watchedDirType
represents a watched directory inside a directory tree.
Definition: inotify_watch.cpp:285
inotify_event_copy::doCopyLeftovers
static void doCopyLeftovers()
Definition: inotify_watch.cpp:226
verbose
options::single< bool > verbose
options::single< std::string >
workDir
options::single< std::string > workDir
inotify_event_copy::getPartner
static inotify_event_copy * getPartner(const inotify_event_wrapper *event, clock_type::time_point then)
get partner of a half rename event.
Definition: inotify_watch.cpp:267
options::single< bool >
class specialisation for options of type bool
Definition: Options.h:595
throwcall::badval
T badval(T call, t badvalue, const Args &... args)
template function to wrap system calls that return a special bad value on failure
Definition: throwcall.h:54
watchedDirType::buildPath
void buildPath(std::string &path)
fill path with the fill path to this directory
Definition: inotify_watch.cpp:333
inotify_event_wrapper::name
const char * name() const
Definition: inotify_watch.cpp:149
inotify_event_wrapper::wd
int wd() const
Definition: inotify_watch.cpp:137
watchedDirType::size
static size_t size()
return number of watched directories
Definition: inotify_watch.cpp:364
options::base::fIsSet
virtual bool fIsSet() const
check if this option was set, regardless of from command line or config file
Definition: Options.h:263
options::single< std::regex >
Definition: OptionsRegex.h:8
inotify_event_wrapper
wrapper for inotify_event with nicer interface.
Definition: inotify_watch.cpp:132
dumpRequested
static std::atomic< bool > dumpRequested(false)
errStream
std::ostream * errStream(nullptr)
watchDirectoryReturnType::islink
@ islink
inotify_event_copy::forget
static void forget(inotify_event_copy *what)
deletes a copy.
Definition: inotify_watch.cpp:254
watchedDirType::getChild
watchedDirType * getChild(const std::string &aName)
Definition: inotify_watch.cpp:454
watchDirectoryReturnType::iswatched
@ iswatched
errPrefix
options::single< std::string > errPrefix('\0', "errPrefix", "prefix for error messages")
logstream::provider
Definition: syslogstream.h:100
watchedDirType::getAdoptedBy
void getAdoptedBy(const std::string &newName, watchedDirType *newParent)
move to a new place in the directory tree.
Definition: inotify_watch.cpp:467
commandSender::sendCommand
void sendCommand(const char *command, std::chrono::system_clock::time_point when, const std::string &path)
Definition: inotify_watch.cpp:602
sigUsrHandler
static void sigUsrHandler(int)
Definition: inotify_watch.cpp:582
options::container< std::string >
pidFileRemover
static void pidFileRemover()
Definition: inotify_watch.cpp:570
maskName::print
static void print(std::ostream &out, uint32_t mask)
Definition: inotify_watch.cpp:91
commandSender::flushDomain
class to flush output on scope basis.
Definition: inotify_watch.cpp:625
logstream::level::crit
@ crit
commandSender::keepalive
void keepalive()
Definition: inotify_watch.cpp:616
pidFileName
static options::single< std::string > pidFileName('p', "pidFile", "name of PID file")
watchDirectory
watchDirectoryReturnType watchDirectory(int fd, const std::string &name, watchedDirType *parent=nullptr)
watch a directory.
Definition: inotify_watch.cpp:494
maskName::addToRange
static void addToRange(options::container< std::string > &opt)
Definition: inotify_watch.cpp:115
maskName::maskFromList
static uint32_t maskFromList(const options::container< std::string > &list)
Definition: inotify_watch.cpp:104
watchedDirType::setExtraEventMask
static void setExtraEventMask(int extraMask)
Definition: inotify_watch.cpp:368
watchedDirType::empty
static bool empty()
test if any directories are watched
Definition: inotify_watch.cpp:360
inotify_event_copy::getStaleSingles
static inotify_event_copy * getStaleSingles()
get the next old inotify_event.
Definition: inotify_watch.cpp:237
watchDirectoryReturnType::vanished
@ vanished
scoped::Dir
Definition: scoped.h:71
options::positional
Definition: Options.h:876
watchedDirType::dump
static void dump(std::ostream &out)
Definition: inotify_watch.cpp:321
sigPipeHandler
static void sigPipeHandler(int)
Definition: inotify_watch.cpp:578
throwcall::good0
void good0(T call, const Args &... args)
template function to wrap system calls that return 0 on success
Definition: throwcall.h:40
watchedDirType::get
static watchedDirType * get(int aId)
get a watcheDir by the inotify id
Definition: inotify_watch.cpp:356
watchDirectoryReturnType::ignore
@ ignore
inotify_event_wrapper::fromChar
static const inotify_event_wrapper * fromChar(char *pointer)
Definition: inotify_watch.cpp:158
sigTermHandler
static void sigTermHandler(int)
Definition: inotify_watch.cpp:575
commandSender
class used to send messages via stdout.
Definition: inotify_watch.cpp:591