libzypp  17.37.5
forkspawnengine.cc
Go to the documentation of this file.
2 
3 #include <sstream>
4 #include <zypp/base/LogControl.h>
5 #include <zypp/base/Gettext.h>
6 #include <zypp/base/IOTools.h>
8 #include <zypp-core/zyppng/core/String>
9 #include <zypp-core/zyppng/base/EventDispatcher>
10 #include <zypp-core/zyppng/base/Timer>
14 
15 #include <cstdint>
16 #include <iostream>
17 #include <signal.h>
18 #include <errno.h>
19 #include <unistd.h>
20 #include <sys/wait.h>
21 #include <fcntl.h>
22 #include <pty.h> // openpty
23 #include <stdlib.h> // setenv
24 #include <sys/prctl.h> // prctl(), PR_SET_PDEATHSIG
25 
26 #include <sys/syscall.h>
27 #ifdef SYS_pidfd_open
28 #include <poll.h>
29 #endif
30 
31 #undef ZYPP_BASE_LOGGER_LOGGROUP
32 #define ZYPP_BASE_LOGGER_LOGGROUP "zypp::exec"
33 
34 
36 {
38  // we got destructed while the external process is still alive
39  // make sure the zombie is cleaned up once it exits
41  }
42 }
43 
45 {
46  if ( _pid < 0 ) return false;
47 
48  int status = 0;
49  int p = zyppng::eintrSafeCall( ::waitpid, _pid, &status, wait ? 0 : WNOHANG );
50  switch ( p )
51  {
52  case -1:
53  ERR << "waitpid( " << _pid << ") returned error '" << strerror(errno) << "'" << std::endl;
54  return false;
55  break;
56  case 0:
57  return true; // still running
58  break;
59  }
60 
61  // Here: completed...
62  _exitStatus = checkStatus( status );
63  _pid = -1;
64  return false;
65 }
66 
67 bool zyppng::AbstractDirectSpawnEngine::waitForExit( const std::optional<uint64_t> &timeout )
68 {
69  if ( _pid < 0 ) return true;
70 
71  // no timeout, wait forever
72  if ( !timeout.has_value () )
73  return !isRunning( true );
74 
75  // busy loop polling in case pidfd is not available or fails, only called if we have a valid timout
76  const auto &fallbackPoll = [&]( uint64_t timeout ){
77  const auto start = Timer::now();
78  do {
79  if ( !isRunning(false) )
80  return true;
81  // give up the CPU, so we do not use 100% just to poll
82  std::this_thread::sleep_for( std::chrono::milliseconds(1) );
83  } while( Timer::elapsedSince ( start ) < timeout );
84 
85  return !isRunning ( false );
86  };
87 
88 #ifdef SYS_pidfd_open
89  // we have pidfd support, but there is not yet a wrapper in glibc
90  const auto &zypp_pidfd_open = [](pid_t pid, unsigned int flags) -> int {
91  return syscall( SYS_pidfd_open, pid, flags );
92  };
93 
94  zypp::AutoFD pidFd = zyppng::eintrSafeCall( zypp_pidfd_open, _pid, 0 );
95  if ( pidFd == -1 ) {
96  // fallback to manual polling
97  ERR << "pidfd_open failed, falling back to polling waidpid" << std::endl;
98  return fallbackPoll( *timeout );
99  }
100 
101  struct pollfd pollfd;
102  pollfd.fd = pidFd;
103  pollfd.events = POLLIN;
104 
105  // timeout always has a value set, we established that above
106  uint64_t tRemaining = *timeout;
107 
108  const auto start = Timer::now();
109  do {
110  // posix using int as timeout, could in theory overflow so protect against it
111  int posixTimeout = tRemaining > INT_MAX ? INT_MAX : static_cast<int>(tRemaining);
112 
113  int ready = poll(&pollfd, 1, posixTimeout );
114  tRemaining = *timeout - std::min<uint64_t>( Timer::elapsedSince( start ), *timeout );
115 
116  if ( ready == -1 && errno != EINTR ) {
117  ERR << "Polling the pidfd failed with error: " << zypp::Errno() << std::endl;
118  if ( tRemaining > 0 ) {
119  ERR << "Falling back to manual polling for the remaining timeout." << std::endl;
120  return fallbackPoll( tRemaining );
121  }
122  break;
123  } else if ( pollfd.revents & POLLIN ) {
124  break;
125  }
126  } while( tRemaining > 0 );
127 
128  // set exit status
129  return !isRunning ( false );
130 #else
131  // we do not have pidfd support, need to busyloop on waitpid until timeout is over
132  return fallbackPoll( *timeout );
133 #endif
134 }
135 
137 {
138  // we might have gotten other FDs to reuse, lets map them to STDERR_FILENO++
139  // BUT we need to make sure the fds are not already in the range we need to map them to
140  // so we first go over a list and collect those that are safe or move those that are not
141  int lastFdToKeep = STDERR_FILENO + _mapFds.size();
142  int nextBackupFd = lastFdToKeep + 1; //this we will use to count the fds upwards
143  std::vector<int> safeFds;
144  for ( auto fd : _mapFds ) {
145  // If the fds are larger than the last one we will map to, it is safe.
146  if ( fd > lastFdToKeep ) {
147  safeFds.push_back( fd );
148  } else {
149  // we need to map the fd after the set of those we want to keep, but also make sure
150  // that we do not close one of those we have already moved or might move
151  while (true) {
152 
153  int backupTo = nextBackupFd;
154  nextBackupFd++;
155  const bool isSafe1 = std::find( _mapFds.begin(), _mapFds.end(), backupTo ) == _mapFds.end();
156  const bool isSafe2 = std::find( safeFds.begin(), safeFds.end(), backupTo ) == safeFds.end();
157  if ( isSafe1 && isSafe2 && ( controlFd == -1 || backupTo != controlFd) ) {
158  dup2( fd, backupTo );
159  safeFds.push_back( backupTo );
160  break;
161  }
162  }
163  }
164  }
165 
166  // now we have a list of safe fds we need to map to the fd we want them to end up
167  int nextFd = STDERR_FILENO;
168  for ( auto fd : safeFds ) {
169  nextFd++;
170  dup2( fd, nextFd );
171  }
172 
173  const auto &canCloseFd = [&]( int fd ){
174  // controlFD has O_CLOEXEC set so it will be cleaned up :)
175  if ( controlFd != -1 && controlFd == fd )
176  return false;
177  // make sure we don't close fd's still need
178  if ( fd <= lastFdToKeep )
179  return false;
180  return true;
181  };
182 
183  const auto maxFds = ( ::getdtablesize() - 1 );
184  //If the rlimits are too high we need to use a different approach
185  // in detecting how many fds we need to close, or otherwise we are too slow (bsc#1191324)
186  if ( maxFds > 1024 && zypp::PathInfo( "/proc/self/fd" ).isExist() ) {
187 
188  std::vector<int> fdsToClose;
189  fdsToClose.reserve (256);
190 
191  zypp::filesystem::dirForEachExt( "/proc/self/fd", [&]( const zypp::Pathname &p, const zypp::filesystem::DirEntry &entry ){
192  if ( entry.type != zypp::filesystem::FT_LINK)
193  return true;
194 
195  const auto &fdVal = zyppng::str::safe_strtonum<int>( entry.name );
196  if ( !fdVal || !canCloseFd(*fdVal) )
197  return true;
198 
199  // we can not call close() directly here because zypp::filesystem::dirForEachExt actually has a fd open on
200  // /proc/self/fd that we would close as well. So we just remember which fd's we WOULD close and then do it
201  // after iterating
202  fdsToClose.push_back (*fdVal);
203  return true;
204  });
205  for ( int cFd : fdsToClose )
206  ::close( cFd );
207  } else {
208  // close all filedescriptors above the last we want to keep
209  for ( int i = maxFds; i > lastFdToKeep; --i ) {
210  if ( !canCloseFd(i) ) continue;
211  ::close( i );
212  }
213  }
214 }
215 
217 {
218  // set all signal handers to their default
219  struct sigaction act;
220  memset (&act, 0, sizeof (struct sigaction));
221  act.sa_handler = SIG_DFL;
222  for ( int i = 1; i < NSIG; i++ ) {
223  // this might return -1 and set errno for unknown signals, but
224  // thats fine for us.
225  sigaction(i, &act, NULL);
226  }
227 
228  // clear the sigmask
229  sigset_t sigMask;
230  sigemptyset ( &sigMask );
231  pthread_sigmask ( SIG_SETMASK, &sigMask, nullptr );
232 }
233 
234 bool zyppng::ForkSpawnEngine::start( const char * const *argv, int stdin_fd, int stdout_fd, int stderr_fd )
235 {
236  _pid = -1;
237  _exitStatus = 0;
238  _execError.clear();
239  _executedCommand.clear();
240  _args.clear();
241 
242  if ( !argv || !argv[0] ) {
243  _execError = _("Invalid spawn arguments given.");
244  _exitStatus = 128;
245  return false;
246  }
247 
248  const char * chdirTo = nullptr;
249 
250  if ( _chroot == "/" ) {
251  // If _chroot is '/' do not chroot, but chdir to '/'
252  // unless arglist defines another dir.
253  chdirTo = "/";
254  _chroot = zypp::Pathname();
255  }
256 
257  if ( !_workingDirectory.empty() )
258  chdirTo = _workingDirectory.c_str();
259 
260  // do not remove the single quotes around every argument, copy&paste of
261  // command to shell will not work otherwise!
262  {
263  _args.clear();
264  std::stringstream cmdstr;
265  for (int i = 0; argv[i]; i++) {
266  if ( i != 0 ) cmdstr << ' ';
267  cmdstr << '\'';
268  cmdstr << argv[i];
269  cmdstr << '\'';
270  _args.push_back( argv[i] );
271  }
272  _executedCommand = cmdstr.str();
273  }
274  DBG << "Executing" << ( _useDefaultLocale?"[C] ":" ") << _executedCommand << std::endl;
275 
276  // we use a control pipe to figure out if the exec actually worked,
277  // this is the approach:
278  // - create a pipe before forking
279  // - block on the read end of the pipe in the parent process
280  // - in the child process we write a error tag + errno into the pipe if we encounter any error and exit
281  // - If child setup works out, the pipe is auto closed by exec() and the parent process knows from just receiving EOF
282  // that starting the child was successful, otherwise the blocking read in the parent will return with actual data read from the fd
283  // which will contain the error description
284 
285  enum class ChildErrType : int8_t {
286  NO_ERR,
287  CHROOT_FAILED,
288  CHDIR_FAILED,
289  EXEC_FAILED
290  };
291 
292  struct ChildErr {
293  int childErrno = 0;
294  ChildErrType type = ChildErrType::NO_ERR;
295  };
296 
297  auto controlPipe = Pipe::create( O_CLOEXEC );
298  if ( !controlPipe ) {
299  _execError = _("Unable to create control pipe.");
300  _exitStatus = 128;
301  return false;
302  }
303 
304  pid_t ppid_before_fork = ::getpid();
305 
306  // Create module process
307  if ( ( _pid = fork() ) == 0 )
308  {
309 
310  // child process
311  resetSignals();
312  controlPipe->unrefRead();
313 
314  const auto &writeErrAndExit = [&]( int errCode, ChildErrType type ){
315  ChildErr buf {
316  errno,
317  type
318  };
319 
320  zypp::io::writeAll( controlPipe->writeFd, &buf, sizeof(ChildErr) );
321  _exit ( errCode );
322  };
323 
325  // Don't write to the logfile after fork!
327  if ( _use_pty )
328  {
329  setsid();
330  dup2 ( stdout_fd, 1); // set new stdout
331  dup2 ( stdin_fd , 0); // set new stdin
332 
333  // We currently have no controlling terminal (due to setsid).
334  // The first open call will also set the new ctty (due to historical
335  // unix guru knowledge ;-) )
336 
337  char name[512];
338  ttyname_r( stdout_fd , name, sizeof(name) );
339  ::close(open(name, O_RDONLY));
340  }
341  else
342  {
343  if ( _switchPgid )
344  setpgid( 0, 0);
345  if ( stdin_fd != -1 )
346  dup2 ( stdin_fd, 0); // set new stdin
347  if ( stdout_fd != -1 )
348  dup2 ( stdout_fd, 1); // set new stdout
349  }
350 
351  // Handle stderr
352  if ( stderr_fd != -1 )
353  dup2 ( stderr_fd, 2); // set new stderr
354 
355  for ( Environment::const_iterator it = _environment.begin(); it != _environment.end(); ++it ) {
356  setenv( it->first.c_str(), it->second.c_str(), 1 );
357  }
358 
359  if( _useDefaultLocale )
360  setenv("LC_ALL","C",1);
361 
362  if( !_chroot.empty() )
363  {
364  if( ::chroot(_chroot.c_str()) == -1)
365  {
366  _execError = zypp::str::form( _("Can't chroot to '%s' (%s)."), _chroot.c_str(), strerror(errno).c_str() );
367  std::cerr << _execError << std::endl; // After fork log on stderr too
368  writeErrAndExit( 128, ChildErrType::CHROOT_FAILED ); // No sense in returning! I am forked away!!
369  }
370  if ( ! chdirTo )
371  chdirTo = "/";
372  }
373 
374  if ( chdirTo && chdir( chdirTo ) == -1 )
375  {
376  _execError = _chroot.empty() ? zypp::str::form( _("Can't chdir to '%s' (%s)."), chdirTo, strerror(errno).c_str() )
377  : zypp::str::form( _("Can't chdir to '%s' inside chroot '%s' (%s)."), chdirTo, _chroot.c_str(), strerror(errno).c_str() );
378 
379  std::cerr << _execError << std::endl;// After fork log on stderr too
380  writeErrAndExit( 128, ChildErrType::CHDIR_FAILED ); // No sense in returning! I am forked away!!
381  }
382 
383  // map the extra fds the user might have set
384  mapExtraFds( controlPipe->writeFd );
385 
386  if ( _dieWithParent ) {
387  // process dies with us
388  int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
389  if (r == -1) {
390  //ignore if it did not work, worst case the process lives on after the parent dies
391  std::cerr << "Failed to set PR_SET_PDEATHSIG" << std::endl;// After fork log on stderr too
392  }
393 
394  // test in case the original parent exited just
395  // before the prctl() call
396  pid_t ppidNow = getppid();
397  if (ppidNow != ppid_before_fork) {
398  // no sense to write to control pipe, parent is gone
399  std::cerr << "PPID changed from "<<ppid_before_fork<<" to "<< ppidNow << std::endl;// After fork log on stderr too
400  _exit(128);
401  }
402  }
403 
404  execvp( argv[0], const_cast<char *const *>( argv ) );
405  // don't want to get here
406  _execError = zypp::str::form( _("Can't exec '%s' (%s)."), _args[0].c_str(), strerror(errno).c_str() );
407  std::cerr << _execError << std::endl;// After fork log on stderr too
408  writeErrAndExit( 129, ChildErrType::EXEC_FAILED ); // No sense in returning! I am forked away!!
410  }
411  else if ( _pid == -1 ) // Fork failed, close everything.
412  {
413  _execError = zypp::str::form( _("Can't fork (%s)."), strerror(errno).c_str() );
414  _exitStatus = 127;
415  ERR << _execError << std::endl;
416  return false;
417  }
418  else {
419 
420  // parent process, fork worked lets wait for the exec to happen
421  controlPipe->unrefWrite();
422 
423  ChildErr buf;
424  const auto res = zypp::io::readAll( controlPipe->readFd, &buf, sizeof(ChildErr) );
425  if ( res == zypp::io::ReadAllResult::Eof ) {
426  // success!!!!
427  DBG << "pid " << _pid << " launched" << std::endl;
428  return true;
429  } else if ( res == zypp::io::ReadAllResult::Ok ) {
430  switch( buf.type ) {
431  case ChildErrType::CHDIR_FAILED:
432  _execError = zypp::str::form( _("Can't exec '%s', chdir failed (%s)."), _args[0].c_str(), zypp::str::strerror(buf.childErrno).c_str() );
433  break;
434  case ChildErrType::CHROOT_FAILED:
435  _execError = zypp::str::form( _("Can't exec '%s', chroot failed (%s)."), _args[0].c_str(), zypp::str::strerror(buf.childErrno).c_str() );
436  break;
437  case ChildErrType::EXEC_FAILED:
438  _execError = zypp::str::form( _("Can't exec '%s', exec failed (%s)."), _args[0].c_str(), zypp::str::strerror(buf.childErrno).c_str() );
439  break;
440  // all other cases need to be some sort of error, because we only get data if the exec fails
441  default:
442  _execError = zypp::str::form( _("Can't exec '%s', unexpected error."), _args[0].c_str() );
443  break;
444  }
445  ERR << "pid " << _pid << " launch failed: " << _execError << std::endl;
446 
447  // reap child and collect exit code
448  isRunning( true );
449  return false;
450  } else {
451  //reading from the fd failed, this should actually never happen
452  ERR << "Reading from the control pipe failed. " << errno << ". This is not supposed to happen ever." << std::endl;
453  return isRunning();
454  }
455  }
456  return true;
457 }
458 
460 {
461  return _use_pty;
462 }
463 
465 {
466  _use_pty = set;
467 }
468 
469 
470 #if ZYPP_HAS_GLIBSPAWNENGINE
471 
472 struct GLibForkData {
473  zyppng::GlibSpawnEngine *that = nullptr;
474  pid_t pidParent = -1;
475 };
476 
477 bool zyppng::GlibSpawnEngine::start( const char * const *argv, int stdin_fd, int stdout_fd, int stderr_fd )
478 {
479  _pid = -1;
480  _exitStatus = 0;
481  _execError.clear();
482  _executedCommand.clear();
483  _args.clear();
484 
485  if ( !argv || !argv[0] ) {
486  _execError = _("Invalid spawn arguments given.");
487  _exitStatus = 128;
488  return false;
489  }
490 
491  const char * chdirTo = nullptr;
492 
493  if ( _chroot == "/" ) {
494  // If _chroot is '/' do not chroot, but chdir to '/'
495  // unless arglist defines another dir.
496  chdirTo = "/";
497  _chroot = zypp::Pathname();
498  }
499 
500  if ( !_workingDirectory.empty() )
501  chdirTo = _workingDirectory.c_str();
502 
503  // do not remove the single quotes around every argument, copy&paste of
504  // command to shell will not work otherwise!
505  {
506  _args.clear();
507  std::stringstream cmdstr;
508  for (int i = 0; argv[i]; i++) {
509  if ( i != 0 ) cmdstr << ' ';
510  cmdstr << '\'';
511  cmdstr << argv[i];
512  cmdstr << '\'';
513  _args.push_back( argv[i] );
514  }
515  _executedCommand = cmdstr.str();
516  }
517  DBG << "Executing" << ( _useDefaultLocale?"[C] ":" ") << _executedCommand << std::endl;
518 
519  // build the env var ptrs
520  std::vector<std::string> envStrs;
521  std::vector<gchar *> envPtrs;
522 
523  for ( char **envPtr = environ; *envPtr != nullptr; envPtr++ )
524  envPtrs.push_back( *envPtr );
525 
526  envStrs.reserve( _environment.size() );
527  envPtrs.reserve( envPtrs.size() + _environment.size() + ( _useDefaultLocale ? 2 : 1 ) );
528  for ( const auto &env : _environment ) {
529  envStrs.push_back( env.first + "=" + env.second );
530  envPtrs.push_back( envStrs.back().data() );
531  }
532  if ( _useDefaultLocale ) {
533  envStrs.push_back( "LC_ALL=C" );
534  envPtrs.push_back( envStrs.back().data() );
535  }
536  envPtrs.push_back( nullptr );
537 
538  GLibForkData data;
539  data.that = this;
540  data.pidParent = ::getpid();
541 
542  bool needCallback = !_chroot.empty() || _dieWithParent || _switchPgid || _mapFds.size();
543 
544  auto spawnFlags = GSpawnFlags( G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH_FROM_ENVP );
545  if ( _mapFds.size() )
546  spawnFlags = GSpawnFlags( spawnFlags | G_SPAWN_LEAVE_DESCRIPTORS_OPEN );
547 
548  GPid childPid = -1;
549  g_autoptr(GError) error = NULL;
550  g_spawn_async_with_fds(
551  chdirTo,
552  const_cast<gchar**>(argv),
553  envPtrs.data(),
554  spawnFlags,
555  needCallback ? &GlibSpawnEngine::glibSpawnCallback : nullptr,
556  needCallback ? &data : nullptr,
557  &childPid,
558  stdin_fd, //in
559  stdout_fd, //out
560  stderr_fd, //err
561  &error
562  );
563 
564  if ( !error ) {
565  _pid = childPid;
566  } else {
567  _execError = zypp::str::form( _("Can't fork (%s)."), strerror(errno).c_str() );
568  _exitStatus = 127;
569  ERR << _execError << std::endl;
570  return false;
571  }
572  return true;
573 }
574 
575 void zyppng::GlibSpawnEngine::glibSpawnCallback(void *data)
576 {
577  GLibForkData *d = reinterpret_cast<GLibForkData *>(data);
578 
579  d->that->resetSignals();
580  bool doChroot = !d->that->_chroot.empty();
581 
582  if ( d->that->_switchPgid )
583  setpgid( 0, 0);
584 
585  if ( doChroot ) {
586  std::string execError;
587 
588  if ( ::chroot( d->that->_chroot.c_str() ) == -1 ) {
589  execError = zypp::str::form( "Can't chroot to '%s' (%s).", d->that->_chroot.c_str(), strerror(errno).c_str() );
590  std::cerr << execError << std::endl;// After fork log on stderr too
591  _exit (128); // No sense in returning! I am forked away!!
592  }
593 
594  std::string chdir; //if we are in chroot we need to chdir again
595  if ( d->that->_workingDirectory.empty() ) {
596  chdir = "/";
597  } else {
598  chdir = d->that->_workingDirectory.asString();
599  }
600 
601  if ( !chdir.empty() && ::chdir( chdir.data() ) == -1 )
602  {
603  execError = doChroot ? zypp::str::form( "Can't chdir to '%s' inside chroot '%s' (%s).", chdir.data(), d->that->_chroot.c_str(), strerror(errno).c_str() )
604  : zypp::str::form( "Can't chdir to '%s' (%s).", chdir.data(), strerror(errno).c_str() );
605  std::cerr << execError << std::endl; // After fork log on stderr too
606  _exit (128); // No sense in returning! I am forked away!!
607  }
608 
609  }
610 
611  if ( d->that->_dieWithParent ) {
612  // process dies with us
613  int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
614  if (r == -1) {
615  //ignore if it did not work, worst case the process lives on after the parent dies
616  std::cerr << "Failed to set PR_SET_PDEATHSIG" << std::endl;// After fork log on stderr too
617  }
618 
619  // test in case the original parent exited just
620  // before the prctl() call
621  pid_t ppidNow = getppid();
622  if (ppidNow != d->pidParent ) {
623  std::cerr << "PPID changed from "<<d->pidParent<<" to "<< ppidNow << std::endl;// After fork log on stderr too
624  _exit(128);
625  }
626  }
627 
628  // map the extra fds the user might have set
629  d->that->mapExtraFds();
630 }
631 #endif
Interface to gettext.
Namespace intended to collect all environment variables we use.
Definition: Env.h:24
Listentry returned by readdir.
Definition: PathInfo.h:509
#define _(MSG)
Definition: Gettext.h:39
Convenience errno wrapper.
Definition: Errno.h:25
static void watchPID(pid_t pid_r)
std::string form(const char *format,...) __attribute__((format(printf
Printf style construction of std::string.
Definition: String.cc:39
AutoDispose<int> calling ::close
Definition: AutoDispose.h:309
#define ERR
Definition: Logger.h:102
bool isRunning(bool wait=false) override
std::string strerror(int errno_r)
Return string describing the error_r code.
Definition: String.cc:56
bool isExist() const
Return whether valid stat info exists.
Definition: PathInfo.h:286
bool start(const char *const *argv, int stdin_fd, int stdout_fd, int stderr_fd) override
bool waitForExit(const std::optional< uint64_t > &timeout={}) override
bool writeAll(int fd, void *buf, size_t size)
Definition: IOTools.cc:55
void mapExtraFds(int controlFd=-1)
static std::optional< Pipe > create(int flags=0)
Definition: linuxhelpers.cc:71
static uint64_t now()
Definition: timer.cc:70
auto eintrSafeCall(Fun &&function, Args &&... args)
Wrapper class for ::stat/::lstat.
Definition: PathInfo.h:225
void setUsePty(const bool set=true)
int dirForEachExt(const Pathname &dir_r, const function< bool(const Pathname &, const DirEntry &)> &fnc_r)
Simiar to.
Definition: PathInfo.cc:598
ReadAllResult readAll(int fd, void *buf, size_t size)
Definition: IOTools.cc:69
#define DBG
Definition: Logger.h:99
static uint64_t elapsedSince(const uint64_t start)
Definition: timer.cc:83