/* ** ATOP - System & Process Monitor ** ** The program 'atop' offers the possibility to view the activity of ** the system on system-level as well as process-level. ** ========================================================================== ** Author: Gerlof Langeveld ** E-mail: gerlof.langeveld@atoptool.nl ** Date: September 2002 ** -------------------------------------------------------------------------- ** Copyright (C) 2000-2010 Gerlof Langeveld ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 2, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, but ** WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ** See the GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ** -------------------------------------------------------------------------- ** ** $Log: rawlog.c,v $ ** Revision 1.32 2010/11/26 06:06:35 gerlof ** Cosmetic. ** ** Revision 1.31 2010/11/17 12:43:31 gerlof ** The flag -r followed by exactly 8 'y' characters is not considered ** as 8 days ago, but as a literal filename. ** ** Revision 1.30 2010/10/23 14:03:03 gerlof ** Counters for total number of running and sleep threads (JC van Winkel). ** ** Revision 1.29 2010/04/23 13:55:52 gerlof ** Get rid of all setuid-root privs before activating other program. ** ** Revision 1.28 2010/04/23 12:19:35 gerlof ** Modified mail-address in header. ** ** Revision 1.27 2010/04/16 12:55:16 gerlof ** Automatically start another version of atop if the logfile to ** be read has not been created by the current version. ** ** Revision 1.26 2010/03/02 13:55:29 gerlof ** Struct stat size did not fit in short any more (modified to int). ** ** Revision 1.25 2009/12/17 08:16:06 gerlof ** Introduce branch-key to go to specific time in raw file. ** ** Revision 1.24 2009/11/27 15:26:29 gerlof ** Added possibility to specify y[y..] als filename for -r flag ** to access file of yesterday, day before yesterday, etc. ** ** Revision 1.23 2009/11/27 14:28:14 gerlof ** Rollback a "transaction" when not all parts could be ** written to the logfile (e.g. file system full) to avoid ** a corrupted logfile. ** ** Revision 1.22 2008/01/07 10:18:05 gerlof ** Implement possibility to make summaries. ** ** Revision 1.21 2007/08/16 12:01:25 gerlof ** Add support for atopsar reporting. ** ** Revision 1.20 2007/03/20 13:02:25 gerlof ** Introduction of variable supportflags. ** ** Revision 1.19 2007/03/20 11:11:57 gerlof ** Verify success of malloc's. ** ** Revision 1.18 2007/03/20 07:25:59 gerlof ** Avoid loop when incompatible raw file is read. ** Verify return code of compress/uncompress functions. ** ** Revision 1.17 2007/02/23 07:34:00 gerlof ** Changed unsigned short's into unsigned int's for process-counters ** in raw record. ** ** Revision 1.16 2007/02/13 10:33:27 gerlof ** Removal of external declarations. ** Store pagesize and hertz in raw logfile. ** ** Revision 1.15 2006/01/30 09:12:34 gerlof ** Minor bug-fix. ** ** Revision 1.14 2005/10/21 09:50:36 gerlof ** Per-user accumulation of resource consumption. ** ** Revision 1.13 2004/12/14 15:06:23 gerlof ** Implementation of patch-recognition for disk and network-statistics. ** ** Revision 1.12 2004/05/06 09:47:36 gerlof ** Ported to kernel-version 2.6. ** ** Revision 1.11 2003/07/07 09:26:48 gerlof ** Cleanup code (-Wall proof). ** ** Revision 1.10 2003/07/03 12:04:11 gerlof ** Implemented subcommand `r' (reset). ** ** Revision 1.9 2003/06/27 12:32:48 gerlof ** Removed rawlog compatibility. ** ** Revision 1.8 2003/02/06 14:08:33 gerlof ** Cosmetic changes. ** ** Revision 1.7 2003/01/17 07:33:39 gerlof ** Modified process statistics: add command-line. ** Implement compatibility for old tstat-structure read from logfiles. ** ** Revision 1.6 2002/10/30 13:46:11 gerlof ** Generate notification for statistics since boot. ** Adapt interval from ushort to ulong (bug-solution); ** this results in incompatible logfile but old logfiles can still be read. ** ** Revision 1.5 2002/10/24 12:22:25 gerlof ** In case of writing a raw logfile: ** If the logfile already exists, append new records to the existing file. ** If the logfile does not yet exist, create it. ** ** Revision 1.4 2002/10/08 11:35:22 gerlof ** Modified storage of raw filename. ** ** Revision 1.3 2002/10/03 10:41:51 gerlof ** Avoid end-less loop when using end-time specification (flag -e). ** ** Revision 1.2 2002/09/17 13:17:01 gerlof ** Allow key 'T' to be pressed to view previous sample in raw file. ** ** */ static const char rcsid[] = "$Id: rawlog.c,v 1.32 2010/11/26 06:06:35 gerlof Exp $"; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "atop.h" #include "showgeneric.h" #include "photoproc.h" #include "photosyst.h" #define BASEPATH "/var/log/atop" /* ** structure which describes the raw file contents ** ** layout raw file: rawheader ** ** rawrecord \ ** compressed system-level statistics | sample 1 ** compressed process-level statistics / ** ** rawrecord \ ** compressed system-level statistics | sample 2 ** compressed process-level statistics / ** ** etcetera ..... */ #define MYMAGIC (unsigned int) 0xfeedbeef struct rawheader { unsigned int magic; unsigned short aversion; /* creator atop version with MSB */ unsigned short future1; /* can be reused */ unsigned short future2; /* can be reused */ unsigned short rawheadlen; /* length of struct rawheader */ unsigned short rawreclen; /* length of struct rawrecord */ unsigned short hertz; /* clock interrupts per second */ unsigned short sfuture[6]; /* future use */ unsigned int sstatlen; /* length of struct sstat */ unsigned int tstatlen; /* length of struct tstat */ struct utsname utsname; /* info about this system */ char cfuture[8]; /* future use */ unsigned int pagesize; /* size of memory page (bytes) */ int supportflags; /* used features */ int osrel; /* OS release number */ int osvers; /* OS version number */ int ossub; /* OS version subnumber */ int ifuture[6]; /* future use */ }; struct rawrecord { time_t curtime; /* current time (epoch) */ unsigned short flags; /* various flags */ unsigned short sfuture[3]; /* future use */ unsigned int scomplen; /* length of compressed sstat */ unsigned int pcomplen; /* length of compressed tstat's */ unsigned int interval; /* interval (number of seconds) */ unsigned int ndeviat; /* number of tasks in list */ unsigned int nactproc; /* number of processes in list */ unsigned int ntask; /* total number of tasks */ unsigned int totproc; /* total number of processes */ unsigned int totrun; /* number of running threads */ unsigned int totslpi; /* number of sleeping threads(S)*/ unsigned int totslpu; /* number of sleeping threads(D)*/ unsigned int totzomb; /* number of zombie processes */ unsigned int nexit; /* number of exited processes */ unsigned int noverflow; /* number of overflow processes */ unsigned int ifuture[6]; /* future use */ }; static int getrawrec (int, struct rawrecord *, int); static int getrawsstat(int, struct sstat *, int); static int getrawtstat(int, struct tstat *, int, int); static int rawwopen(void); static int lookslikedatetome(char *); static void testcompval(int, char *); static void try_other_version(int, int); /* ** write a raw record to file ** (file is opened/created during the first call) */ char rawwrite(time_t curtime, int numsecs, struct sstat *ss, struct tstat *ts, struct tstat **proclist, int ndeviat, int ntask, int nactproc, int totproc, int totrun, int totslpi, int totslpu, int totzomb, int nexit, unsigned int noverflow, char flag) { static int rawfd = -1; struct rawrecord rr; int rv; struct stat filestat; Byte scompbuf[sizeof(struct sstat)], *pcompbuf; unsigned long scomplen = sizeof scompbuf; unsigned long pcomplen = sizeof(struct tstat) * ndeviat; /* ** first call: ** take care that the log file is opened */ if (rawfd == -1) rawfd = rawwopen(); /* ** register current size of file in order to "roll back" ** writes that have been done while not *all* writes could ** succeed, e.g. when file system full */ (void) fstat(rawfd, &filestat); /* ** compress system- and process-level statistics */ rv = compress(scompbuf, &scomplen, (Byte *)ss, (unsigned long)sizeof *ss); testcompval(rv, "compress"); pcompbuf = malloc(pcomplen); ptrverify(pcompbuf, "Malloc failed for compression buffer\n"); rv = compress(pcompbuf, &pcomplen, (Byte *)ts, (unsigned long)pcomplen); testcompval(rv, "compress"); /* ** fill record header and write to file */ memset(&rr, 0, sizeof rr); rr.curtime = curtime; rr.interval = numsecs; rr.flags = 0; rr.ndeviat = ndeviat; rr.nactproc = nactproc; rr.ntask = ntask; rr.nexit = nexit; rr.noverflow = noverflow; rr.totproc = totproc; rr.totrun = totrun; rr.totslpi = totslpi; rr.totslpu = totslpu; rr.totzomb = totzomb; rr.scomplen = scomplen; rr.pcomplen = pcomplen; if (flag&RRBOOT) rr.flags |= RRBOOT; if (supportflags & NETATOP) rr.flags |= RRNETATOP; if (supportflags & NETATOPD) rr.flags |= RRNETATOPD; if ( write(rawfd, &rr, sizeof rr) == -1) { fprintf(stderr, "%s - ", rawname); perror("write raw record"); if ( ftruncate(rawfd, filestat.st_size) == -1) cleanstop(8); cleanstop(7); } /* ** write compressed system status structure to file */ if ( write(rawfd, scompbuf, scomplen) == -1) { fprintf(stderr, "%s - ", rawname); perror("write raw status record"); if ( ftruncate(rawfd, filestat.st_size) == -1) cleanstop(8); cleanstop(7); } /* ** write compressed list of process status structures to file */ if ( write(rawfd, pcompbuf, pcomplen) == -1) { fprintf(stderr, "%s - ", rawname); perror("write raw process record"); if ( ftruncate(rawfd, filestat.st_size) == -1) cleanstop(8); cleanstop(7); } free(pcompbuf); return '\0'; } /* ** open a raw file for writing ** ** if the raw file exists already: ** - read and validate the header record (be sure it is an atop-file) ** - seek to the end of the file ** ** if the raw file does not yet exist: ** - create the raw file ** - write a header record ** ** return the filedescriptor of the raw file */ static int rawwopen() { struct rawheader rh; int fd; /* ** check if the file exists already */ if ( (fd = open(rawname, O_RDWR)) >= 0) { /* ** read and verify header record */ if ( read(fd, &rh, sizeof rh) < sizeof rh) { fprintf(stderr, "%s - cannot read header\n", rawname); cleanstop(7); } if (rh.magic != MYMAGIC) { fprintf(stderr, "file %s exists but does not contain raw " "atop output (wrong magic number)\n", rawname); cleanstop(7); } if ( rh.sstatlen != sizeof(struct sstat) || rh.tstatlen != sizeof(struct tstat) || rh.rawheadlen != sizeof(struct rawheader) || rh.rawreclen != sizeof(struct rawrecord) || rh.supportflags != (supportflags & ~(NETATOP|NETATOPD))) { fprintf(stderr, "existing file %s has incompatible header\n", rawname); if (rh.aversion & 0x8000 && (rh.aversion & 0x7fff) != getnumvers()) { fprintf(stderr, "(created by version %d.%d - " "current version %d.%d)\n", (rh.aversion >> 8) & 0x7f, rh.aversion & 0xff, getnumvers() >> 8, getnumvers() & 0x7f); } cleanstop(7); } (void) lseek(fd, (off_t) 0, SEEK_END); return fd; } /* ** file does not exist (or can not be opened) */ if ( (fd = creat(rawname, 0666)) == -1) { fprintf(stderr, "%s - ", rawname); perror("create raw file"); cleanstop(7); } memset(&rh, 0, sizeof rh); rh.magic = MYMAGIC; rh.aversion = getnumvers() | 0x8000; rh.sstatlen = sizeof(struct sstat); rh.tstatlen = sizeof(struct tstat); rh.rawheadlen = sizeof(struct rawheader); rh.rawreclen = sizeof(struct rawrecord); rh.supportflags = (supportflags & ~(NETATOP|NETATOPD)); rh.osrel = osrel; rh.osvers = osvers; rh.ossub = ossub; rh.hertz = hertz; rh.pagesize = pagesize; memcpy(&rh.utsname, &utsname, sizeof rh.utsname); if ( write(fd, &rh, sizeof rh) == -1) { fprintf(stderr, "%s - ", rawname); perror("write raw header"); cleanstop(7); } return fd; } /* ** read the contents of a raw file */ #define OFFCHUNK 256 void rawread(void) { int i, j, rawfd, len; char *py; struct rawheader rh; struct rawrecord rr; struct sstat devsstat; struct tstat *devtstat; struct tstat **devpstat; struct stat filestat; /* ** variables to maintain the offsets of the raw records ** to be able to see previous samples again */ off_t *offlist; unsigned int offsize = 0; unsigned int offcur = 0; char lastcmd = 'X', flags; time_t timenow; struct tm *tp; switch ( len = strlen(rawname) ) { /* ** if no filename is specified, assemble the name of the raw file */ case 0: timenow = time(0); tp = localtime(&timenow); snprintf(rawname, RAWNAMESZ, "%s/atop_%04d%02d%02d", BASEPATH, tp->tm_year+1900, tp->tm_mon+1, tp->tm_mday); break; /* ** if date specified as filename in format YYYYMMDD, assemble ** the full pathname of the raw file */ case 8: if ( access(rawname, F_OK) == 0) break; /* existing file */ if (lookslikedatetome(rawname)) { char savedname[RAWNAMESZ]; strncpy(savedname, rawname, RAWNAMESZ-1); snprintf(rawname, RAWNAMESZ, "%s/atop_%s", BASEPATH, savedname); break; } /* ** if one or more 'y' (yesterday) characters are used and that ** string is not known as an existing file, the standard logfile ** is shown from N days ago (N is determined by the number ** of y's). */ default: if ( access(rawname, F_OK) == 0) break; /* existing file */ /* ** make a string existing of y's to compare with */ py = malloc(len+1); ptrverify(py, "Malloc failed for 'yes' sequence\n"); memset(py, 'y', len); *(py+len) = '\0'; if ( strcmp(rawname, py) == 0 ) { timenow = time(0); timenow -= len*3600*24; tp = localtime(&timenow); snprintf(rawname, RAWNAMESZ, "%s/atop_%04d%02d%02d", BASEPATH, tp->tm_year+1900, tp->tm_mon+1, tp->tm_mday); } free(py); } /* ** open raw file */ if ( (rawfd = open(rawname, O_RDONLY)) == -1) { char command[512], tmpname1[RAWNAMESZ], tmpname2[RAWNAMESZ]; /* ** check if a compressed raw file is present */ snprintf(tmpname1, sizeof tmpname1, "%s.gz", rawname); if ( access(tmpname1, F_OK|R_OK) == -1) { fprintf(stderr, "%s - ", rawname); perror("open raw file"); cleanstop(7); } /* ** compressed raw file to be decompressed via gunzip */ fprintf(stderr, "Decompressing logfile ....\n"); snprintf(tmpname2, sizeof tmpname2, "/tmp/atopwrkXXXXXX"); rawfd = mkstemp(tmpname2); if (rawfd == -1) { fprintf(stderr, "%s - ", rawname); perror("creating decompression temp file"); cleanstop(7); } snprintf(command, sizeof command, "gunzip -c %s > %s", tmpname1, tmpname2); const int system_res = system (command); unlink(tmpname2); if (system_res) { fprintf(stderr, "%s - gunzip failed", rawname); cleanstop(7); } } /* ** read the raw header and verify the magic */ if ( read(rawfd, &rh, sizeof rh) < sizeof rh) { fprintf(stderr, "can not read raw file header\n"); cleanstop(7); } if (rh.magic != MYMAGIC) { fprintf(stderr, "file %s does not contain raw atop/atopsar " "output (wrong magic number)\n", rawname); cleanstop(7); } /* ** magic okay, but file-layout might have been modified */ if (rh.sstatlen != sizeof(struct sstat) || rh.tstatlen != sizeof(struct tstat) || rh.rawheadlen != sizeof(struct rawheader) || rh.rawreclen != sizeof(struct rawrecord) ) { fprintf(stderr, "raw file %s has incompatible format\n", rawname); if (rh.aversion & 0x8000 && (rh.aversion & 0x7fff) != getnumvers()) { fprintf(stderr, "(created by version %d.%d - " "current version %d.%d)\n", (rh.aversion >> 8) & 0x7f, rh.aversion & 0xff, getnumvers() >> 8, getnumvers() & 0x7f); } else { fprintf(stderr, "(files from other system architectures might" " be binary incompatible)\n"); } close(rawfd); if (((rh.aversion >> 8) & 0x7f) != (getnumvers() >> 8) || (rh.aversion & 0xff) != (getnumvers() & 0x7f) ) { try_other_version((rh.aversion >> 8) & 0x7f, rh.aversion & 0xff); } cleanstop(7); } memcpy(&utsname, &rh.utsname, sizeof utsname); supportflags = rh.supportflags; osrel = rh.osrel; osvers = rh.osvers; ossub = rh.ossub; interval = 0; if (rh.hertz) hertz = rh.hertz; if (rh.pagesize) pagesize = rh.pagesize; /* ** allocate a list for backtracking of rawrecord-offsets */ offlist = malloc(sizeof(off_t) * OFFCHUNK); ptrverify(offlist, "Malloc failed for backtrack list\n"); offsize = OFFCHUNK; *offlist = lseek(rawfd, 0, SEEK_CUR); offcur = 1; /* ** read a raw record header until end-of-file */ sampcnt = 0; while (lastcmd && lastcmd != 'q') { while ( getrawrec(rawfd, &rr, rh.rawreclen) == rh.rawreclen) { unsigned int secsinday = daysecs(rr.curtime); /* ** store the offset of the raw record in the offset list ** in case of offset list overflow, extend the list */ *(offlist+offcur) = lseek(rawfd, 0, SEEK_CUR) - rh.rawreclen; if ( ++offcur >= offsize ) { offlist = realloc(offlist, (offsize+OFFCHUNK)*sizeof(off_t)); ptrverify(offlist, "Realloc failed for backtrack list\n"); offsize+= OFFCHUNK; } /* ** check if this sample is within the time-range ** specified with the -b and -e flags (if any) */ if ( (begintime && begintime > secsinday) ) { lastcmd = 1; lseek(rawfd, rr.scomplen+rr.pcomplen, SEEK_CUR); continue; } begintime = 0; // allow earlier times from now on if ( (endtime && endtime < secsinday) ) { free(offlist); close(rawfd); return; } /* ** allocate space, read compressed system-level ** statistics and decompress */ if ( !getrawsstat(rawfd, &devsstat, rr.scomplen) ) cleanstop(7); /* ** allocate space, read compressed process-level ** statistics and decompress */ devtstat = malloc(sizeof(struct tstat) * rr.ndeviat); ptrverify(devtstat, "Malloc failed for %d stored tasks\n", rr.ndeviat); devpstat = malloc(sizeof(struct tstat *) * rr.nactproc); ptrverify(devpstat, "Malloc failed for %d stored processes\n", rr.nactproc); if ( !getrawtstat(rawfd, devtstat, rr.pcomplen, rr.ndeviat) ) cleanstop(7); /* ** activate the installed print-function to visualize ** the system- and process-level statistics */ sampcnt++; if (rr.flags & RRNETATOP) supportflags |= NETATOP; else supportflags &= ~NETATOP; if (rr.flags & RRNETATOPD) supportflags |= NETATOPD; else supportflags &= ~NETATOPD; flags = rr.flags & RRBOOT; (void) fstat(rawfd, &filestat); if ( filestat.st_size - lseek(rawfd, (off_t)0, SEEK_CUR) <= rh.rawreclen) flags |= RRLAST; for (i=j=0; i < rr.ndeviat; i++) { if ( (devtstat+i)->gen.isproc) devpstat[j++] = devtstat+i; } lastcmd = (vis.show_samp)(rr.curtime, rr.interval, &devsstat, devtstat, devpstat, rr.ndeviat, rr.ntask, rr.nactproc, rr.totproc, rr.totrun, rr.totslpi, rr.totslpu, rr.totzomb, rr.nexit, rr.noverflow, flags); free(devtstat); free(devpstat); switch (lastcmd) { case MSAMPPREV: if (offcur >= 2) offcur-= 2; else offcur = 0; lseek(rawfd, *(offlist+offcur), SEEK_SET); break; case MRESET: lseek(rawfd, *offlist, SEEK_SET); offcur = 1; break; case MSAMPBRANCH: if (begintime && begintime < secsinday) { lseek(rawfd, *offlist, SEEK_SET); offcur = 1; } } } begintime = 0; // allow earlier times from now on if (offcur >= 1) offcur--; lseek(rawfd, *(offlist+offcur), SEEK_SET); } free(offlist); close(rawfd); } /* ** read the next raw record from the raw logfile */ static int getrawrec(int rawfd, struct rawrecord *prr, int rrlen) { return read(rawfd, prr, rrlen); } /* ** read the system-level statistics from the current offset */ static int getrawsstat(int rawfd, struct sstat *sp, int complen) { Byte *compbuf; unsigned long uncomplen = sizeof(struct sstat); int rv; if ( (compbuf = malloc(complen)) == NULL) ptrverify(compbuf, "Malloc failed for reading compressed sysstats\n"); if ( read(rawfd, compbuf, complen) < complen) return 0; rv = uncompress((Byte *)sp, &uncomplen, compbuf, complen); testcompval(rv, "uncompress"); free(compbuf); return 1; } /* ** read the process-level statistics from the current offset */ static int getrawtstat(int rawfd, struct tstat *pp, int complen, int ndeviat) { Byte *compbuf; unsigned long uncomplen = sizeof(struct tstat) * ndeviat; int rv; compbuf = malloc(complen); ptrverify(compbuf, "Malloc failed for reading compressed procstats\n"); if ( read(rawfd, compbuf, complen) < complen) return 0; rv = uncompress((Byte *)pp, &uncomplen, compbuf, complen); testcompval(rv, "uncompress"); free(compbuf); return 1; } /* ** verify if a particular ascii-string is in the format yyyymmdd */ static int lookslikedatetome(char *p) { register int i; for (i=0; i < 8; i++) if ( !isdigit(*(p+i)) ) return 0; if (*p != '2') return 0; /* adapt this in the year 3000 */ if ( *(p+4) > '1') return 0; if ( *(p+6) > '3') return 0; return 1; /* yes, looks like a date to me */ } static void testcompval(int rv, char *func) { switch (rv) { case Z_OK: case Z_STREAM_END: case Z_NEED_DICT: break; case Z_MEM_ERROR: fprintf(stderr, "atop/atopsar - " "%s: failed due to lack of memory\n", func); cleanstop(7); case Z_BUF_ERROR: fprintf(stderr, "atop/atopsar - " "%s: failed due to lack of room in buffer\n", func); cleanstop(7); case Z_DATA_ERROR: fprintf(stderr, "atop/atopsar - " "%s: failed due to corrupted/incomplete data\n", func); cleanstop(7); default: fprintf(stderr, "atop/atopsar - " "%s: unexpected error %d\n", func, rv); cleanstop(7); } } /* ** try to activate another atop- or atopsar-version ** to read this logfile */ static void try_other_version(int majorversion, int minorversion) { char tmpbuf[1024], *p; extern char **argvp; int fds; struct rlimit rlimit; int setresuid(uid_t, uid_t, uid_t); /* ** prepare name of executable file ** the current pathname (if any) is stripped off */ snprintf(tmpbuf, sizeof tmpbuf, "%s-%d.%d", (p = strrchr(*argvp, '/')) ? p+1 : *argvp, majorversion, minorversion); fprintf(stderr, "trying to activate %s....\n", tmpbuf); /* ** be sure no open file descriptors are passed ** except stdin, stdout en stderr */ (void) getrlimit(RLIMIT_NOFILE, &rlimit); for (fds=3; fds < rlimit.rlim_cur; fds++) close(fds); /* ** be absolutely sure not to pass setuid-root privileges ** to the loaded program; errno EAGAIN and ENOMEM are not ** acceptable! */ if ( setresuid(getuid(), getuid(), getuid()) == -1 && errno != EPERM) { fprintf(stderr, "not possible to drop root-privileges!\n"); exit(1); } /* ** load alternative executable image ** at this moment the saved-uid might still be set ** to 'root' but this is reset at the moment of exec */ (void) execvp(tmpbuf, argvp); /* ** point of no return, except when exec failed */ fprintf(stderr, "activation of %s failed!\n", tmpbuf); }