/*- * Copyright (C) @BABOLO V.M 0.2 ΟΤ 2000 May 25 * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * * $FreeBSD: $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MY_ID 0x1984e34f8969adfb #define VERSION 1 #ifndef NAMEPID #define NAMEPID "+deleted.pid=" #endif NAMEPID #define BONUS_LIFE 2 /* 1 second minimum for rounding */ #define USE_A 1 #define USE_M 2 #define USE_C 4 #define NOW (time(NULL)) #define SHOW_DIR_DEBUG_LEVEL 3 #define C_PID_LEN 8 enum bool {undef=-1, false=0, true=1}; struct deleted_pid { char cpid[C_PID_LEN]; long long id; int version; pid_t pid; int prio; time_t ttl; time_t wake_up; unsigned char fmask, dmask; }; struct d_entry { struct d_entry *next; struct d_entry *high; char *name; time_t time; }; char *myname; short flag_v; enum bool res_low; #define RES_LOW(A) \ { if (flag_v > 1) fprintf(stderr, "Resources low in ###%03d\n", (A));\ res_low = true;\ break;\ } void debug_date(time_t time) { struct tm *t; const char mn[][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun" , "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; t = localtime(&time); fprintf( stderr, "%04d-%s-%02d %02d:%02d:%02d" , 1900 + t->tm_year, mn[t->tm_mon], t->tm_mday , t->tm_hour, t->tm_min, t->tm_sec ); } void debug_time(time_t time) { time_t tim = time; unsigned t; for (t = 0; tim >= 31556952; t++) tim -= 31556952; if (t) fprintf(stderr, "%uy", t); for (t = 0; tim >= 604800; t++) tim -= 604800; if (t) fprintf(stderr, "%uw", t); for (t = 0; tim >= 86400; t++) tim -= 86400; if (t) fprintf(stderr, "%ud", t); for (t = 0; tim >= 3600; t++) tim -= 3600; if (t) fprintf(stderr, "%uh", t); for (t = 0; tim >= 60; t++) tim -= 60; if (t) fprintf(stderr, "%um", t); if (tim || !time) fprintf(stderr, "%us", (unsigned)tim); } void debug_mask(int mask) { enum bool defined; defined = false; if (mask & USE_A) { fprintf(stderr, " atime"); defined = true; } if (mask & USE_C) { fprintf(stderr, " ctime"); defined = true; } if (mask & USE_M) { fprintf(stderr, " mtime"); defined = true; } if (!defined) fprintf(stderr, " empty"); } void debug_node(struct d_entry *l) { if (l) { debug_date(l->time); fprintf( stderr, " %08X next %08X high %08X %s\n" , (unsigned int)l, (unsigned int)(l->next), (unsigned int)(l->high), l->name ); }else fprintf( stderr, "-----------------\n"); } void debug_tree(struct d_entry *tree, int level) { int i; struct d_entry *l; for (l = tree; l; l = l->next) { for (i = 0; i < level; i++) fputc('|', stderr); debug_node(l); if (l->high) debug_tree(l->high, level + 1); } } time_t select_time(struct stat *sb, int mask) { enum bool defined; time_t result = 0; defined = false; if (mask & USE_A) { result = sb->st_atime; defined = true; } if (mask & USE_C) { if (!defined) result = sb->st_ctime; else if (result < sb->st_ctime) result = sb->st_ctime; defined = true; } if (mask & USE_M) { if (!defined) result = sb->st_mtime; else if (result < sb->st_mtime) result = sb->st_mtime; defined = true; } if (defined) return(result); fprintf(stderr, "%s: time mask is empty\n", myname); exit(EX_USAGE); } void free_node(struct d_entry *node) { if (node->name) free(node->name); free(node); } void free_tree(struct d_entry *tree) { struct d_entry *d; while (tree) { if (tree->high && tree->next) { d = tree->next; if (d->high && d->next) { tree->next = d->high; d->high = tree; tree = d; }else if (d->high) { tree->next = d->high; free_node(d); }else if (d->next) { tree->next = d->next; free_node(d); }else { tree->next = NULL; free_node(d); } }else if (tree->high) { d = tree; tree = tree->high; free_node(d); }else if (tree->next) { d = tree; tree = tree->next; free_node(d); }else { free_node(tree); tree = NULL; } } } #define FREE_TREE(A) {free_tree(A); A = NULL;} time_t youngest(time_t *a, time_t b) { if (*a < b) *a = b; return(*a); } char *high_name(const char *path, const char *next) { char *next_name; asprintf( &next_name, "%s%s%s" , path , ( *path && path[strlen(path) - 1] == '/') ? "" : "/" , next ); return(next_name); } enum bool isin_tree(struct d_entry **list, const char *name) { struct d_entry *d; if (list) for (d = *list; d; d = d->next) if (!strcmp(name, d->name)) return(true); return(false); } struct d_entry *create_n(const char *name, time_t time) { struct d_entry *d; d = (struct d_entry *)malloc(sizeof(struct d_entry)); if (d) { d->high = NULL; d->next = NULL; d->name = name ? strdup(name) : NULL; d->time = time; } return(d); } void delete_n(struct d_entry **list) { struct d_entry *d; d = *list; *list = d->next; d->next = NULL; FREE_TREE(d); } void insert_n(struct d_entry **list, struct d_entry *d) { struct d_entry **l; int i; for (i = 0, l = list; *l && (*l)->time < d->time; l = &((*l)->next)) i++; if (i) FREE_TREE(d->high); if (*l) FREE_TREE((*l)->high); d->next = *l; *l = d; } int bubble_n(struct d_entry **list) { struct d_entry *d, **l; int i; i = 0; if (list && *list && (*list)->next) { d = *list; for (l = list; *(l = &((*l)->next)) && (*l)->time < d->time;) i++; if (i) { FREE_TREE(d->high); *list = d->next; d->next = *l; *l = d; } } return(i); } /*********************************************************************** * */ struct d_entry *build_tree(struct d_entry **list, const char *path, int fmask, int dmask) /* * *********************************************************************** * Build partial tree * high high high high * tree -> node ----> node ----> node ----> node ----> node < the oldest node in the tree * | | | | * | next | next | next | next * V V V V * node node node node * | | | | * | next | next | next | next * V V V V * node node node node * | | | * | next | next sorted | next * V V by time V * node node node * * sorted sorted sorted * by time by time by time - oldest is highest, * younger is lower, * bubbles go down (in sort) * * Next link points to the node in the same directory as parent. * Next linked list sorted by time. * High link points to subdirectory. * The only high linked line exists - the way to the * oldest node in the directory. * Time of the directory is oldest time of files and subdirectories * of this directory if it is not empty. ********************************************************************** */ { DIR *dirp; struct dirent *dp; struct d_entry *d, *l; struct stat sb; size_t s; char *next_name; int f; enum bool rebild; if (flag_v > 1) fprintf(stderr, "Build tree %s\n", path); if ( lstat(path, &sb) || !S_ISDIR(sb.st_mode) || !(dirp = opendir(path)) ) return(NULL); if (!list) { rebild = false; l = NULL; list = &l; }else rebild = true; s = strlen(NAMEPID); while ((dp = readdir(dirp)) != NULL) { if ( strcmp(dp->d_name, ".") && strcmp(dp->d_name, "..") && ( dp->d_namlen != s || strcmp(path, "/") || strcmp(dp->d_name, NAMEPID) ) ) { chdir(path); if ( (rebild && isin_tree(list, dp->d_name)) || lstat(dp->d_name, &sb) ); else if (S_ISLNK(sb.st_mode)) { time_t linktime = select_time(&sb, fmask); if (!stat(dp->d_name, &sb)) (void)youngest(&linktime, select_time(&sb, fmask)); if (!(d = create_n(dp->d_name, linktime))) RES_LOW(600); /*###*/ insert_n(list, d); }else if (!(S_ISREG(sb.st_mode) || S_ISDIR(sb.st_mode))) { if (!(d = create_n(dp->d_name, select_time(&sb, fmask)))) RES_LOW(601); /*###*/ insert_n(list, d); }else if ((f = open(dp->d_name, O_RDONLY | O_EXLOCK | O_NONBLOCK)) >= 0) { close(f); if (S_ISREG(sb.st_mode)) { if (!(d = create_n(dp->d_name, select_time(&sb, fmask)))) RES_LOW(602); /*###*/ insert_n(list, d); }else if (S_ISDIR(sb.st_mode)) { if (!(d = create_n(dp->d_name, select_time(&sb, dmask)))) RES_LOW(603); /*###*/ if (!(next_name = high_name(path, dp->d_name))) RES_LOW(604); /*###*/ d->high = build_tree(NULL, next_name, fmask, dmask); free(next_name); if (d->high) d->time = d->high->time; insert_n(list, d); } }else if (errno == EMFILE || errno == ENFILE) { RES_LOW(605); /*###*/ }else if (errno == ETXTBSY || errno == EAGAIN) { if (!(d = create_n(dp->d_name, NOW))) RES_LOW(606); /*###*/ insert_n(list, d); } } } (void)closedir(dirp); return(*list); } void clear_tree(struct d_entry **tree, time_t when, const char *path, int fmask, int dmask) { struct stat sb; int f; char *next_name = NULL; #define REBILD(TREE, CODE) \ { if (*(TREE))\ { if (!(next_name = high_name(path, (*(TREE))->name))) RES_LOW(CODE);\ (void)build_tree(&(*(TREE))->high, next_name, fmask, dmask);\ free(next_name);\ } } if (flag_v > 1) fprintf(stderr, "Clear tree %s\n", path); if (flag_v > SHOW_DIR_DEBUG_LEVEL + 1) debug_tree(*tree, 0); for (; *tree && (*tree)->time <= when;) { chdir(path); if (lstat((*tree)->name, &sb)) { if (flag_v) fprintf(stderr, " gone %s\n", (*tree)->name); delete_n(tree); REBILD(tree, 700); /*###*/ }else if (!(S_ISREG(sb.st_mode) || S_ISDIR(sb.st_mode))) { time_t linktime = select_time(&sb, fmask); if (S_ISLNK(sb.st_mode) && !stat((*tree)->name, &sb)) (void)youngest(&linktime, select_time(&sb, fmask)); if (youngest(&((*tree)->time), linktime) <= when) { if (unlink((*tree)->name)) { if (flag_v) fprintf(stderr, "no unlink XXX %s\n", (*tree)->name); (*tree)->time = NOW; if (bubble_n(tree)) REBILD(tree, 701); /*###*/ }else { if (flag_v) fprintf(stderr, " unlink XXX %s\n", (*tree)->name); delete_n(tree); REBILD(tree, 702); /*###*/ } }else { if (flag_v) fprintf(stderr, " younger XXX %s\n", (*tree)->name); if (bubble_n(tree)) REBILD(tree, 703); /*###*/ } }else if ((f = open((*tree)->name, O_RDONLY | O_EXLOCK | O_NONBLOCK)) >= 0) { close(f); if (S_ISREG(sb.st_mode)) { if (youngest(&((*tree)->time), select_time(&sb, fmask)) <= when) { if (unlink((*tree)->name)) { if (flag_v) fprintf(stderr, "no unlink REG %s\n", (*tree)->name); (*tree)->time = NOW; if (bubble_n(tree)) REBILD(tree, 704); /*###*/ }else { if (flag_v) fprintf(stderr, " unlink REG %s\n", (*tree)->name); delete_n(tree); REBILD(tree, 705); /*###*/ } }else { if (flag_v) fprintf(stderr, " younger REG %s\n", (*tree)->name); if (bubble_n(tree)) REBILD(tree, 706); /*###*/ } }else if (S_ISDIR(sb.st_mode) && (*tree)->time <= when) { if ((*tree)->high) { if (!(next_name = high_name(path, (*tree)->name))) RES_LOW(707); /*###*/ clear_tree(&((*tree)->high), when, next_name, fmask, dmask); free(next_name); chdir(path); if ((*tree)->high) (*tree)->time = (*tree)->high->time; else if(!stat((*tree)->name, &sb)) (void)youngest(&((*tree)->time), select_time(&sb, dmask)); if (bubble_n(tree)) REBILD(tree, 708); /*###*/ }else if (!rmdir((*tree)->name)) { if (flag_v) fprintf(stderr, " rmdir DIR %s\n", (*tree)->name); delete_n(tree); REBILD(tree, 709); /*###*/ }else { if (flag_v) fprintf(stderr, "no rmdir DIR %s\n", (*tree)->name); (*tree)->time = NOW; if (bubble_n(tree)) REBILD(tree, 710); /*###*/ } } }else if (errno == EMFILE || errno == ENFILE) { RES_LOW(711); /*###*/ }else if (errno == ETXTBSY || errno == EAGAIN) { if (flag_v) fprintf(stderr, " lock younger %s\n", (*tree)->name); (*tree)->time = NOW; if (bubble_n(tree)) REBILD(tree, 712); /*###*/ } } (void)build_tree(tree, path, fmask, dmask); while (0 && *tree && !((*tree)->high)) { if (!(next_name = high_name(path, (*tree)->name))) RES_LOW(713); /*###*/ (void)build_tree(&(*tree)->high, next_name, fmask, dmask); free(next_name); if (!bubble_n(tree)) break; } if (flag_v > 1) fprintf(stderr, "Cleared %s\n", path); } int prio_set(int conf_prio) { int prio; enum bool correct; correct = true; errno = 0; prio = getpriority(PRIO_PROCESS, 0); if (prio == -1 && errno) { if (flag_v) warn("getpriority"); prio = 0; correct = false; } if (!correct || prio < conf_prio) { (void)setpriority(PRIO_PROCESS, 0, conf_prio); errno = 0; prio = getpriority(PRIO_PROCESS, 0); if (prio == -1 && errno) { if (flag_v) warn("getpriority"); prio = 0; } } return(prio); } void main_loop(struct deleted_pid *conf, int prio) { struct d_entry *tree; time_t now, base, unsleep, ttl; tree = build_tree(NULL, "/", conf->fmask, conf->dmask); if (flag_v == SHOW_DIR_DEBUG_LEVEL) debug_tree(tree, 0); base = NOW; while (prio <= conf->prio) { if (flag_v > SHOW_DIR_DEBUG_LEVEL) debug_tree(tree, 0); res_low = false; /*XXXX need res_low test */ ttl = conf->ttl + BONUS_LIFE; now = NOW; if (tree) conf->wake_up = ttl + tree->time; else conf->wake_up = ttl + base; if (flag_v) { fprintf(stderr, "UTC now "); debug_date(now); if (conf->wake_up > now) { fprintf(stderr, ", wake_up at "); debug_date(conf->wake_up); } fprintf(stderr, "\n"); } unsleep = 0; if (conf->wake_up > now) unsleep = sleep(conf->wake_up - now); if (!unsleep) { clear_tree(&tree, NOW - ttl, "/", conf->fmask, conf->dmask); base = NOW; } prio = prio_set(conf->prio); } } void query() { int file_pid; struct deleted_pid *conf; pid_t pid; conf = NULL; file_pid = open(NAMEPID, O_RDONLY | O_NONBLOCK | O_EXLOCK, 0600); if (file_pid >= 0) /* If no other deleted running */ { fprintf(stderr, "Control file found. Deleted daemon not found\n"); if (flock(file_pid, LOCK_SH)) err(EX_UNAVAILABLE, "flock shared after exclusive"); }else if (errno != EWOULDBLOCK) fprintf(stderr, "Control file not found or not permitted\n"); else { file_pid = open(NAMEPID, O_RDONLY | O_NONBLOCK | O_SHLOCK); if (file_pid >= 0) fprintf(stderr, "Control file found.\n"); else fprintf(stderr, "Where is control file?\n"); } if (file_pid >= 0) { conf = (struct deleted_pid *)mmap( NULL , sizeof(struct deleted_pid) , PROT_READ , MAP_SHARED , file_pid , (off_t)0 ); if ((void*)conf == MAP_FAILED) err(EX_UNAVAILABLE, "mmap"); if ( conf->id != MY_ID || conf->version != VERSION ) { fprintf(stderr, NAMEPID" belongs to another program or version\n"); }else { pid = atoi(conf->cpid); if (pid != conf->pid) fprintf(stderr, "!!! (cpid == %d) != (pid == %d) !!!\n", pid, conf->pid); else { fprintf(stderr, "pid == %d", pid); if (kill(pid, 0)) fprintf(stderr, " not running\n"); else fprintf(stderr, " running\n"); } fprintf( stderr, "Nice = %d, TimeToLive = ", conf->prio); debug_time(conf->ttl); fprintf( stderr, "\nWake up at "); debug_date(conf->wake_up); fprintf(stderr, "\nFile mask:"); debug_mask(conf->fmask); fprintf(stderr, "\nDirectory mask:"); debug_mask(conf->dmask); fprintf(stderr, "\n"); } } } void usage() { fprintf( stderr , "deleted @BABOLO V.M "VERS" ΟΤ 2000 May 30\n" "Usage:\n" "deleted -t TimeToLive [-n Nice] [-f mask] [-d mask] directory\n" " where mask is some word from [amc]*\n" " for files (-f) and directories (-d),\n" " a - use atime\n" " m - use mtime\n" " c - use ctime\n" " or\n" "deleted -q directory\n" " to query running deleted daemon\n" ); } int get_t_mask(char *in) { char *p; int res; for (res = 0, p = in; p && *p; p++) { switch (*p) {case 'a': res |= USE_A; break; case 'm': res |= USE_M; break; case 'c': res |= USE_C; break; default: usage() ; exit(EX_USAGE); } } return(res); } void huphandler(int signal) {return;} int main(int argc, char **argv) { int c, flag_q; struct deleted_pid parm; int file_pid; int prio; struct sigaction act = {{huphandler}, 0, {{0}}}; struct deleted_pid *conf; myname = *argv; flag_v = 0; flag_q = 0; parm.id = MY_ID; parm.version = VERSION; parm.pid = getpid(); parm.ttl = 0; parm.prio = 0; bzero(parm.cpid, C_PID_LEN); snprintf(parm.cpid, C_PID_LEN, "%d\n", parm.pid); parm.fmask = USE_A | USE_M | USE_C; parm.dmask = USE_M | USE_C; while ((c = getopt(argc, argv, "qvn:t:d:f:h?")) > 0) { time_t t; switch (c) {case 'q': flag_q++; break; case 'v': flag_v++; break; case 't': for (parm.ttl = 0, t = 0; *optarg; optarg++) { switch (*optarg) {case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': t = t * 10 + (*optarg - '0'); break; case 'Y':case 'y': t *= 53; case 'W':case 'w': t *= 7; case 'D':case 'd': t *= 24; case 'H':case 'h': t *= 60; case 'M':case 'm': t *= 60; case 'S':case 's': parm.ttl += t; t = 0; break; default: while (*optarg) optarg++; } } parm.ttl += t; break; case 'n': parm.prio = atoi(optarg); break; case 'd': parm.dmask = get_t_mask(optarg); break; case 'f': parm.fmask = get_t_mask(optarg); break; case 'h': case '?': default: usage(); exit(EX_OK); } } argc -= optind; argv += optind; if (!parm.ttl && !flag_q) { fprintf(stderr, "TimeToLive must be specified\n"); usage(); exit(EX_USAGE); } if (argc != 1) { fprintf(stderr, "TmpDir must be specified\n"); usage(EX_USAGE); exit(EX_USAGE); } /* *argv may be absolute path. or relative... */ if (!flag_q) { /**************************/ /* it is what seteuid for */ if (chroot(*argv)) err(EX_UNAVAILABLE, "chroot"); } /* */ /* */ /* */ if (setuid(getuid())) err(EX_UNAVAILABLE, "setuid"); /* END seteuid bit */ /**************************/ if (flag_q) { if (chdir(*argv)) err(EX_UNAVAILABLE, "chdir"); query(); exit(EX_OK); }else if (chdir("/")) err(EX_UNAVAILABLE, "chdir /"); if (sigaction(SIGHUP, &act, NULL)) err(EX_UNAVAILABLE, "sigaction"); prio = prio_set(parm.prio); if (parm.prio < prio) { if (flag_v) fprintf(stderr, "Nice %d impossible, %d used instead.\n", parm.prio, prio); parm.prio = prio; } conf = NULL; file_pid = -1; /* If two or more deleted starts almoust simultaniesly for this dir, one loop can be unsuccessful*/ while (1) { file_pid = open(NAMEPID, O_RDWR | O_NONBLOCK | O_CREAT | O_EXLOCK, 0600); if (file_pid >= 0) /* If no other deleted running */ { struct stat sb, sbf; if (flag_v) fprintf(stderr, "First deleted started\n"); if ( (lstat(NAMEPID, &sb)) || (fstat(file_pid, &sbf)) ) err(EX_UNAVAILABLE, "[fl]stat"); if ( (!S_ISREG(sb.st_mode)) || (sb.st_dev != sbf.st_dev) || (sb.st_ino != sbf.st_ino) ) { fprintf(stderr, "!!! Substitute attack !!! -> "NAMEPID); exit(EX_TEMPFAIL); } if (ftruncate(file_pid, sizeof(struct deleted_pid))) err(EX_UNAVAILABLE, "ftruncate"); if (write(file_pid, &parm, sizeof(struct deleted_pid)) != sizeof(struct deleted_pid)) err(EX_UNAVAILABLE, "First write"); if (flock(file_pid, LOCK_SH)) err(EX_UNAVAILABLE, "flock shared after exclusive"); conf = (struct deleted_pid *)mmap( NULL , sizeof(struct deleted_pid) , PROT_READ | PROT_WRITE , MAP_SHARED , file_pid , (off_t)0 ); if ((void*)conf == MAP_FAILED) err(EX_UNAVAILABLE, "mmap"); main_loop(conf, prio); break; } if (errno != EWOULDBLOCK) err(EX_UNAVAILABLE, "open "NAMEPID); file_pid = open(NAMEPID, O_RDWR | O_NONBLOCK | O_SHLOCK); if (file_pid >= 0) /* Change parameters if other deleted running */ { conf = (struct deleted_pid *)mmap( NULL , sizeof(struct deleted_pid) , PROT_READ | PROT_WRITE , MAP_SHARED , file_pid , (off_t)0 ); if ((void*)conf == MAP_FAILED) err(EX_UNAVAILABLE, "mmap"); if ( conf->id != parm.id || conf->version != parm.version ) { fprintf(stderr, NAMEPID" belongs to another program or version\n"); exit(EX_DATAERR); } conf->ttl = parm.ttl; if (conf->prio > parm.prio) { if (flag_v) fprintf(stderr, "Second deleted substitutes\n"); conf->prio = parm.prio; if (kill(conf->pid, SIGHUP) && errno != ESRCH && errno != EPERM) err(EX_UNAVAILABLE, "kill"); }else { if (flag_v) fprintf(stderr, "Second deleted corrects\n"); conf->prio = parm.prio; if (!kill(conf->pid, SIGHUP)) break; else if (errno != ESRCH && errno != EPERM) err(EX_UNAVAILABLE, "kill"); } }else if (errno != EWOULDBLOCK) err(EX_UNAVAILABLE, "open "NAMEPID); if (conf) (void)munmap(conf, sizeof(struct deleted_pid)); conf = NULL; if (file_pid >= 0) (void)flock(file_pid, LOCK_UN); file_pid = -1; if (flag_v) fprintf(stderr, "deleted: Next try \n"); (void)sleep(1); } if (conf) (void)munmap(conf, sizeof(struct deleted_pid)); if (file_pid >= 0) (void)flock(file_pid, LOCK_UN); exit(EX_OK); }