/* proxy-fu.c : A demonstration of a simple HTTP caching proxy server. * Copyright (C) 2001 Paul Stauffer * * 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 of the License, 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 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_DIR "/tmp" /* Directory to create cache files in. */ #define FILENAME_PREFIX "proxy." /* Prefix used for naming cache files. */ #define QUEUE_LEN 5 /* TCP connect queue, 5 for portability. */ #define CLIENT_TIMEOUT 20 /* Timeout for client response, seconds. */ #define SERVER_TIMEOUT 120 /* Timeout for server response, seconds. */ #define MAX_ENTRIES 100 /* Maximum items allowed in the cache. */ #define HASH_SIZE 10000 /* Scope of the hashing function used. */ #define MAX_URL_LEN 1024 /* Maximum allowed length of a URL. */ #define HOST_LEN 256 /* Maximum allowed length of a hostname. */ #define MAX_REQUEST_LEN 2048 /* Max length of an HTTP request string. */ #define BUFFER_LEN 1400 /* Size of temp buffer, based on MTU. */ #define CMD_LEN 256 /* Max length of a system() command. */ int DEBUG = 0; /* Debug flag, off by default. */ int PORT = 8088; /* Default proxy server port. */ char DIR[PATH_MAX]; /* Contains DEFAULT_DIR or alternate. */ int MAX_AGE = 5; /* Max age of a cache item, in minutes. */ extern int errno; /* Description of a single item in the proxy's cache. */ struct cache_entry { char url[MAX_URL_LEN]; /* The URL of the item. */ unsigned hash_value; /* The hash value of the item's URL. */ struct timeval last_access_time; /* Item's last access time. */ }; /* Table containing descriptions of each item in the cache. */ struct cache_entry cache_table[MAX_ENTRIES]; /* Hash table containing pointers to entries in the cache table. */ struct cache_entry* hash_table[HASH_SIZE]; /* The number of items stored in the cache. */ int cache_count = 0; int handle_connect(int); int handle_request(int, unsigned, int); int clean_cache(int); unsigned calculate_hash(char*); void reaper(int sig); void graceful_exit(int sig); int main(int argc, char* argv[]) { int option; int ssock; /* Server socket descriptor. */ int csock; /* Client connection socket descriptor. */ int alen; struct sockaddr_in saddr; /* Socket address structure for server. */ struct sockaddr_in caddr; /* Socket address structure for client. */ /* Set defined default path. */ memset(DIR, '\0', PATH_MAX); strcat(DIR, DEFAULT_DIR); opterr = 0; /* Tell getopt not to write to stderr. */ /* Get command line options. */ while ((option = getopt(argc, argv, "hdp:t:a:")) != -1) { switch(option) { case 'h': printf("usage: %s [-h] [-d] [-p port] [-t tempdir] [-a age]\n", argv[0]); printf(" -h Print this help.\n"); printf(" -d Turn on debug mode.\n"); printf(" -p Port to listen on. (default: %i)\n", PORT); printf(" -t Temp dir for cache files. (default: %s)\n", DIR); printf(" -a Max allowed age of items in the cache, in minutes. (default: %i)\n", MAX_AGE); exit(0); case 'd': DEBUG = 1; break; case 'p': PORT = atoi(optarg); break; case 't': memset(DIR, '\0', PATH_MAX); strncat(DIR, optarg, PATH_MAX-1); break; case 'a': MAX_AGE = atoi(optarg); break; case '?': printf("Unrecognized option: -%c\n", optopt); printf("Try using -h for help.\n"); exit(1); } } (DEBUG) && fprintf(stderr, "*** Debug mode on\n"); /* To be safe, zero out these items */ memset(&cache_table, '\0', sizeof(cache_table)); memset(&hash_table, '\0', sizeof(hash_table)); memset(&saddr, '\0', sizeof(saddr)); memset(&caddr, '\0', sizeof(caddr)); /* Set up signal handling. */ signal(SIGCHLD, reaper); signal(SIGHUP, graceful_exit); signal(SIGINT, graceful_exit); /* define socket address to listen on */ saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = INADDR_ANY; saddr.sin_port = htons(PORT); alen = sizeof(saddr); /* set up the server port and listen on it */ if ((ssock = socket(PF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "Error creating socket: %s\n", strerror(errno)); return(1); } if (bind(ssock, &saddr, alen) < 0) { fprintf(stderr, "Error binding to socket: %s\n", strerror(errno)); return(1); } if (listen(ssock, QUEUE_LEN) < 0) { fprintf(stderr, "Error listening on socket: %s\n", strerror(errno)); return(1); } (DEBUG) && fprintf(stderr, "*** Listening for connections on port %i\n", PORT); /* Main server loop. */ while(1) { /* Clear client socket address structure. */ memset(&caddr, '\0', sizeof(caddr)); /* Block until a client connects. */ if ((csock = accept(ssock, &caddr, &alen)) < 0) { fprintf(stderr, "Error on accept: %s\n", strerror(errno)); return(1); } (DEBUG) && fprintf(stderr, "*** Received connect from %s\n", inet_ntoa(caddr.sin_addr)); /* Call the connection handling function. */ handle_connect(csock); (DEBUG) && fprintf(stderr, "*** Closing connection to %s\n", inet_ntoa(caddr.sin_addr)); close(csock); } } /* Deal with a client connection attempt. Takes the socket descriptor of a new client connection. Returns zero on success, one on an error. */ int handle_connect(int csock) { struct timeval timeout; /* Select timeout. */ fd_set readset; /* Set of descriptors for select. */ int retval; char requestbuf[MAX_REQUEST_LEN]; /* Request received from client. */ char requested_url[MAX_URL_LEN]; /* URL extracted from the request. */ char filename[NAME_MAX]; /* Cache file name to use. */ int offset; unsigned hash; /* Hash value of the requested URL. */ int cached_flag; /* Flags cached/not cached. */ pid_t pid; /* To be safe, null-out the buffers. */ memset(requestbuf, '\0', MAX_REQUEST_LEN); memset(requested_url, '\0', MAX_URL_LEN); memset(filename, '\0', NAME_MAX); /* Set up for select, to read initial client request. */ memset(&timeout, 0, sizeof(timeout)); timeout.tv_sec = CLIENT_TIMEOUT; timeout.tv_usec = 0; FD_ZERO(&readset); FD_SET(csock, &readset); if ((retval = select(csock+1, &readset, NULL, NULL, &timeout)) < 0) { fprintf(stderr, "Error on select: %s\n", strerror(errno)); return(1); } else if (retval == 0) { fprintf(stderr, "Client not responding, disconnecting\n"); return(1); } (DEBUG) && fprintf(stderr, "*** Reading request from client\n"); /* Read the request from the client. */ if (recv(csock, requestbuf, MAX_REQUEST_LEN-1, 0) < 0) { fprintf(stderr, "Error reading from client: %s\n", strerror(errno)); return(1); } /* Verify request is an HTTP GET. */ if (strncasecmp(requestbuf, "GET HTTP://", 11) != 0) { fprintf(stderr, "Request was not an HTTP GET\n"); return(1); } /* Extract URL from request string. */ offset = 4; while((strncmp(requestbuf+offset, " ", 1) != 0) && (strncmp(requestbuf+offset, "\0", 1) != 0) && (strncmp(requestbuf+offset, "\r", 1) != 0)) offset++; if (offset > MAX_URL_LEN+3) { fprintf(stderr, "Error, URL too long\n"); return(1); } strncpy(requested_url, requestbuf+4, offset-4); (DEBUG) && fprintf(stderr, "*** Client requested: %s\n", requested_url); /* Calculate the URL's hash value. */ hash = calculate_hash(requested_url); /* Determine the current cache status of this hash. */ if (hash_table[hash] == NULL) { /* The hash is unused. */ /* Clean the cache. */ cache_count = clean_cache(-1); (DEBUG) && fprintf(stderr, "*** Filling in hash %i\n", hash); /* Create entry in the cache table. */ hash_table[hash] = &cache_table[cache_count]; strncpy(hash_table[hash]->url, requested_url, strlen(requested_url)+1); hash_table[hash]->hash_value = hash; gettimeofday(&hash_table[hash]->last_access_time, NULL); cached_flag = 0; cache_count++; } else if (strncmp(requested_url, hash_table[hash]->url, strlen(requested_url)) == 0) { (DEBUG) && fprintf(stderr, "*** Already cached at hash %i\n", hash); /* The item is the same, thus is already cached. */ gettimeofday(&hash_table[hash]->last_access_time, NULL); cached_flag = 1; } else { /* We have a collision. */ (DEBUG) && fprintf(stderr, "*** Collision detected at hash %i\n", hash); /* First, see if cleaning the cache resolves it. */ cache_count = clean_cache(-1); /* See if it's empty now. */ if (hash_table[hash] == NULL) { /* Collision was resolved. */ (DEBUG) && fprintf(stderr, "*** Collision successfully resolved via cache clean\n"); /* Fill in the cache table entry. */ hash_table[hash] = &cache_table[cache_count]; strncpy(hash_table[hash]->url, requested_url, strlen(requested_url)+1); hash_table[hash]->hash_value = hash; gettimeofday(&hash_table[hash]->last_access_time, NULL); cached_flag = 0; cache_count++; } else { /* Still there, so resolve via replacement. */ (DEBUG) && fprintf(stderr, "*** Collision unresolved by cache clean; overwriting\n"); /* Build filename. */ pid = getpid(); strncat(filename, DIR, strlen(DIR)); strcat(filename, "/"); strncat(filename, FILENAME_PREFIX, strlen(FILENAME_PREFIX)); snprintf(filename+strlen(filename), NAME_MAX-strlen(filename), "%i.%i", pid, hash); /* Remove the existing file. */ if (unlink(filename) < 0) { fprintf(stderr, "Error removing file %s: %s\n", filename, strerror(errno)); return(1); } /* Clear out the old URL. */ memset(hash_table[hash]->url, '\0', strlen(hash_table[hash]->url)); /* Save the new URL, and update the last access timestamp. */ strncpy(hash_table[hash]->url, requested_url, strlen(requested_url)+1); gettimeofday(&hash_table[hash]->last_access_time, NULL); cached_flag = 0; } } (DEBUG) && fprintf(stderr, "*** Forking child to answer request\n"); /* Fork a child process for concurrent processing of request. */ if ((pid = fork()) < 0) { fprintf(stderr, "Error on fork: %s\n", strerror(errno)); return(1); } if (pid == 0) { /* The child process. */ (DEBUG) && fprintf(stderr, "**** Child has pid %i\n", getpid()); /* Call the request handling function. */ exit(handle_request(csock, hash, cached_flag)); } /* Still in the parent process. */ (DEBUG) && fprintf(stderr, "*** Child forked; parent returning\n"); return(0); /* Go back to listening for the next request. */ } /* Respond to a client's request. Only called from within a child of the main server process. Takes the socket descriptor of an open client connection, the computed hash value of the requested item, and a flag indicating whether the item is already cached locally or not. Returns zero on success, or the hash value of the requested item on error. */ int handle_request(int csock, unsigned hash, int cached_flag) { int wsock; /* Web server socket descriptor. */ int alen; struct sockaddr_in waddr; /* Web server socket address. */ struct hostent* servdata; /* Web server hostname lookup data. */ pid_t ppid; int fd; int caching = 1; /* Are we actually going to cache? */ char filename[NAME_MAX]; /* Cache file name for this item. */ struct timeval timeout; fd_set readset; int retval; int offset; char requested_url[MAX_URL_LEN]; /* URL requested by the client. */ char server_name[HOST_LEN]; /* Web server hostname. */ char requestbuf[MAX_REQUEST_LEN]; /* HTTP request string. */ char tmpbuf[BUFFER_LEN]; /* Temp buffer for received data. */ /* To be safe, zero out these items. */ memset(&waddr, '\0', sizeof(waddr)); memset(filename, '\0', NAME_MAX); memset(requested_url, '\0', MAX_URL_LEN); memset(server_name, '\0', HOST_LEN); memset(requestbuf, '\0', MAX_REQUEST_LEN); memset(tmpbuf, '\0', BUFFER_LEN); /* Build filename. */ ppid = getppid(); strncat(filename, DIR, strlen(DIR)); strcat(filename, "/"); strncat(filename, FILENAME_PREFIX, strlen(FILENAME_PREFIX)); snprintf(filename+strlen(filename), NAME_MAX-strlen(filename), "%i.%i", ppid, hash); (DEBUG) && fprintf(stderr, "**** Built filename: %s\n", filename); if (cached_flag == 1) { /* Requested item is cached locally. */ (DEBUG) && fprintf(stderr, "**** Item is cached locally\n"); /* Open cache file. */ if ((fd = open(filename, O_RDONLY)) < 0) { fprintf(stderr, "Error opening cache file %s: %s\n", filename, strerror(errno)); send(csock, "HTTP/1.0 500 Server Error\r\n\r\n", 29, 0); close(csock); return(hash); } /* Read requested data from cache file, and send it to the client. */ while ((retval = read(fd, tmpbuf, BUFFER_LEN)) > 0) { send(csock, tmpbuf, retval, 0); memset(tmpbuf, '\0', BUFFER_LEN); } if (retval < 0) { fprintf(stderr, "Error reading from %s: %s\n", filename, strerror(errno)); send(csock, "HTTP/1.0 500 Server Error\r\n\r\n", 29, 0); close(fd); close(csock); return(hash); } (DEBUG) && fprintf(stderr, "**** Finished reading from cache\n"); close(fd); close(csock); } else { /* Requested item not cached. */ (DEBUG) && fprintf(stderr, "**** Item is not cached locally\n"); /* Get URL from cache table. */ strncpy(requested_url, hash_table[hash]->url, MAX_URL_LEN-1); (DEBUG) && fprintf(stderr, "**** Got URL: %s\n", requested_url); /* Extract destination web server hostname from URL. */ offset = 7; while((strncmp(requested_url+offset, "/", 1) != 0) && (strncmp(requested_url+offset, " ", 1) != 0)) offset++; strncpy(server_name, requested_url+7, offset-7); (DEBUG) && fprintf(stderr, "**** Server hostname is: %s\n", server_name); /* Perform lookup on web server. */ if ((servdata = gethostbyname(server_name)) == NULL) { fprintf(stderr, "Error looking up server %s: %s\n", server_name, hstrerror(h_errno)); send(csock, "HTTP/1.0 500 Server Error\r\n\r\n", 29, 0); close(csock); return(hash); } (DEBUG) && fprintf(stderr, "**** Host lookup successful\n"); /* Set up socket address for remote web server. */ memcpy(&waddr.sin_addr, servdata->h_addr, servdata->h_length); waddr.sin_family = AF_INET; waddr.sin_port = htons(80); /* HTTP port */ alen = sizeof(waddr); /* Connect to web server. */ if ((wsock = socket(PF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "Error creating socket: %s\n", strerror(errno)); send(csock, "HTTP/1.0 500 Server Error\r\n\r\n", 29, 0); close(csock); return(hash); } if ((connect(wsock, &waddr, alen)) < 0) { fprintf(stderr, "Error connecting to server %s: %s\n", server_name, strerror(errno)); send(csock, "HTTP/1.0 500 Server Error\r\n\r\n", 29, 0); close(wsock); close(csock); return(hash); } (DEBUG) && fprintf(stderr, "**** Connected to server\n"); /* Build request string. */ strcat(requestbuf, "GET "); strcat(requestbuf, requested_url); strcat(requestbuf, " HTTP/1.0\r\n\r\n"); (DEBUG) && fprintf(stderr, "**** Sending request: %s\n", requestbuf); /* Place request. */ send(wsock, requestbuf, strlen(requestbuf), 0); (DEBUG) && fprintf(stderr, "**** Waiting for response from server\n"); /* Set up for the select. */ memset(&timeout, 0, sizeof(timeout)); timeout.tv_sec = SERVER_TIMEOUT; timeout.tv_usec = 0; FD_ZERO(&readset); FD_SET(wsock, &readset); if ((retval = select(wsock+1, &readset, NULL, NULL, &timeout)) < 0) { fprintf(stderr, "Error on select: %s\n", strerror(errno)); send(csock, "HTTP/1.0 500 Server Error\r\n\r\n", 29, 0); close(csock); return(0); /* Not a permanent fatal error. */ } else if (retval == 0) { fprintf(stderr, "Select timeout expired\n"); send(csock, "HTTP/1.0 500 Server Error\r\n\r\n", 29, 0); close(csock); return(0); /* Not a permanent fatal error. */ } (DEBUG) && fprintf(stderr, "**** Creating cache file %s\n", filename); /* Create file to cache data to. */ if ((fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR)) < 0) { fprintf(stderr, "Error opening file %s for write: %s\n", filename, strerror(errno)); caching = 0; } (DEBUG) && fprintf(stderr, "**** Reading data from server"); memset(tmpbuf, '\0', BUFFER_LEN); /* Read response data from the web server. */ while ((retval = read(wsock, tmpbuf, BUFFER_LEN)) > 0) { (DEBUG) && fprintf(stderr, "."); /* Write received data to the cache file. */ if ((caching == 1) && (write(fd, tmpbuf, retval)) < 0) { fprintf(stderr, "Error writing to %s: %s\n", filename, strerror(errno)); caching == 0; } /* Send received data to the client. */ send(csock, tmpbuf, retval, 0); memset(tmpbuf, '\0', BUFFER_LEN); } if (retval < 0) { fprintf(stderr, "Error reading from server: %s\n", strerror(errno)); send(csock, tmpbuf, strlen(tmpbuf), 0); close(fd); close(wsock); close(csock); return(hash); } (DEBUG) && fprintf(stderr, "\n**** Data transferred successfully\n"); /* Mission accomplished. */ close(fd); close(wsock); close(csock); } /* If this flag is down, we failed to cache the data; return an error. */ if (caching == 0) { (DEBUG) && fprintf(stderr, "**** Data was not cached due to file I/O error\n"); return(hash); } (DEBUG) && fprintf(stderr, "**** Data now cached at %s\n", filename); return(0); } /* Clean up the proxy's cache, possibly including expired entries, the oldest entries if the cache is full, or a specific item targetted for removal. If the argument passed to the function is non-negative, the cached item with that hash value is removed, and the function returns success or failure. Otherwise, the standard housecleaning tasks are performed, and the function returns the number of items in the cache after the cleaning. */ int clean_cache(int target) { int i; pid_t pid; unsigned hash; struct timeval now; /* Current time. */ long time_to_die; /* Cut-off time for aged cache data. */ long oldest_time; /* Oldest non-expired cache item. */ int oldest_index; /* Cache table index of oldest item. */ char filename[NAME_MAX]; /* An item's cache file name. */ if (target >= 0) { /* We were asked to remove a specific item. */ hash = target; (DEBUG) && fprintf(stderr, "*** Initiating cleanup of item %i\n", hash); /* Build filename. */ memset(filename, '\0', NAME_MAX); pid = getpid(); strncat(filename, DIR, strlen(DIR)); strcat(filename, "/"); strncat(filename, FILENAME_PREFIX, strlen(FILENAME_PREFIX)); snprintf(filename+strlen(filename), NAME_MAX-strlen(filename), "%i.%i", pid, hash); /* Remove the existing file. */ if (unlink(filename) < 0) { fprintf(stderr, "Fatal: Cannot clean file %s: %s\n", filename, strerror(errno)); exit(1); } /* If targeted item is the last in the cache table, simply delete its hash table entry and decrement the cache count. */ if (hash == cache_table[cache_count-1].hash_value) { hash_table[hash] = NULL; cache_count--; (DEBUG) && fprintf(stderr, "*** Removed item %i\n", hash); return(0); } /* Move the last item in the cache table to this position. */ memcpy(hash_table[hash], &cache_table[cache_count-1], sizeof(cache_table[cache_count-1])); hash_table[cache_table[cache_count-1].hash_value] = hash_table[hash]; hash_table[hash] = NULL; cache_count--; (DEBUG) && fprintf(stderr, "*** Removed item %i\n", hash); return(0); } /* Calculate cut-off time for aged cache entries. */ gettimeofday(&now, NULL); time_to_die = now.tv_sec - (MAX_AGE * 60); oldest_time = now.tv_sec; (DEBUG) && fprintf(stderr, "*** Initiating cleanup of expired cache items [%i]\n", cache_count); /* Remove any cached items that are older than MAX_AGE. */ for (i=0; i < cache_count; i++) { if (cache_table[i].last_access_time.tv_sec <= time_to_die) { /* Build filename. */ memset(filename, '\0', NAME_MAX); pid = getpid(); hash = cache_table[i].hash_value; strncat(filename, DIR, strlen(DIR)); strcat(filename, "/"); strncat(filename, FILENAME_PREFIX, strlen(FILENAME_PREFIX)); snprintf(filename+strlen(filename), NAME_MAX-strlen(filename), "%i.%i", pid, hash); (DEBUG) && fprintf(stderr, "*** Removing item with hash %i\n", hash); /* Remove the existing file. */ if (unlink(filename) < 0) { fprintf(stderr, "Fatal: Cannot clean file %s: %s\n", filename, strerror(errno)); exit(1); } /* Remove the item's hash table entry. */ hash_table[cache_table[i].hash_value] = NULL; /* If item is the last in the cache table, simply decrement the cache count. */ if (i == cache_count-1) cache_count--; else { /* Move the last item in the cache table to this position. */ hash_table[cache_table[cache_count-1].hash_value] = &cache_table[i]; memcpy(&cache_table[i], &cache_table[cache_count-1], sizeof(cache_table[i])); cache_count--; } } /* While we're here, keep track of the oldest non-expired item. */ else if (cache_table[i].last_access_time.tv_sec <= oldest_time) { oldest_time = cache_table[i].last_access_time.tv_sec; oldest_index = i; } } /* If the cache is still too big, remove the oldest item. */ if (cache_count >= MAX_ENTRIES) { (DEBUG) && fprintf(stderr, "*** Cache still too big; removing oldest item (%i)\n", cache_table[i].hash_value); i = oldest_index; /* Build filename. */ memset(filename, '\0', NAME_MAX); pid = getpid(); hash = cache_table[i].hash_value; strncat(filename, DIR, strlen(DIR)); strcat(filename, "/"); strncat(filename, FILENAME_PREFIX, strlen(FILENAME_PREFIX)); snprintf(filename+strlen(filename), NAME_MAX-strlen(filename), "%i.%i", pid, hash); /* Remove the existing file. */ if (unlink(filename) < 0) { fprintf(stderr, "Fatal: Cannot clean file %s: %s\n", filename, strerror(errno)); exit(1); } /* Remove the item's hash table entry. */ hash_table[cache_table[i].hash_value] = NULL; /* If it's the last thing in the cache table, just decrement. */ if (i == cache_count-1) cache_count--; else { /* Move the last item in the cache table to this position. */ hash_table[cache_table[cache_count-1].hash_value] = &cache_table[i]; memcpy(&cache_table[i], &cache_table[cache_count-1], sizeof(cache_table[i])); cache_count--; } } (DEBUG) && fprintf(stderr, "*** Cache cleaned successfully [%i]\n", cache_count); /* Return new count of items in the cache. */ return(cache_count); } /* A simple hash function, taken from pg 144 of K&R (ANSI C, 2nd ed). Calculates a hash value for a given string s. */ unsigned calculate_hash(char* s) { unsigned hashval; for(hashval = 0; *s != '\0'; s++) hashval = *s + 31 * hashval; return(hashval % HASH_SIZE); } /* Wait for children that have exited, possibly doing some clean-up work for them. */ void reaper(int sig) { int status; int exit_code; /* The child process' exit code. */ /* Wait for all children that have already exited; don't block. */ while (wait3(&status, WNOHANG, NULL) > 0) { /* If child exited with an error, request to clean its item from the cache. */ if (WIFEXITED(status) == 0) { exit_code = WEXITSTATUS(status); (DEBUG) && fprintf(stderr, "*** Child process exited with error %i\n", exit_code); clean_cache(exit_code); } else (DEBUG) && fprintf(stderr, "*** Child process exited cleanly\n"); } } /* Remove all our cache files when signaled to exit. This is a bit of a hack, but it works reliably. */ void graceful_exit(int sig) { pid_t pid; char command[CMD_LEN]; /* Command string to remove cache files. */ memset(command, '\0', CMD_LEN); /* Build command string. */ pid = getpid(); strcat(command, "rm -f "); strcat(command, DIR); strcat(command, "/"); strcat(command, FILENAME_PREFIX); sprintf(command+strlen(command), "%i.*", pid); (DEBUG) && fprintf(stderr, "*** Caught signal, calling \"%s\", exiting gracefully\n", command); /* Run command via call to system(), and exit with its return code. */ exit(system(command)); }