swapspace-1.10/0000755000175000017500000000000010470513212011604 5ustar jtvjtvswapspace-1.10/doc/0000755000175000017500000000000010457074371012366 5ustar jtvjtvswapspace-1.10/doc/swapspace.80000644000175000017500000001713710356646327014462 0ustar jtvjtv.\" swapspace manpage .TH SWAPSPACE 8 "July 2005" "swapspace 1.5" "Linux System Administration" .SH NAME swapspace \- dynamically manage swap space .SH SYNOPSIS .B swapspace .RI [ options ] .SH DESCRIPTION .PP Monitor memory and swap usage and allocate or deallocate swap space as needed. This program aims to reduce or do away with the need for swapfiles. .PP .B swapspace can be built in two configurations: the full-featured one (which is now the default) or the unconfigurable version. The latter only accepts a few options, disabling all features that are probably only relevant to developers. At some point in the feature, when enough confidence exists that the various configuration parameters are either good enough for everyone or have been made unnecessary, the unconfigurable build may become the default. .PP The following options are accepted in the unconfigurable version. All of these have more extensive equivalents in the full-featured version. .TP \fB\-d\fR Quietly ignored; always enabled in unconfigurable version. .TP \fB\-e\fR Attempt to free up all allocated swap files. Returns 0 if all files were successfully erased, or 1 otherwise. .TP \fB\-h\fR Display usage information and exit. .TP \fB\-p\fR Write process identifier to \fI/var/lib/swapspace.pid\fR when starting (and delete it when shutting down). The file's name and location cannot be changed in the unconfigurable version. .TP \fB\-q\fR Quietly ignored; always enabled in unconfigurable version. .TP \fB\-v\fR Quietly ignored; always enabled in unconfigurable version. .TP \fB\-V\fR Print program version information and exit. .PP In most cases, these should be the only options of any interest for normal use of the program. All files are kept in their default locations, which were chosen in accordance with the Linux Filesystem Hierarchy Standard, and algorithmic parameters are left at defaults that have been tested to work well for a wide range of uses. .PP The full-featured configuration accepts more options, as listed below. The long-format options may also be specified, without the leading "\-\-", in a configuration file. By default, \fI/etc/swapspace.conf\fR is read on startup. .TP \fB\-a\fR \fIduration\fR, \fB\-\-cooldown\fR=\fIduration\fR If disk space runs out when allocating a swapfile, wait for \fIduration\fR iterations (of roughly one second each) before considering allocating one again; or if space doesn't run out, wait for \fIduration\fR iterations before considering deallocating uneeded swapfiles. This stabilizes the daemon's behaviour in the face of varying memory requirements. .TP \fB\-B\fR \fIp\fR, \fB\-\-buffer_elasticity\fR=\fIp\fR Consider \fIp\fR% of system-allocated I/O buffers to be available for other use. .TP \fB\-c\fR \fIfile\fR, \fB\-\-configfile\fR=\fIfile\fR Read \fIfile\fR instead of the default configuration file. .TP \fB\-C\fR \fIp\fR, \fB\-\-cache_elasticity\fR=\fIp\fR Consider \fIp\fR% of filesystem cache to be available for other use. .TP \fB\-d\fR, \fB\-\-daemon\fR Run quietly in the background. This is the normal way to run the program. .TP \fB\-e\fR, \fB\-\-erase\fR Attempt to free up all allocated swap files. Returns 0 if all files were successfully erased, or 1 otherwise. .TP \fB\-f\fR \fIp\fR, \fB\-\-freetarget\fR=\fIp\fR Aim to have \fIp\fR% of combined memory and swap space free. .TP \fB\-h\fR, \fB\-\-help\fR Display usage information and exit. .TP \fB\-l\fR \fIp\fR, \fB\-\-lower_freelimit\fR=\fIp\fR Try to keep at least \fIp\fR% of combined memory and swap space free; if less than \fIp\fR percent is available, attempt to allocate more swap space. .TP \fB\-M\fR \fIsize\fR, \fB\-\-max_swapsize\fR=\fIsize\fR Never let swapfiles become larger than \fIsize\fR bytes. You don't normally need to set this; the daemon will learn when its swap files get too big and adapt automatically. .TP \fB\-m\fR \fIsize\fR, \fB\-\-min_swapsize\fR=\fIsize\fR Never bother to allocate any swapfiles smaller than \fIsize\fR bytes. There should be no need to change this variable except for testing. .TP \fB\-p\fR [\fIfile\fR], \fB\-\-pidfile\fR[=\fIfile\fR] Write process identifier to \fIfile\fR when starting (and delete \fIfile\fR when shutting down); defaults to \fI/var/lib/swapspace.pid\fR. .TP \fB\-P\fR, \fB\-\-paranoid\fR Overwrite retired swapfiles before they are deleted, so an attacker who obtains access to the disk without going through the system's access control checks (e.g. by unplugging the computer and then rebooting from a CD) cannot retrieve the swapped data. There is no guarantee that this will work, and it will not thwart advanced forensic analysis using custom-built hardware; but it may reduce the chances of an attacker with physical access to the system obtaining passwords, credit card numbers etc. The program will attempt to free up all allocated swapfiles on termination and return a success code for this cleanup, as if the \fB\-\-erase\fR had been specified. .TP \fBCaution\fR The \fB\-\-paranoid\fR option will slow down swap file management considerably. In particular, stopping the daemon will cause it to try and deallocate (and wipe) all swapfiles it has created, and they will not be available for swapping immediately after reboot. .TP \fB\-q\fR, \fB\-\-quiet\fR Suppress informational output. .TP \fB\-s\fR \fIdir\fR, \fB\-\-swappath\fR=\fIdir\fR Create swapfiles in directory \fIdir\fR instead of default location \fI/var/lib/swapspace\fR. This location must be accessible to \fIroot\fR only; allowing anyone else to write to this directory or even read swapped data would be a \fUserious security breach\fR. .TP \fB\-u\fR \fIp\fR, \fB\-\-upper_freelimit\fR=\fIp\fR Avoid having more than \fIp\fR% of combined memory and swap space free; if this percentage is exceeded, try to deallocate swap space. .TP \fB\-v\fR, \fB\-\-verbose\fR Log debug information to system log and/or standard output, as appropriate. .TP \fB\-V\fR, \fB\-\-version\fR Print program version information and exit. .PP Numbers may be suffixed with \fIk\fR, \fIm\fR, \fIg\fR or \fIt\fR to indicate kilobytes, megabytes, gigabytes or terabytes respectively: \fI1k\fR means 1024 bytes, \fI1m\fR means 1024 kilobytes, \fI4g\fR means 4096 megabytes and so on. .PP Timings are measured in \fIiterations\fR, which should typically last about one second each. No pretense of accurate timing is made; this is not the kind of program you would run on a hard-realtime system. .PP Any messages are sent to the system daemon log; it is also printed to the standard output/error streams (as appropriate based on the urgency of each individual message) if available. .SH SIGNALS State information can be logged by sending the program the \fBSIGUSR1\fR signal (user-defined signal 1). Not all of this information will always be current; most of the information internal to \fBswapspace\fR is only updated when needed. .PP Sending the \fBSIGUSR2\fR signal will make the program free all swapfiles that are not currently needed, and abstain from allocating any more for the timespan of one cooldown period. The program will behave as if it just tried to create a swapfile but ran out of disk space. .SH FILES \& /etc/init.d/swapspace \& /etc/swapspace.conf \& /usr/sbin/swapspace \& /var/lib/swapspace/ .SH AUTHOR Written by Jeroen T. Vermeulen .SH BUGS Please report any bugs you may find on the project website at: http://thaiopensource.org/development/swapspace/ .SH COPYRIGHT Copyright \(co 2005 Software Industry Promotion Agency (SIPA), Thailand .br This is free software; see the source for copying conditions. There is no warranty whatsoever. Use entirely at your own risk. .SH "SEE ALSO" .BR kill (1), .BR mkswap (8), .BR signal (7), .BR swapon (2), .BR swapoff (2), .BR swapon (8), .BR swapoff (8) swapspace-1.10/doc/fsm.dot0000644000175000017500000000033010356646327013663 0ustar jtvjtvdigraph { steady; hungry; diet; overweight; steady -> hungry; steady -> overweight; hungry -> hungry; hungry -> steady; hungry -> diet; overweight -> steady; diet -> diet; diet -> steady; } swapspace-1.10/doc/fsm.png0000644000175000017500000000456710457074371013675 0ustar jtvjtvPNG  IHDR!PLTE___???<tRNS@f IDATxKw8NB(oraH'YMZ{iN_9%[WEkDzu?J^1ro- DE( "(N*OVԠ ǩ݆.UvKjSXY ,%zk2kh (oB/cQDހ ob XY 4jJShrtI($Py M@-JRT G2#|=[+[%C)(7J7_$E.rH zw>3iC |u" @ KQ:}bCqEe)(Ȧ9KP̨+C2\R2{qJ2Y?`Rj8RL_ zaya g sR;foR >x:(2Yrٮ>Dal}t6 AS7h&W K"eV]µH, +Ыº=FLś8 l5#,`/md> gĔQV ǖpQPYZ@mD(TBٌqQw E`GYZ@dᡠax,Qt%KX,Bq@ua:FAt db]QUakpXGaLEG\$e]'Z )C\뜠Q]( rrQNNIx u8[8Ծt}x4JkampoB(TxzEmH'tM7dF["oD~*Wj,j~Y@ٴ<|bS50VAkDiTUZD(:Xzng5LdX'Pz5Mu[~i V3":?]( R0V\@2H-ӵJBIRz9U&$MT` [ .4G`_أ(r`K$EQ$GJ H"i.ZH;Lѿ@abY*( >eo{Q& aQ(%fRRV,%U/;@+"r>p=Yiǿ栌gvp8NBug`_PPn`Nn[-R]aQPB0RGICu % -DZYTPB0o:.xG_`Z`跈Y䡺LG=,T&E_ El0-b(2 e"aXqlyzR}췈ĠQE9cТ8YW `Q],+ĀEy({S)3D>bܢ@[ؠG]IENDB`swapspace-1.10/doc/Makefile0000644000175000017500000000031210356646327014026 0ustar jtvjtv#! /bin/make all: fsm.png # Generate fsm.png from fsm.dot using the circo tool from the graphviz package, # if available fsm.png: fsm.dot if (which circo >/dev/null); then circo -Tpng $< -o $@ ; fi swapspace-1.10/doc/logo.svg0000644000175000017500000001372010356646327014056 0ustar jtvjtv image/svg+xml swap space swapspace-1.10/src/0000755000175000017500000000000010470513212012373 5ustar jtvjtvswapspace-1.10/src/support.c0000644000175000017500000000301610457073744014273 0ustar jtvjtv/* This file is part of Swapspace. Copyright (C) 2005,2006, Software Industry Industry Promotion Agency (SIPA) Written by Jeroen T. Vermeulen . Swapspace 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. Swapspace 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 swapspace; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "env.h" #include #include #include #include "main.h" #include "support.h" #ifndef HAVE_SWAPON /// Replacement function for system function. Clobbers localbuf. int swapon(const char path[], int flags) { return runcommand("/sbin/swapon", path); } #endif #ifndef HAVE_SWAPOFF /// Replacement function for system function. Clobbers localbuf. int swapoff(const char path[]) { return runcommand("/sbin/swapoff", path); } #endif int runcommand(const char cmd[], const char arg[]) { const size_t bufsz = sizeof(localbuf); if (unlikely(snprintf(localbuf, bufsz, "%s '%s'", cmd, arg) >= bufsz)) { errno = E2BIG; return -1; } return system(localbuf); } swapspace-1.10/src/swaps.h0000644000175000017500000000531610457073743013725 0ustar jtvjtv/* This file is part of Swapspace. Copyright (C) 2005,2006, Software Industry Industry Promotion Agency (SIPA) Written by Jeroen T. Vermeulen . Swapspace 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. Swapspace 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 swapspace; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #ifndef SWAPSPACE_SWAPS_H #define SWAPSPACE_SWAPS_H #include "memory.h" /// Dump statistics to stdout void dump_stats(void); /// Read /proc/swaps and set up swapfiles[] accordingly. Clobbers localbuf. /** * @return whether this succeeded */ bool read_proc_swaps(void); /// Was /proc/swaps in the expected format? /** This assumes that read_proc_swaps() has been called successfully at least * once. If no swaps were active during any of those calls, /proc/swaps may * have been completely empty so we cannot verify that the header line conforms * to the expected format. */ bool proc_swaps_parsed(void); /// Scan for pre-existing swapfiles in the current directory, and activate them /** Read /proc/swaps once before doing this, to ensure that we don't attempt to * re-enable any swapfiles that are already active (which shouldn't normally be * possible, but abnormal situations do occur sometimes). * * @return true unless a fatal error occurred */ bool activate_old_swaps(void); /// Create a new swapfile. Clobbers localbuf. /** * @param size number of bytes to allocate * @return success */ bool alloc_swapfile(memsize_t size); /// Free swap space /** * @param maxsize maximum amount of memory that may be freed */ void free_swapfile(memsize_t maxsize); /// Attempt to get rid of all our swapfiles right now bool retire_all(void); /// Total space on swap directory's filesystem memsize_t swapfs_size(void); /// Amount of free space on swap directory's filesystem memsize_t swapfs_free(void); #ifndef NO_CONFIG /// Try to guard against attacker getting unguarded access to the disk? extern bool paranoid; char *set_min_swapsize(long long size); char *set_max_swapsize(long long size); char *set_swappath(long long dummy); char *set_paranoid(long long dummy); /// Verify configuration for swaps module; cd into swappath bool swaps_check_config(void); #endif bool to_swapdir(void); #endif swapspace-1.10/src/memory.c0000644000175000017500000003462010461300711014052 0ustar jtvjtv/* This file is part of Swapspace. Copyright (C) 2005,2006, Software Industry Industry Promotion Agency (SIPA) Written by Jeroen T. Vermeulen . Swapspace 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. Swapspace 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 swapspace; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "env.h" #include #include #include #include #include #include #include "log.h" #include "memory.h" #include "opts.h" #include "support.h" /// Lower bound to percentage of memory/swap space kept available static int lower_freelimit=20; /// Upper bound to percentage of memory/swap space kept available static int upper_freelimit=60; /// Configuration item: target percentage of available space after adding swap static int freetarget=30; /// Configuration item: what percentage of buffer space do we consider "free"? static int buffer_elasticity=30; // TODO: Make this adaptive based on actual cache usage, to avoid thrashing? // TODO: Any way of detecting actual cache flexibility or minimum size? /// Configuration item: what percentage of cache space do we consider "free"? static int cache_elasticity=80; #ifndef NO_CONFIG char *set_freetarget(long long pct) { freetarget = (int)pct; return NULL; } char *set_lower_freelimit(long long pct) { lower_freelimit = (int)pct; return NULL; } char *set_upper_freelimit(long long pct) { upper_freelimit = (int)pct; return NULL; } char *set_buffer_elasticity(long long pct) { buffer_elasticity = (int)pct; return NULL; } char *set_cache_elasticity(long long pct) { cache_elasticity = (int)pct; return NULL; } bool memory_check_config(void) { CHECK_CONFIG_ERR(lower_freelimit > upper_freelimit); CHECK_CONFIG_ERR(freetarget < lower_freelimit); CHECK_CONFIG_ERR(freetarget > upper_freelimit); return true; } #endif struct meminfo_item { char entry[200]; memsize_t value; }; /// Read and parse next line from /proc/meminfo. Clobbers localbuf. /** Attempt to read a line from /proc/meminfo into result. If an error occurs, * result will hold errno (which may be zero, e.g. in the case of a parse error) * and a generic error string. If end-of-file has been reached or a meaningless * line has been read, result will contain zero and the empty string. * * @return success (not EOF and not error) */ static bool read_meminfo_item(FILE *fp, struct meminfo_item *result) { result->entry[0] = '\0'; result->value = 0; if (unlikely(!fgets(localbuf, sizeof(localbuf), fp))) { if (unlikely(ferror(fp))) result->value = (memsize_t)errno; if (unlikely(result->value)) strcpy(result->entry, "Error reading /proc/meminfo"); return false; } char fact[20]; const int x = sscanf(localbuf, "%[^:]: %lld %19s", result->entry, &result->value, fact); if (unlikely(x == 2)) { // Since Linux 2.6.18-mm4 or so, /proc/meminfo may contain lines without a // unit or "factor" attached. fact[0] = '\0'; } else if (unlikely(x < 2)) { result->value = 0; // In Linux 2.4, /proc/meminfo starts with a header line with leading // whitespace. We accept that as a special case. if (likely(isspace(localbuf[0]))) { result->entry[0] = '\0'; return true; } // All other parse failures are fatal. snprintf(result->entry, sizeof(result->entry), "Parse error in /proc/meminfo: '%150s'", localbuf); return false; } // Another special case for Linux 2.4: two extra lines summarize the various // pools of physical memory and swap space. We'd fail to parse these lines // (which we're not interested in anyway) because there's a number where we // expect a scale factor. Detect this and skip those lines. if (isdigit(fact[0])) { result->entry[0] = '\0'; result->value = 0; return true; } // Parse scale factor at end of line. I've never seen it be anything other // than "kB", but who knows what can happen... memsize_t scale = 0; const size_t factlen = strlen(fact); switch (factlen) { case 0: scale = 1; break; case 3: if (fact[1] != 'i') break; // FALL-THROUGH case 2: if (fact[factlen-1] != 'B') break; // FALL-THROUGH case 1: switch (tolower(fact[0])) { case 'b': scale = 1; break; case 'k': scale = KILO; break; case 'm': scale = MEGA; break; case 'g': scale = GIGA; break; } break; } if (unlikely(!scale)) { result->value = 0; snprintf(result->entry, sizeof(result->entry), "Unknown scale factor in /proc/meminfo: %s", fact); return false; } result->value *= scale; return true; } struct memstate { memsize_t MemTotal, MemFree, Buffers, Cached, Dirty, Writeback, SwapCached, SwapTotal, SwapFree; }; void imbibe_meminfo_entry(const struct meminfo_item *inf, struct memstate *st) { switch (inf->entry[0]) { case 'B': if (strcmp(inf->entry, "Buffers")==0) st->Buffers = inf->value; break; case 'C': if (strcmp(inf->entry, "Cached")==0) st->Cached = inf->value; break; case 'D': if (strcmp(inf->entry, "Dirty")==0) st->Dirty = inf->value; break; case 'M': if (strncmp(inf->entry,"Mem",3) == 0) { if (strcmp(inf->entry+3, "Total")==0) st->MemTotal = inf->value; else if (strcmp(inf->entry+3, "Free")==0) st->MemFree = inf->value; } break; case 'S': if (strncmp(inf->entry,"Swap",4) == 0) { if (strcmp(inf->entry+4, "Total")==0) st->SwapTotal = inf->value; else if (strcmp(inf->entry+4, "Free")==0) st->SwapFree = inf->value; else if (strcmp(inf->entry+4, "Cached")==0) st->SwapCached = inf->value; } break; case 'W': if (strcmp(inf->entry,"Writeback")==0) st->Writeback = inf->value; break; } } static bool read_proc_meminfo(struct memstate *s) { FILE *fp = fopen("/proc/meminfo", "r"); if (unlikely(!fp)) { log_perr(LOG_ERR, "Could not open /proc/meminfo for reading", errno); return false; } struct meminfo_item inf; while (read_meminfo_item(fp, &inf)) imbibe_meminfo_entry(&inf, s); fclose(fp); if (unlikely(inf.entry[0])) { log_perr(LOG_ERR, inf.entry, inf.value); return false; } if (unlikely(!s->MemTotal)) { logm(LOG_ERR, "No memory detected! Perhaps /proc/meminfo is in an unexpected format"); return false; } if (unlikely(s->MemTotal < s->MemFree+s->Buffers+s->Cached+s->SwapCached)) { logm(LOG_ERR, "Memory statistics read from /proc/meminfo don't add up"); return false; } return true; } bool check_memory_status(void) { const memsize_t init_req = memory_target(); if (unlikely(init_req == MEMSIZE_ERROR)) return false; #ifndef NO_CONFIG if (init_req && !quiet) { printf("Initial memory status: "); if (init_req > 0) logm(LOG_INFO, "would prefer %lld extra bytes", init_req); else logm(LOG_INFO, "%lld bytes to spare", -init_req); } #endif return true; } /// How much buffer space can we expect the system to free up? static inline memsize_t buffers_free(const struct memstate *st) { return (st->Buffers/100) * buffer_elasticity; } /// How much cache space can we expect the system to free up? static inline memsize_t cache_free(const struct memstate *st) { const memsize_t cache = st->Cached - (st->Dirty + st->Writeback); return (cache > 0) ? (cache/100)*cache_elasticity : 0; } static inline memsize_t space_free(const struct memstate *st) { /* Estimating "free" memory is not only hard, but subjective as well. Ideally * we'd want some measure of "pressure"--which we don't seem to have. * * The big question is what to do about Cached. This is where most bytes tend * to go, and it grows unless and until memory requirements get really, really * urgent. In other words, it doesn't behave as if it's something very * flexible that we could treat as essentially "free" space. * * What if we subtracted Dirty and Writeback from this number? Writeback * appears to be a very rare state, but Dirty can get quite large when the * system is pressed for memory. Would the remainder be more or less * flexible? For some reason I get more flexibility now, even without having * a noticeable number of dirty pages, on a 2.6.10 kernel than I observed in * the past. Has something changed here? * * I managed to push back Cached all the way to about half a percent of total * physical memory before the system started to thrash. And that was with a * large number of applications loaded and large amounts of memory allocated * and committed by applications. * * Under these circumstances, best thing to do is subtract Dirty/Writeback * from Cached and "discount" the remainder against a new parameter ("Cache * Pressure") which says how much of the non-dirty part of Cache should be * considered in-use. */ return st->MemFree + st->SwapFree + st->SwapCached + buffers_free(st) + cache_free(st); } static inline memsize_t space_total(const struct memstate *st) { return st->MemTotal + st->SwapTotal; } static inline int pct_free(const struct memstate *st) { // Phrased oddly to avoid integer overflows return space_free(st)/(space_total(st)/100); } static memsize_t ideal_swapsize(memsize_t totalspace, memsize_t freespace) { /* This is phrased rather oddly, and a fairly high rounding error is * introduced. * * The reason is to avoid overflows in the computation: we effectively compute * at a granularity of 100 bytes to avoid overflows, then multiply by 100 * again to get back to an approximation of our original number. * * This might conceivably be a problem if freetarget is ridiculously close to * either freespace threshold. * * We try to find the ideal allocation size x>0 such that if we add x free * bytes (i.e. we add it to both freespace and totalspace), we end up at * exactly our freetarget. In mathematical terms, i.e. without caring about * rounding or overflows: * * (freespace+x)/(totalspace+x) = freetarget/100 <=> * freespace+x = freetarget*(totalspace+x)/100 <=> * 100*(freespace+x)/freetarget = totalspace+x <=> * 100*freespace/freetarget+100*x/freetarget = totalspace+x <=> * 100*freespace/freetarget-totalspace = x - 100*x/freetarget <=> * x-100*x/freetarget = 100*freespace/freetarget - totalspace <=> * x*(1-100/freetarget) = 100*freespace/freetarget - totalspace <=> * * x = (100*freespace/freetarget - totalspace) / (1 - 100/freetarget) * = (100*freespace - freetarget*totalspace) / (freetarget - 100) * * This latter expression we convert to an overflow-free form by dividing both * freespace and totalspace by some constant p, and multiplying by this same p * afterwards: * * x/p = (100*freespace/p - freetarget*totalspace/p) / (freetarget-100) * * The natural choice for p is 100, since it gives up the exact minimum * precision while ensuring that both 100*freespace/p and (given that * freetarget<100) freetarget*totalspace/p remain within the range of * representable numbers, assuming that freespace and totalspace were * themselves representable to begin with. * * For p==100 we get: * * x/100 = (100*freespace/100-freetarget*totalspace/100) / (freetarget-100) * = (freespace - freetarget*totalspace/100) / (freetarget-100) * * Note that one of the two divisions by p has disappeared. To recover as * much of that lost precision of the remaining division-by-p, we round to the * nearest integer, not downwards: */ return 100 * ((freespace-freetarget*((totalspace+50)/100))/(freetarget-100)); } memsize_t memory_target(void) { /* Determining how much memory we need is a pretty difficult job. One reason * is that if no swap space is available under Linux 2.6, lots of pages stay * marked as "cached." So when /proc/meminfo says that a large portion of * physical memory is in use as cache, that doesn't mean that memory can be * made available for other uses! */ assert(upper_freelimit > lower_freelimit); struct memstate st = { 0, 0, 0, 0, 0, 0, 0, 0 }; if (unlikely(!read_proc_meminfo(&st))) return MEMSIZE_ERROR; const int freepct = pct_free(&st); memsize_t request = 0; // Policy: once we hit either freelimit, steer for "freetarget"% free space // TODO: Use /proc/sys/vm/min_free_kbytes when no swap yet allocated? if (freepct < lower_freelimit || freepct > upper_freelimit) request = ideal_swapsize(space_total(&st), space_free(&st)); return request; } memsize_t minimal_swapfile(void) { struct memstate st = { 0, 0, 0, 0, 0, 0, 0, 0 }; if (unlikely(!read_proc_meminfo(&st))) return MEMSIZE_ERROR; const memsize_t total = space_total(&st); // Compute desired swapfile size if we're exactly at lower_freelimit return ideal_swapsize(total, (total/100)*lower_freelimit); } static void dump_memline(const char category[], long long total, long long free, long long cached) { logm(LOG_INFO, "%s: %lld total, %lld free (%lld used); %lld cached", category, total, free, (total - free), cached); } void dump_memory(void) { struct memstate st = { 0, 0, 0, 0, 0, 0, 0, 0 }; if (unlikely(!read_proc_meminfo(&st))) return; check_memory_status(); dump_memline("core", st.MemTotal, st.MemFree, st.Cached); dump_memline("swap", st.SwapTotal, st.SwapFree, st.SwapCached); dump_memline("total", space_total(&st), st.MemFree + st.SwapFree, st.Cached + st.SwapCached); logm(LOG_INFO, "bufs: %lld, dirty: %lld, writeback: %lld", st.Buffers, st.Dirty, st.Writeback); const int pf = pct_free(&st); logm(LOG_INFO, "estimate free: %lld cache, %lld bufs, %lld total (%d%%)", cache_free(&st), buffers_free(&st), space_free(&st), pf); logm(LOG_INFO, "thresholds: %d%% < %d%% < %d%%", lower_freelimit, pf, upper_freelimit); } swapspace-1.10/src/config.h0000644000175000017500000000201710457073744014031 0ustar jtvjtv/* This file is part of Swapspace. Copyright (C) 2005,2006 Software Industry Industry Promotion Agency (SIPA) Written by Jeroen T. Vermeulen . Swapspace 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. Swapspace 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 swapspace; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #ifndef SWAPSPACE_CONFIG_H #define SWAPSPACE_CONFIG_H // Define if system has swapon(const char *, int) #define HAVE_SWAPON // Define if system has swapoff(const char *) #define HAVE_SWAPOFF #endif swapspace-1.10/src/support.h0000644000175000017500000000360010457074756014303 0ustar jtvjtv/* This file is part of Swapspace. Copyright (C) 2005,2006, Software Industry Industry Promotion Agency (SIPA) Written by Jeroen T. Vermeulen . Swapspace 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. Swapspace 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 swapspace; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #ifndef SWAPSPACE_SUPPORT_H #define SWAPSPACE_SUPPORT_H #include #include #include #include "config.h" #ifndef HAVE_SWAPON int swapon(const char path[], int flags); #endif #ifndef HAVE_SWAPOFF int swapoff(const char path[]); #endif /// Run given shell command with given argument. Clobbers localbuf. /** A convenient front-end for system(), this composes a command line consisting * of a command followed by a single argument (which will be quoted). * @return -1 on failure to execute (check errno), or the command's return * value. */ int runcommand(const char cmd[], const char arg[]); #define INTERNAL_STRINGIFY(VALUE) #VALUE #define STRINGIFY(VALUE) INTERNAL_STRINGIFY(VALUE) /// PATH_MAX value as a string constant #define PMS STRINGIFY(PATH_MAX) #ifndef likely /// As in Linux kernel: evaluate condition, which is likely to be true #define likely(x) __builtin_expect(x,1) #endif #ifndef unlikely /// As in Linux kernel: evaluate condition, which is likely to be false #define unlikely(x) __builtin_expect(x,0) #endif #endif swapspace-1.10/src/memory.h0000644000175000017500000000537510457073744014106 0ustar jtvjtv/* This file is part of Swapspace. Copyright (C) 2005,2006, Software Industry Industry Promotion Agency (SIPA) Written by Jeroen T. Vermeulen . Swapspace 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. Swapspace 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 swapspace; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #ifndef SWAPSPACE_MEMORY_H #define SWAPSPACE_MEMORY_H #include "main.h" /// Type used for swap sizes and such. Must be signed. /** All internal accounting of file and memory sizes is done entirely in bytes, * which is why we need a very wide type for these values. This way we avoid * nasty double-conversion or missing-conversion bugs; incorrect results due to * unexpected relationships between page size, disk block size, and kilobytes; * and most hidden overflow cases. Using an unnaturally wide variable type is a * small price to pay. */ typedef long long memsize_t; /// Singular value for memsize_t, analogous to the null pointer #define MEMSIZE_ERROR LLONG_MIN /// Check if we can access memory status etc. Clobbers localbuf. bool check_memory_status(void); /// Recommend change in available swap space. Clobbers localbuf. /** This is where policy on the total available memory size is formulated. * @return recommended increase in swap size (negative for a recommended * decrease) */ memsize_t memory_target(void); /// What is the minimum swapfile size we can expect to allocate? /** This has nothing to do with the minimal permissible swapfile size; it * depends on system memory size and the threshold settings currently in effect. * * The value is computed by assuming we hit the lower_freelimit without falling * below it, and computing the size of the swapfile we'd need in that case. * We can use this to determine whether the swapdir is in a usefully-sized * partition, and to warn if there is not enough space free on that partition * for swapspace to do useful work. */ memsize_t minimal_swapfile(void); /// Log memory statistics void dump_memory(void); #ifndef NO_CONFIG char *set_lower_freelimit(long long pct); char *set_upper_freelimit(long long pct); char *set_freetarget(long long pct); char *set_buffer_elasticity(long long pct); char *set_cache_elasticity(long long pct); bool memory_check_config(void); #endif #endif swapspace-1.10/src/hog.c0000644000175000017500000000441410457073744013337 0ustar jtvjtv/* This file is part of Swapspace. Copyright (C) 2005,2006, Software Industry Industry Promotion Agency (SIPA) Written by Jeroen T. Vermeulen . Swapspace 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. Swapspace 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 swapspace; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include // Memory hog program. Allocates amount requested on the command line (in // megabytes) and keeps it, or if no argument is given, reads standard input and // tries to follow the amounts requested there. static void usage(const char name[]) { fprintf(stderr, "Usage: %s [megabytes]\n", name); } void gimme(unsigned long megs) { static char *ptr = NULL; static size_t oldsize = 0; const size_t bytes = megs*1024*1024; printf("Allocating %lu MB: ", megs); fflush(NULL); ptr = realloc(ptr, bytes); if (!ptr) { printf("Failed!\n"); exit(EXIT_FAILURE); } if (bytes > oldsize) memset(ptr+oldsize, '#', bytes-oldsize); oldsize = bytes; printf("0x%lx bytes\n", (unsigned long)bytes); } static int loop(void) { char buf[1000]; while (fgets(buf, sizeof(buf), stdin)) { const size_t target = strtoul(buf, NULL, 0); if (target) gimme(target); } return EXIT_SUCCESS; } int main(int argc, char *argv[]) { unsigned long target; switch (argc) { case 1: puts("Just keep entering how much memory you'd like, in megabytes:"); return loop(); case 2: target = strtoul(argv[1], NULL, 0); if (!target) { usage(argv[0]); return EXIT_FAILURE; } gimme(target); sleep(3600); puts("Timing out"); return EXIT_SUCCESS; default: usage(argv[0]); return EXIT_FAILURE; } return EXIT_SUCCESS; } swapspace-1.10/src/state.c0000644000175000017500000001222410457073744013700 0ustar jtvjtv/* This file is part of Swapspace. Copyright (C) 2005,2006, Software Industry Industry Promotion Agency (SIPA) Written by Jeroen T. Vermeulen . Swapspace 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. Swapspace 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 swapspace; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "env.h" #include #include "log.h" #include "main.h" #include "memory.h" #include "state.h" #include "support.h" #include "swaps.h" /* The allocation/deallocation algorithm is driven by a state machine. */ /* TODO: Adaptive cooldown_time? * Extend cooldown time if a swapfile is allocated shortly after deallocation in * "overfed" state (say, within another timeout period), or if one is * deallocated shortly (say, at most two timeout periods) after having been * allocated. * Reduce cooldown time if "hungry" and/or "overfed" states consistently timeout * too soon (say, X times in the first half of the timeout period but never in * the second) */ /// Minimum cooldown time before returning to "steady state" allocation policy static time_t cooldown_time = 600; /// Countdown timer for return to "steady state" static time_t timer = 0; static inline void timer_reset(void) { timer = cooldown_time; } static inline void timer_tick(void) { --timer; } static inline bool timer_timeout(void) { return timer <= 0; } #ifndef NO_CONFIG char *set_cooldown(long long duration) { cooldown_time = (time_t)duration; timer_reset(); return NULL; } #endif /// State machine describing transitions in allocation policy enum State { st_diet, // Ran into disk limit; don't allocate st_hungry, // Want more, or at least don't consider deallocation st_steady, // Entirely neutral st_overfed // Waiting to see if it's okay to deallocate }; static const char *Statenames[] = { "diet", "hungry", "steady", "overfed" }; static enum State the_state = st_hungry; static bool need_diet = false; void request_diet(void) { need_diet = true; } static void state_to(enum State s) { #ifndef NO_CONFIG if (verbose) logm(LOG_DEBUG,"%s -> %s",Statenames[the_state],Statenames[s]); #endif the_state = s; timer_reset(); } void handle_requirements(void) { if (unlikely(need_diet)) { need_diet = false; state_to(st_diet); return; } const memsize_t reqbytes = memory_target(); timer_tick(); if (unlikely(reqbytes > 0) && likely(the_state != st_diet)) { /* In any state except "diet," where allocation is inhibited, a shortage of * memory means we forget what state we're in and jump straight to "hungry" * mode, allocating a new swapfile along the way. If the allocation fails, * we bail out into "diet" mode next time, on alloc_swapfile()'s request. */ if (likely(alloc_swapfile(reqbytes))) state_to(st_hungry); } else if (unlikely(timer_timeout())) { /* All states except "steady" are designed to time out eventually; in every * case that leads back to "steady" so we can make it a general rule that * timeout leads to "steady." * The only other action that accompanies timeout is when timing out of the * "overfed" state, which is where we normally deallocate. */ if (the_state == st_overfed) free_swapfile(-reqbytes); state_to(st_steady); } else switch (the_state) { case st_diet: /* If we overallocated and now find ourselves with more swap space than we * think we need, deallocate it right away. Don't leave "diet" state just * yet in that case, however, or we may invite thrashing. */ if (unlikely(reqbytes < 0)) free_swapfile(-reqbytes); break; case st_hungry: /* The "hungry" state can either time out, or allocate more swap space and * loop back to itself, resetting the timer (or if allocation fails, bail * out to "diet"). In other words, all actions for this state have been * handled by the general cases above. */ break; case st_steady: /* If we have more swap space than we need, go to "overfed" state which may * eventually lead to deallocation. */ if (unlikely(reqbytes < 0)) state_to(st_overfed); break; case st_overfed: /* There are two ways out of "overfed" state: either we find that we no * longer have more memory than we need, in which case we default back to * steady state; or we time out as per the general case described above, * having had excess swap space for an entire timer period and therefore * deallocating swap space. */ if (unlikely(reqbytes >= 0)) state_to(st_steady); break; } } void dump_state(void) { logm(LOG_INFO, "state: %s", Statenames[the_state]); if (timer > 0) logm(LOG_INFO, "timer: %ld", (long)timer); } swapspace-1.10/src/log.c0000644000175000017500000000531510457073744013344 0ustar jtvjtv/* This file is part of Swapspace. Copyright (C) 2005,2006, Software Industry Industry Promotion Agency (SIPA) Written by Jeroen T. Vermeulen . Swapspace 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. Swapspace 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 swapspace; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #include "log.h" static bool logging = false; #ifndef LOG_PERROR /// If available (and nonzero), causes log output to be echoed to stderr #define LOG_PERROR 0 #endif void log_start(const char myname[]) { if (!logging) { openlog(myname, LOG_PERROR, LOG_DAEMON); logging = true; } } /// Stop logging, e.g. because storage containing log name goes out of scope void log_close() { closelog(); logging = false; } static const char *prefix(int priority) { switch (priority) { case LOG_ERR: return "Error: "; case LOG_WARNING: return "Warning: "; case LOG_NOTICE: return "Notice: "; } return ""; } static FILE *stream(int priority) { switch (priority) { case LOG_ERR: case LOG_WARNING: return stderr; } return stdout; } static void vlog(int priority, const char fmt[], va_list ap) { if (logging) { vsyslog(priority, fmt, ap); } else { FILE *const out = stream(priority); fprintf(out, "%s", prefix(priority)); vfprintf(out, fmt, ap); fprintf(out, "\n"); } } void logm(int priority, const char fmt[], ...) { va_list ap; va_start(ap, fmt); vlog(priority, fmt, ap); va_end(ap); } void log_perr(int priority, const char msg[], int fault_errno) { if (!fault_errno) logm(priority, "%s", msg); else logm(priority, "%s: %s", msg, strerror(fault_errno)); } void log_perr_str(int priority, const char msg[], const char arg[], int err) { if (!err) logm(priority, "%s '%s'", msg, arg); else logm(priority, "%s '%s': %s", msg, arg, strerror(err)); } #ifndef NO_CONFIG void log_discrep(int priority, const char msg[], int swapfile, memsize_t expected, memsize_t found) { logm(priority, "Discrepancy in swapfile %d: %s (%lld bytes vs. %lld)", swapfile, msg, (long long)expected, (long long)found); } #endif swapspace-1.10/src/.cvsignore0000644000175000017500000000001610356646327014411 0ustar jtvjtvswapspace hog swapspace-1.10/src/main.c0000644000175000017500000004002410457075046013500 0ustar jtvjtv/* This file is part of Swapspace. Copyright (C) 2005,2006, Software Industry Industry Promotion Agency (SIPA) Written by Jeroen T. Vermeulen . Swapspace 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. Swapspace 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 swapspace; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "env.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "log.h" #include "main.h" #include "memory.h" #include "opts.h" #include "state.h" #include "support.h" #include "swaps.h" char localbuf[16384]; time_t runclock = 0; /// Have we received a kill/HUP/power signal? static volatile bool stop = false; static char pidfile[PATH_MAX] = "/var/run/swapspace.pid"; static bool make_pidfile = false; static bool have_pidfile = false; static int pidfd = -1; char *set_pidfile(long long dummy) { make_pidfile = true; return pidfile; } #ifndef NO_CONFIG static bool godaemon = false; char *set_daemon(long long dummy) { godaemon = true; return NULL; } bool quiet = false; bool verbose = false; char *set_quiet(long long dummy) { quiet = true; return NULL; } char *set_verbose(long long dummy) { verbose = true; return NULL; } bool main_check_config(void) { CHECK_CONFIG_ERR(quiet & verbose); return true; } #else #define godaemon true #endif static bool erase = false; char *set_erase(long long dummy) { erase = true; return NULL; } static void rmpidfile(void) { if (have_pidfile) { unlink(pidfile); have_pidfile = false; } } /// Write our process identifier to pid file. Clobbers localbuf. static bool writepid(pid_t pid) { snprintf(localbuf, sizeof(localbuf), "%d\n", pid); const size_t len = strlen(localbuf); if (unlikely(write(pidfd, localbuf, len) < len)) { log_perr_str(LOG_ERR, "Could not write pidfile", pidfile, errno); return false; } return true; } /// Create pidfile, if requested /** This is implemented separately from finishpidfile() to allow errors to be * reported to stderr before we fork off a daemon process. * * @return Success (which is trivially achieved if no pidfile is requested) */ static bool startpidfile(void) { assert(pidfd == -1); if (!make_pidfile) return true; pidfd = open(pidfile, O_WRONLY|O_CREAT|O_EXCL); if (unlikely(pidfd == -1)) { if (errno == EEXIST) logm(LOG_ERR, "Daemon already running, or leftover pidfile: '%s'", pidfile); else log_perr_str(LOG_ERR, "Could not create pidfile", pidfile, errno); return false; } have_pidfile = true; // Write temporary process id to pidfile; have to do this again if we fork() return writepid(getpid()); } /// Signal handler that requests clean exit /** Unix IPC sucks. * * It's all very powerful and flexible, but the primitives are too many and * vastly overweight. One-size-fits-all can be very useful, but in the case of * signals it also causes a lot of problems. We can be interrupted at any * moment, so we really can't assume anything about the current state of the * program--yet the compiler has to optimize with one hand tied behind its back * because it must respect sequence points no matter when the signal comes in. * Our programs could be running much faster than they are, if only the compiler * weren't obliged to assume that signal handlers may interact with the program * and keep even non-volatile variables synchronized for this purpose. * * This helped kill the Alpha architecture, by the way. The Alpha AXP was a * beautiful, clean 64-bit RISC processor family designed by DEC (the Digital * Equipment Corporation, later acquired by Compaq, which in turn was later * acquired by HP) that was almost endian-neutral because it simply didn't have * single-byte or 16-bit memory accesses. Everything went to and from memory in * larger chunks, so ECC checking could be moved off the motherboard and into * the processor itself; loops over text and such could be vectorized to read * and write entire 64-bit registers, i.e. 8 bytes at a time. Both of these * ideas were expected to make a huge difference, since memory traffic is the * most stringent system performance bottleneck today. But it didn't work out, * and the 8-bit and 16-bit load/store instructions were eventually added to the * architecture, increasing performance on the order of 20%. * * One reason for the change was that device drivers sometimes needed 8-bit or * 16-bit memory-mapped hardware accesses. Windows NT introduced wrapper macros * for such accesses that enabled an MMU-based workaround on Alpha systems. But * since these macros were no-ops on Intel x86-compatible processors, device * driver writers often failed to use them. If Linux had hit the big time a bit * earlier, DEC * * The other reason why these instructions were added were signal handlers. The * idea of vectorizing common loops was great; it was a precursor to today's * generation of vector instruction set extensions (first SPARC's VIS, later * Intel's MMX and PowerPC's Altivec/VMX). It got such great performance that * compiler engineers at Intel wouldn't stop programming until they could do the * same trick. Alpha was so fast at the time that it outran the top-of-the-line * Pentium Pro on the still-popular SPECint92 benchmarks... even while emulating * the x86 version of the code! Intel finally thought they nailed the problem, * until Motorola engineers discovered a bug in the generated code: a vital loop * was vectorized so it did multiple iterations at a time, but it no longer * looped. The affected benchmark was giving the correct answers through sheer * coincidence. It turned out this mistake had inflated their compound * performance rating for the entire SPECint suite by an embarassing 15%. * * Unfortunately, DEC's compiler engineers found that they could not use their * aggressive optimizations in real life. The compiler never knows when a * signal is going to arrive, and yet it must synchronize the in-memory state of * most variables when it happens. Which means practically all of the time! * This perhaps goes some way towards explaining that 20% performance benefit of * adding the extra load/store instructions: the optimizations that would have * made them unnecessary could be stunningly effective--but they could almost * never be used because they might break signal handling! * * Yet despite all the conservative compiler care in the world it remains * terribly hard and complex to avoid serious bugs in signal handlers: you may * be logging to an output sink that isn't quite ready for use yet, or has just * been closed, or was just doing something else. You may want to change a * variable that the main process was changing at the same time. It's got all * the problems of multithreading, but without the aid of synchronization * primitives (okay, so you get race conditions in place of deadlocks). * * Now that we're here, feel free to build your own lightweight mechanism on top * of this that does something actually useful! * * All we really want is to set a flag saying "an exit was requested." We don't * even need to know when, how many times, or by whom. A modern kernel would * provide a primitive for just that, and build signal functionality on top of * it--not to mention select() and poll() and such. * * Look at the Amiga OS for a shining example: * - A "signal" is just a bit in the process descriptor that can be set * atomically by another process, the process itself, or even an interrupt * handler. As with Unix select(), a process can atomically retrieve and * clear any set of incoming signals, optionally sleeping first if nothing in * the set has arrived yet. Delivery is asynchronous. * - All interrupt handling is done in the "bottom half;" signals are efficient * enough to guarantee that the handler is woken up in time. The decision to * reschedule the signaled process is a simple matter of comparing the signal * to a sleep mask. * - Other forms of IPC build on top of the signal interface, using it to flag * message arrival and such. But many basic forms of event notification, * such as a timer going off, or modification of a watched file, require only * that simple single-bit signal. * - Unix-style signal handlers can be registered as "exceptions." These are * all managed in terms of signal masks. As it turned out this was not what * anybody actually wanted; it was documented in the late 1980's but not * properly implemented until 1992 because nobody ever tried to use it for * anything anyway. Or nobody apart from a Unix emulation library, perhaps. * Everybody else just uses the single bit for communication and does the * rest of the work in the event loop. * - The graphical subsystem communicates with applications through these * lightweight mechanisms, allowing a single process to provide effectively * hard-realtime GUI responsiveness even while the application is busy. You * want to resize a window? If the owner doesn't signal its willingness to * repaint within 15 milliseconds, the system lets you know it's not going to * happen. You click a button while the application doesn't have time for * you? Either it's set up to send a signal, which will happen on cue, or it * will "feel dead" from the start and it won't suddenly wake up and "become * clicked" later. You select a different radio button or move the mouse * into a sensitive region? The application isn't even woken up unless it * registered an interest. It's all stunningly efficient. * * Instead, we need the system to schedule and/or interrupt our process, mask * the signal and jump through hoops to deal with various types of other * signals that may come in before we're finished handling this one, interrupt * any system call that we may be in (which must of course be able to deal with * this correctly), save execution context on the stack, invoke our handler * function (making the signal number available to us just in case we're * interested, which we're not), note completion of the handler, check for and * deal with any other incoming signals, unmask our signal and restore normal * running state, and return our process to where it was when it all happened. * The program pays the cost of expecting that any system call may return * unexpectedly with an error code of "oops, a signal arrived." * * And all this just so we can write one lousy Boolean to memory. One single * stinking bit. */ static void sighand_exit(int sig) { stop = true; } /// Write process id to pidfile if requested, and close it. Clobbers localbuf. /** * @return Success (which is trivially achieved if no pidfile is requested) */ static void finishpidfile(void) { if (likely(make_pidfile)) { atexit(rmpidfile); close(pidfd); } } static pid_t daemonize(void) { const pid_t result = fork(); if (unlikely(result < 0)) perror("Could not fork off daemon process"); else if (result == 0) setsid(); return result; } /// Was a dump of statistics requested? static volatile bool print_status=false; /// Was an immediate adjustment requested? static volatile bool adjust_swap=false; /// Signal handler that requests generation of a status report to stdout static void sighand_status(int sig) { print_status = true; } /// Signal handler that initiates immediate swapspace adjustment static void sighand_diet(int sig) { adjust_swap = true; } static void install_sighandler(int signum, void (*handler)(int)) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = handler; sigaction(signum, &sa, NULL); } /// Install signal handlers static void install_sigs(void) { #ifdef SIGXFSZ // Some systems may send signal if our swapfiles get too large, but we have // our own ways of dealing with that eventuality. Ignore the signal. signal(SIGXFSZ, SIG_IGN); #endif // Install exit handler. These may not be repeatable, i.e. if the same signal // comes in again, we will probably just be terminated. But that's not // unreasonable, come to think of it. signal(SIGTERM, sighand_exit); signal(SIGHUP, sighand_exit); #ifdef SIGPWR signal(SIGPWR, sighand_exit); #endif // These handlers must be repeatable. install_sighandler(SIGUSR1, sighand_status); install_sighandler(SIGUSR2, sighand_diet); } bool swapfs_large_enough(void) { const memsize_t minswapfile = minimal_swapfile(); if (minswapfile == MEMSIZE_ERROR) return false; if (swapfs_size() < minswapfile) { logm(LOG_CRIT, "The filesystem holding swapspace's swap directory isn't big enough " "to hold useful swapfiles."); logm(LOG_CRIT, "Please try to expand this partition or relocate it to a larger one, " "if possible; or if all else fails, choose a different swap directory " "in your swapspace configuration."); return false; } if (swapfs_free() < minswapfile) logm(LOG_WARNING, "Not enough free space on swap directory. As things stand now, " "swapspace will not be able to create swap files."); return true; } int main(int argc, char *argv[]) { assert(sizeof(localbuf) >= getpagesize()); close(STDIN_FILENO); setlocale(LC_ALL, "C"); if (!configure(argc, argv)) return EXIT_FAILURE; if (unlikely(!read_proc_swaps()) || unlikely(!activate_old_swaps()) || unlikely(!check_memory_status())) return EXIT_FAILURE; if (erase) return retire_all() ? EXIT_SUCCESS : EXIT_FAILURE; install_sigs(); if (unlikely(!startpidfile())) return EXIT_FAILURE; /* Do a first iteration here so we can report any startup errors, and if we're * going to run as a daemon, we can do so in a steady state. */ handle_requirements(); /* If we're going to fork(), this is the last chance to read /proc/swaps in a * nonempty state before we do so. Make one last attempt to check its format. */ if (!proc_swaps_parsed()) { if (!read_proc_swaps()) return EXIT_FAILURE; #ifndef NO_CONFIG if (!quiet && !proc_swaps_parsed()) fputs("[/proc/swaps is empty, so cannot check its format]\n", stderr); #endif } if (godaemon) { #ifndef NO_CONFIG if (verbose) logm(LOG_DEBUG, "daemonizing..."); #endif const pid_t pid = daemonize(); if (unlikely(pid < 0)) { rmpidfile(); return EXIT_FAILURE; } if (pid > 0) { /* New process, so new pid. Parent process rewrites pidfile. We do this * from the parent, not the child process so that we're sure that the * pidfile is in a stable state when the parent exits. */ lseek(pidfd, 0, SEEK_SET); #ifndef NO_CONFIG if (verbose) logm(LOG_DEBUG, "got process id %d", pid); #endif return writepid(pid) ? EXIT_SUCCESS : EXIT_FAILURE; } } finishpidfile(); if (godaemon) { close(STDERR_FILENO); close(STDOUT_FILENO); // From here on std output is pointless in daemon mode. Use syslog instead. log_start(argv[0]); } // Central loop for (++runclock; !stop; ++runclock) { if (print_status) print_status = false, dump_stats(); else if (adjust_swap) adjust_swap = false, request_diet(); else handle_requirements(); sleep(1); } int result = EXIT_SUCCESS; #ifndef NO_CONFIG /* If we're worried about attackers getting unguarded access to the disk, we * need to retire and erase all swap files to keep them secret. */ if (paranoid && !retire_all()) result = EXIT_FAILURE; #endif rmpidfile(); log_close(); return result; } swapspace-1.10/src/state.h0000644000175000017500000000227110457073744013706 0ustar jtvjtv/* This file is part of Swapspace. Copyright (C) 2005,2006, Software Industry Industry Promotion Agency (SIPA) Written by Jeroen T. Vermeulen . Swapspace 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. Swapspace 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 swapspace; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #ifndef SWAPSPACE_STATE_H #define SWAPSPACE_STATE_H /// Perform one iteration of the allocation algorithm. Clobbers localbuf. void handle_requirements(void); /// Log state information. Clubbers localbuf. void dump_state(void); /// Request a transition to "diet" state void request_diet(void); #ifndef NO_CONFIG char *set_cooldown(long long duration); #endif #endif swapspace-1.10/src/opts.c0000644000175000017500000003211110457075064013537 0ustar jtvjtv/* This file is part of Swapspace. Copyright (C) 2005,2006, Software Industry Industry Promotion Agency (SIPA) Written by Jeroen T. Vermeulen . Swapspace 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. Swapspace 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 swapspace; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "env.h" #include #include #include #include #include #include #include #include #include "memory.h" #include "opts.h" #include "support.h" #include "state.h" #include "swaps.h" static const char copyright[] = "\n" "Copyright (c) 2004-2005 Software Industry Promotion Agency of Thailand\n" "Written by Jeroen T. Vermeulen \n" "This program is free software, and is available for use under the GNU\n" "General Public License (GPL).\n"; #ifndef NO_CONFIG static char configfile[PATH_MAX] = "/etc/swapspace.conf"; static char *set_configfile(long long dummy) { return configfile; } static char *set_help(long long dummy); static char *set_version(long long dummy); static bool inspect = false; static char *set_inspect(long long dummy) { inspect = true; return NULL; } enum argtype { at_none, at_num, at_str }; struct option { const char *name; char shortopt; enum argtype argtype; /// Minimum value for integer option, or for string, whether arg is required long long min; /// Maximum value for integer option, or for string, maximum length long long max; char *(*setter)(long long value); const char *desc; }; /// Available options, sorted alphabetically by long option name static const struct option options[] = { { "buffer_elasticity",'B', at_num, 0, 100, set_buffer_elasticity, "Consider n% of buffer memory to be \"available\"" }, { "cache_elasticity", 'C', at_num, 0, 100, set_cache_elasticity, "Consider n% of cache memory to be \"available\"" }, { "configfile", 'c', at_str, 1, PATH_MAX, set_configfile, "Use configuration file s" }, { "cooldown", 'a', at_num, 0, LONG_MAX, set_cooldown, "Give allocation attempts n seconds to settle" }, { "daemon", 'd', at_none, 0, 0, set_daemon, "Run quietly in background" }, { "erase", 'e', at_none, 0, 0, set_erase, "Try to free up all swapfiles, then exit" }, { "freetarget", 'f', at_num, 2, 99, set_freetarget, "Aim for n% of available space" }, { "help", 'h', at_none, 0, 0, set_help, "Display usage information" }, { "inspect", 'i', at_none, 0, 0, set_inspect, "Verify that configuration is okay, then exit" }, { "lower_freelimit", 'l', at_num, 0, 99, set_lower_freelimit, "Try to keep at least n% of memory/swap available" }, { "max_swapsize", 'M', at_num, 8192, LLONG_MAX, set_max_swapsize, "Restrict swapfiles to n bytes" }, { "min_swapsize", 'm', at_num, 8192, LLONG_MAX, set_min_swapsize, "Don't create swapfiles smaller than n bytes" }, { "paranoid", 'P', at_none, 0, 0, set_paranoid, "Wipe disk space occupied swapfiles after use" }, { "pidfile", 'p', at_str, 0, PATH_MAX, set_pidfile, "Write process identifier to file s" }, { "quiet", 'q', at_none, 0, 0, set_quiet, "Suppress informational output" }, { "swappath", 's', at_str, 1, PATH_MAX, set_swappath, "Create swapfiles in secure directory s" }, { "upper_freelimit", 'u', at_num, 0, 100, set_upper_freelimit, "Reduce swapspace if more than n% is free" }, { "verbose", 'v', at_none, 0, 0, set_verbose, "Print lots of debug information" }, { "version", 'V', at_none, 0, 0, set_version, "Print version number and exit" } }; static int optcmp(const void *o1, const void *o2) { return strcmp(((const struct option *)o1)->name, ((const struct option *)o2)->name); } #ifndef NDEBUG static bool options_okay(void) { static const int num_opts = sizeof(options)/sizeof(*options); for (int i=0; i= 127) { fprintf(stderr, "Shortopt for option %d out of range\n", i); return false; } if (options[i].min > options[i].max) { fprintf(stderr,"Minimum for %s exceeds maximum\n", options[i].name); return false; } if (!options[i].setter) { fprintf(stderr,"No setter defined for option %s\n",options[i].name); return false; } } localbuf[(unsigned char)options[0].shortopt] = true; for (int i=1; i= 0) { fprintf(stderr, "Options array not sorted (%s >= %s)\n", options[i-1].name, options[i].name); return false; } if (localbuf[(unsigned char)options[i].shortopt]) { fprintf(stderr, "Option %s re-uses shortopt '%c'\n", options[i].name, options[i].shortopt); return false; } } return true; } #endif // NDEBUG static const char *argproto(int opt, bool shortopt) { const char *result = ""; switch (options[opt].argtype) { case at_none: break; case at_num: result = (shortopt ? " n" : "=n"); break; case at_str: result = (shortopt ? " s" : "=s"); break; } return result; } static char *set_help(long long dummy) { puts("Usage: swapspace [options]\n" "Dynamic swap space manager for GNU/Linux.\n\n" "Options are:"); size_t longestopt = 1; for (int i=0; i < sizeof(options)/sizeof(*options); ++i) { size_t len = strlen(options[i].name); if (len > longestopt) longestopt = len; } for (int i=0; i < sizeof(options)/sizeof(*options); ++i) { static const char pad[] = " "; const char *const pt = argproto(i, true); size_t ptlen; ptlen = strlen(pt); assert(ptlen < strlen(pad)); printf(" -%c%s,%s--%s%s%*s\t%s\n", options[i].shortopt, pt, pad+ptlen, options[i].name, argproto(i, false), (int)(longestopt+2-strlen(options[i].name)-2*strlen(pt)), "", options[i].desc); } puts(copyright); exit(EXIT_SUCCESS); } #else static void short_usage(void) { puts("This is the unconfigurable version of swapspace.\n" "\n" "Usage: swapspace [-d] [-h] [-p] [-V]\n" "\t-d run as daemon" "\t-h display this overview and exit\n" "\t-p create pidfile\n" "\t-q (ignored)\n" "\t-v (ignored)\n" "\t-V display version information and exit\n" "\n" "Use zero-argument version for normal operation."); puts(copyright); exit(EXIT_SUCCESS); } #endif // NO_CONFIG char *set_version(long long dummy) { puts("swapspace " VERSION ", " DATE); puts(copyright); exit(EXIT_SUCCESS); } #ifndef NO_CONFIG /// Emit error message about configuration. Returns false. static bool config_error(const char msg[]) { fprintf(stderr, "%s: %s\n", configfile, msg); return false; } static bool value_error(const char keyword[], const char msg[]) { fprintf(stderr, "Configuration error: '%s': %s\n", keyword, msg); return false; } static bool handle_configitem(const char keyword[], const char *value) { const struct option keyopt = { keyword, 0, 0, 0, 0 }; const struct option *const opt = bsearch(&keyopt, options, sizeof(options)/sizeof(*options), sizeof(*options), optcmp); if (!opt) return value_error(keyword, "unknown configuration item"); long long numarg = 0; size_t arglen = 0; if (value) arglen = strlen(value); const char *err = NULL; if (value) switch (opt->argtype) { case at_none: err = "does not take an argument"; break; case at_num: if (!*value) err = "requires a numeric argument"; else { char *endptr; numarg = strtoll(value, &endptr, 0); if (*endptr) switch (tolower(*endptr++)) { case 'k': numarg *= KILO; break; case 'm': numarg *= MEGA; break; case 'g': numarg *= GIGA; break; case 't': numarg *= TERA; break; default: err = "invalid unit letter"; } if (*endptr) err = "invalid numeric argument"; else if (numarg < opt->min) switch (opt->min) { case 0: err = "argument may not be negative"; break; case 1: err = "argument must be greater than zero"; break; default: err = "given value too small"; break; } else if (numarg > opt->max) err = "given value too large"; } break; case at_str: if (arglen > opt->max) err = "string too long"; break; } else if (opt->argtype == at_num || (opt->argtype == at_str && opt->min)) { err = "requires an argument"; } if (err) return value_error(keyword, err); char *const strdest = opt->setter(numarg); if (strdest && value) strcpy(strdest, value); return true; } static bool read_config(void) { FILE *fp = fopen(configfile, "r"); if (!fp) { const int err = errno; const bool nofile = (err == ENOENT); // TODO: It's also an error if nofile and -c option used if (!nofile) perror("Could not open configuration file"); else if (!quiet) fputs("Using default configuration\n",stderr); return nofile; } while (fgets(localbuf, sizeof(localbuf), fp)) { // Cut line short at hash character, if any int j; for (j=0; localbuf[j] && localbuf[j] != '#'; ++j); localbuf[j] = '\0'; char key[100], val[PATH_MAX], dummy[2]; // TODO: Check that quotes are closed properly if (sscanf(localbuf," %100[a-z_] = \"%"PMS"[^\"]\" %1s",key,val,dummy)==2 || sscanf(localbuf," %100[a-z_] = %"PMS"s %1s",key,val,dummy) == 2) { if (!handle_configitem(key, val)) return false; } else if (sscanf(localbuf," %100[a-z_] %1s",key,dummy) == 1) { if (!handle_configitem(key, NULL)) return false; } else if (sscanf(localbuf," %1s",dummy) > 0) { return config_error("syntax error"); } } if (ferror(fp)) { perror("Error reading configuration file"); return false; } return true; } /// Parse command line. Clobbers localbuf. static bool read_cmdline(int argc, char *argv[]) { assert(argv[argc-1]); assert(!argv[argc]); for (int i=1; i= sizeof(localbuf)) { fprintf(stderr, "Option name too long\n"); return false; } strncpy(localbuf, optname, optnamelen); localbuf[optnamelen] = '\0'; optname = localbuf; nextarg = eqsign + 1; } } else { // Short option name (starting with single dash) int j; for (j=0; j. Swapspace 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. Swapspace 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 swapspace; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #ifndef SWAPSPACE_LOG_H #define SWAPSPACE_LOG_H #include #include "main.h" #include "memory.h" /// Open log, with given program name (string storage must remain available!) void log_start(const char myname[]); /// Stop logging, e.g. because storage containing log name goes out of scope void log_close(); /// Log formatted message to log, or stdout/sterr as appropriate void logm(int priority, const char fmt[], ...); /// Log message with given errno (or ignore error number if it is zero) void log_perr(int priority, const char msg[], int fault_errno); /// Log message, perror()-style, but appending a quoted string void log_perr_str(int priority, const char msg[], const char arg[], int err); #ifndef NO_CONFIG /// Log logfile size discrepancy void log_discrep(int priority, const char msg[], int swapfile, memsize_t expected, memsize_t found); #endif #endif swapspace-1.10/src/main.h0000644000175000017500000000435510457074312013507 0ustar jtvjtv/* This file is part of Swapspace. Copyright (C) 2005,2006, Software Industry Industry Promotion Agency (SIPA) Written by Jeroen T. Vermeulen . Swapspace 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. Swapspace 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 swapspace; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #ifndef SWAPSPACE_MAIN_H #define SWAPSPACE_MAIN_H #include /// I thought C99 had this built in... Maybe I'm doing something wrong. typedef int bool; enum { false=0, true=1 }; #define KILO (1024LL) #define MEGA (KILO*KILO) #define GIGA (KILO*MEGA) #define TERA (KILO*GIGA) /// Reusable local buffer space /** Can be used for composing command lines or error messages, and other * function-local temporary storage. This is not pretty, but the program must * get its work done under low-memory conditions. Avoiding dynamic memory * allocation (and even wild fluctuations in stack use) may really matter here. * * Size is best kept to an even multiple of page size, and is guaranteed to be * no less than one page in size. */ extern char localbuf[16384]; /// Timestamp counter extern time_t runclock; /// Is the swapdir on a filesystem large enough for useful swap files? /** Also prints a warning if the filesystem is large enough, but does not have * sufficient free space (but does not fail in that case). */ bool swapfs_large_enough(void); #ifndef NO_CONFIG /// Suppress informational output and warnings? extern bool quiet; /// Print debug output when changing state and such? extern bool verbose; char *set_quiet(long long dummy); char *set_verbose(long long dummy); bool main_check_config(void); #endif char *set_daemon(long long dummy); char *set_pidfile(long long dummy); char *set_erase(long long dummy); #endif swapspace-1.10/src/opts.h0000644000175000017500000000223410457073744013552 0ustar jtvjtv/* This file is part of Swapspace. Copyright (C) 2005,2006, Software Industry Industry Promotion Agency (SIPA) Written by Jeroen T. Vermeulen . Swapspace 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. Swapspace 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 swapspace; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #ifndef SWAPSPACE_OPTS_H #define SWAPSPACE_OPTS_H #include "main.h" bool configure(int argc, char *argv[]); /// Check for error condition EXPR /** If the condition is met, prints error message to stderr and returns false. */ #define CHECK_CONFIG_ERR(EXPR) \ if(EXPR)return fputs("Configuration error: " #EXPR "\n",stderr),false #endif swapspace-1.10/src/Makefile0000644000175000017500000000302610356646327014055 0ustar jtvjtv#! /usr/bin/make # Assumes we're using gcc # swapspace is written in C99, but with at least one extension that isn't # supported in gcc's C99 mode: sigaction() (though it is part of POSIX). CFLAGS += --std=gnu99 -Wall --pedantic -Wshadow -O2 -g CPPFLAGS += -DSUPPORT_LARGE_FILES -DVERSION="\"$(VERSION)\"" -DDATE="\"$(DATE)\"" RM=rm -f # Uncomment this to build a special ultra-lightweight unconfigurable version of # swapspace. This saves perhaps 20 kilobytes of binary size and breaks all but # a few of the command line options. You probably don't want this; it's mostly # here as a proof-of-concept and testing ground for the future elimination of # configuration items. #CPPFLAGS += -DNO_CONFIG # Uncomment this to disable assertions and internal consistency checking that # you probably don't need after release. # CPPFLAGS += -DNDEBUG all : swapspace hog SWAPSPACEOBJS=log.o main.o memory.o opts.o state.o support.o swaps.o swapspace : $(SWAPSPACEOBJS) $(CC) $^ $(LDFLAGS) $(LOADLIBES) $(LDLIBS) -o $@ hog : hog.o log.o : log.c log.h main.h memory.h main.o : main.c config.h env.h log.h main.h memory.h support.h memory.o : memory.c config.h env.h log.h main.h memory.h support.h opts.o : opts.c opts.h main.h ../VERSION ../DATE state.o : state.c state.h log.h main.h memory.h support.h swaps.h support.o : support.c config.h env.h support.h swaps.o : swaps.c config.h env.h log.h main.h memory.h state.h support.h swaps.h clean : $(RM) $(SWAPSPACEOBJS) hog.o distclean : clean $(RM) swapspace hog .PHONY : all clean distclean swapspace-1.10/src/env.h0000644000175000017500000000216710457073744013362 0ustar jtvjtv/* This file is part of Swapspace. Copyright (C) 2005,2006, Software Industry Industry Promotion Agency (SIPA) Written by Jeroen T. Vermeulen . Swapspace 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. Swapspace 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 swapspace; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ // Include this file from every source file, and before any system includes! #ifndef SWAPSPACE_ENV_H #define SWAPSPACE_ENV_H #ifdef SUPPORT_LARGE_FILES # define _LARGEFILE64_SOURCE # define SEEK(fd,pos,whence) lseek64(fd,pos,whence) #else # define SEEK(fd,pos,whence) lseek(fd,pos,whence) #endif #endif swapspace-1.10/src/swaps.c0000644000175000017500000005141610470511757013717 0ustar jtvjtv/* This file is part of Swapspace. Copyright (C) 2005,2006, Software Industry Industry Promotion Agency (SIPA) Written by Jeroen T. Vermeulen . Swapspace 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. Swapspace 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 swapspace; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "env.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "log.h" #include "opts.h" #include "state.h" #include "support.h" #include "swaps.h" // Try to use O_LARGEFILE #ifndef O_LARGEFILE #define O_LARGEFILE 0 #endif // Don't follow symlinks, if possible; they may pose a security risk #ifndef O_NOFOLLOW #define O_NOFOLLOW 0 #endif static char swappath[PATH_MAX] = "/var/lib/swapspace"; static size_t swappath_len; /// Smallest allowed swapfile size static memsize_t min_swapsize = 4*MEGA; /// Largest allowed swapfile size /** Don't set this too low. The program will learn if it runs into file size * limits. */ static memsize_t max_swapsize = 2*TERA; /// Truncate n to a multiple of memory page size static memsize_t trunc_to_page(memsize_t n) { return n & ~((memsize_t)getpagesize()-1); } /// Round n upwards to multiple of page size static memsize_t ext_to_page(memsize_t n) { return trunc_to_page(n) + getpagesize()-1; } #ifndef NO_CONFIG char *set_swappath(long long dummy) { return swappath; } char *set_min_swapsize(long long size) { min_swapsize = trunc_to_page(size); return NULL; } char *set_max_swapsize(long long size) { max_swapsize = trunc_to_page(size); return NULL; } bool paranoid = false; char *set_paranoid(long long dummy) { paranoid = true; return NULL; } #endif #ifndef NO_CONFIG bool swaps_check_config(void) { CHECK_CONFIG_ERR(min_swapsize > max_swapsize); CHECK_CONFIG_ERR(min_swapsize < 10*getpagesize()); if (swappath[0] != '/') { logm(LOG_ERR, "Swap path is not absolute (must start with '/'): '%s'", swappath); return false; } return true; } #endif /// Change to swap directory, canonicalizing swappath in the process bool to_swapdir(void) { if (chdir(swappath) == -1) { const int err = errno; bool please_reinstall = false; log_perr_str(LOG_ERR, "Could not cd to swap directory", swappath, errno); switch (err) { case ENOENT: // Does not exist case ENOTDIR: // Is not a directory #ifdef ELOOP case ELOOP: // Leads to a link loop (or too many link levels anyway) #endif please_reinstall = true; break; case EACCES: // If we're running as root, we definitely should have this permission! please_reinstall = (geteuid() == 0); break; } if (please_reinstall) logm(LOG_ERR, "swapspace installed incorrectly. Please reinstall!"); return false; } swappath_len = strlen(swappath); #ifndef NO_CONFIG // Get rid of any "/./", "//", and "/../" clutter that might be in swappath. // This is needed because we want to recognize our swapfiles in /proc/swaps, // which will list them with the canonicalized version of swappath. if (!getcwd(swappath, sizeof(swappath))) { logm(LOG_CRIT, "Swap path too long"); return false; } // Remove trailing slash, if any if (swappath[swappath_len-1] == '/') { --swappath_len; swappath[swappath_len] = '\0'; } for (int i=swappath_len-1; i >= 0; --i) if (isspace(swappath[i])) { logm(LOG_ERR, "Not supported: swap path contains whitespace"); return false; } #endif return true; } struct Swapfile { memsize_t size; memsize_t used; long long created; /// Has this swapfile been spotted in /proc/swaps? bool observed_in_wild; }; static int sequence_number = 0; /// Power-of-two defining how many active swapfiles to support /** Since swapspace allocates swapfiles of increasing sizes, there is probably * little need for a large number of swapfile slots. */ #define MAXSWAPS_SHIFT 5 #define MAX_SWAPFILES (1 << MAXSWAPS_SHIFT) /// Array of swapfile descriptors /** The last element remains zeroed for use as a sentry. */ static struct Swapfile swapfiles[MAX_SWAPFILES+1]; /// Have we been able to verify that /proc/swaps is in the expected format? static bool proc_swaps_read_ok = false; /// Print status information to stdout void dump_stats(void) { logm(LOG_INFO, "clock: %lld", (long long)runclock); dump_state(); dump_memory(); // Count active swapfiles. Note that we don't remember this anywhere; it's // rarely needed (only when requested), it's not very costly to derive, and // keeping a redundant counter is just asking for minor bugs. int activeswaps = 0; for (int i=0; i= 0 || (errno == EINTR && persevere))); // Byte count may end up a little low if write() returns -1. That's okay. return written; } /// Populate swapfile by writing data to it /** * @return Real size of created file, or zero on failure */ static memsize_t fill_swapfile(const char file[], int fd, memsize_t size) { // memsize_t bytes = write_data(fd, size, false); if (unlikely(bytes < size)) { const int err = errno; log_perr_str(LOG_ERR, "Error writing swapfile", file, errno); switch (err) { case EFBIG: // File too big. Don't try creating files this large again. if (likely(bytes > 0 && max_swapsize > bytes)) { max_swapsize = trunc_to_page(bytes); #ifndef NO_CONFIG if (verbose) logm(LOG_INFO, "Restricting swapfile size to %lld", (long long)bytes); #endif } break; case ENOSPC: case EIO: // Disk full, or low-level I/O error. Go into "diet" state. request_diet(); break; default: logm(LOG_WARNING, "Unexpected error writing swap file"); } bytes = 0; } return bytes; } /// Create file to be used as swap. Clobbers localbuf. /** * @param filename File to be created * @param size Desired size in bytes (but already rounded to page size) * @return Size of new swapfile, which may differ from requested size. Zero * indicates failure, in which case the file is deleted. */ static memsize_t make_swapfile(const char file[], memsize_t size) { assert(min_swapsize <= max_swapsize); assert(max_swapsize == trunc_to_page(max_swapsize)); assert(min_swapsize == trunc_to_page(min_swapsize)); assert(size == trunc_to_page(size)); if (unlikely(size < min_swapsize)) return 0; size = MIN(size, max_swapsize); unlink(file); const int fd=open(file, O_WRONLY|O_CREAT|O_EXCL|O_LARGEFILE, S_IRUSR|S_IWUSR); if (unlikely(fd == -1)) { log_perr_str(LOG_ERR, "Could not create swapfile", file, errno); return 0; } if (unlikely(!fill_swapfile(file, fd, size))) { size = 0; unlink(file); } close(fd); return size; } /// Information about a swapfile struct swapfile_info { char name[PATH_MAX]; int seqno; memsize_t size; memsize_t used; }; static bool valid_swapfile(const char filename[], int *seqno) { if (unlikely(!*filename)) return false; char *endptr; long seql = strtol(filename, &endptr, 10); if (unlikely(*endptr || seql < 0 || seql >= MAX_SWAPFILES)) return false; *seqno = (int)seql; return true; } static memsize_t filesize(const char name[]) { int fd = open(name, O_RDONLY|O_LARGEFILE|O_NOFOLLOW); if (unlikely(fd == -1)) { log_perr_str(LOG_WARNING, "Can't determine size of", name, errno); return -1; } memsize_t pos = SEEK(fd, 0, SEEK_END); if (unlikely(pos == -1)) log_perr_str(LOG_WARNING, "Can't determine size of", name, errno); return pos; } bool activate_old_swaps(void) { DIR *dir = opendir("."); if (unlikely(!dir)) { log_perr(LOG_ERR, "Cannot read swap directory", errno); return false; } for (struct dirent *d = readdir(dir); d; d = readdir(dir)) { int seqno; if (valid_swapfile(d->d_name, &seqno) && !swapfiles[seqno].size) { #ifndef NO_CONFIG if (!quiet) logm(LOG_INFO, "Found old swapfile '%d'", seqno); #endif const memsize_t size = filesize(d->d_name); if (likely(size > min_swapsize && enable_swapfile(d->d_name))) { swapfiles[seqno].size = size; } else { #ifndef NO_CONFIG if (!quiet) logm(LOG_NOTICE, "Deleting unusable swapfile '%d'", seqno); #endif unlink(d->d_name); } } } if (unlikely(closedir(dir)==-1)) perror("Error closing swap directory"); if (!proc_swaps_parsed() && unlikely(!read_proc_swaps())) return false; return true; } static FILE *open_proc_swaps(void) { FILE *fp = fopen("/proc/swaps", "r"); if (unlikely(!fp)) log_perr(LOG_ERR, "Could not open /proc/swaps", errno); return fp; } /// Check that /proc/swaps header line, as found in buf, matches expected format static bool check_proc_swaps_header(const char buf[]) { char c; if (unlikely(sscanf(buf, "Filename Type Size Used %c",&c) < 1)) { logm(LOG_ERR, "/proc/swaps is not in the expected format"); return false; } proc_swaps_read_ok = true; return true; } /// Read next swapfile description from /proc/swaps. Clobbers localbuf. /** Use to iterate the swapspace-managed swapfiles in /proc/swaps. Partitions, * as well as non-swapspace swapfiles, are ignored. Status of known swapfiles * found in the list is updated along the way. * * Returns false if no more swapfile descriptions were found; in that case, * result will contain garbage. Check ferror(fp) to distinguish eof from error. */ static bool get_swapfile_status(FILE *fp, struct swapfile_info *result) { while (fgets(localbuf, sizeof(localbuf), fp)) { char type[100]; char c; const int x=sscanf(localbuf, "%"PMS"s %100s %lld %lld %c", result->name, type, &result->size, &result->used, &c); if (unlikely(x < 5) && likely(localbuf[0])) { /* Nasty special case: if /proc/swaps is nonempty, it should have a header * line of a fixed format that we'd like to check. Unfortunately, some * kernels including at least 2.6.10 (and who knows how many else) have a * bug that means the line may disappear if the oldest swap is disabled * while newer ones remain active. * * Also, looking at the kernel code, it seems unlikely that this can be * fixed cleanly without a vfs interface change. Since we're working * around that bug here without giving up on our chance to verify the * file's format entirely, we may as well take the possibility into * account that an attempted kernel fix could occasionally put the header * line in the wrong place--i.e., not at the top of /proc/swaps but * somewhere further down. */ if (!check_proc_swaps_header(localbuf)) { logm(LOG_ERR, "Parse error in /proc/swaps: '%s'", localbuf); return false; } } // /proc/swaps appears to report sizes in 1 KB blocks result->size *= KILO; result->used *= KILO; if (likely(strcmp(type, "file") == 0) && likely(strncmp(result->name, swappath, swappath_len) == 0) && likely(result->name[swappath_len] == '/') && likely(valid_swapfile(result->name+swappath_len+1, &result->seqno))) { // Found what looks to be one of our swapfiles. Update our list. if (unlikely(!swapfiles[result->seqno].size)) { // We didn't know about this swapfile yet. Adopt it. #ifndef NO_CONFIG if (!quiet) logm(LOG_NOTICE, "Detected swapfile '%d'", result->seqno); #endif swapfiles[result->seqno].created = runclock; } #ifndef NO_CONFIG else if (unlikely(swapfiles[result->seqno].size != result->size)) { // Size wasn't exactly what we expected. This is not unusual; there are // likely to be a few pages of overhead. const memsize_t expected = swapfiles[result->seqno].size, found = result->size; if (unlikely(swapfiles[result->seqno].observed_in_wild)) log_discrep(LOG_NOTICE,"size changed",result->seqno,expected,found); else if (unlikely(found > expected)) log_discrep(LOG_INFO, "larger than expected", result->seqno, expected, found); else if (!quiet && unlikely(found+2*getpagesize() < expected)) log_discrep(LOG_INFO, "smaller than expected", result->seqno, expected, found); } if (!quiet && unlikely(result->used > result->size)) log_discrep(LOG_NOTICE, "usage exceeds size", result->seqno, result->used, result->size); #endif swapfiles[result->seqno].size = result->size; swapfiles[result->seqno].used = result->used; swapfiles[result->seqno].observed_in_wild = true; return true; } } return false; } bool proc_swaps_parsed(void) { return proc_swaps_read_ok; } /// Read /proc/swaps and set up swapfiles[] accordingly. Clobbers localbuf. bool read_proc_swaps(void) { FILE *const fp = open_proc_swaps(); if (unlikely(!fp)) return false; for (int i=0; i= 0); assert(file < MAX_SWAPFILES); /* If swapoff() fails, we may be in an awkward situation. If we don't delete * the swapfile, we may be leaving an unused swapfile behind on the disk. * That would not be very nice of us. If the swapfile is still in use, on the * other hand, then we run into another problem. Since swapoff() works by * filename, if we are able to delete the file then we won't be able to * deactivate it without disabling swapping altogether. * * Finally, we also need to avoid reusing names of deleted swapfiles while * they are still in use or things would get horribly confused. */ char namebuf[30]; snprintf(namebuf, sizeof(namebuf), "%d", file); #ifndef NO_CONFIG if (!quiet) logm(LOG_NOTICE, "Retiring swapfile '%d'", file); #endif if (unlikely(swapoff(namebuf) == -1)) return false; #ifndef NO_CONFIG int fd = -1; if (paranoid) fd = open(namebuf, O_WRONLY|O_LARGEFILE|O_NOFOLLOW); #endif unlink(namebuf); #ifndef NO_CONFIG if (fd != -1) write_data(fd, swapfiles[file].size, true); #endif swapfiles[file].size = 0; return true; } /// Is given swapfile eligible for retirement? static bool retirable(int file, memsize_t maxsize) { assert(file >= 0); assert(file < MAX_SWAPFILES); // TODO: Include usage in calculations? Like "free the most unused space"? return swapfiles[file].size && swapfiles[file].size <= maxsize; } /// Find a file to retire, or return MAX_SWAPFILES if none available /** Policy is to offer the largest swapfile that is not bigger than target. */ static int find_retirable(memsize_t target) { if (!read_proc_swaps()) return MAX_SWAPFILES; int best=MAX_SWAPFILES; assert(!swapfiles[best].size); for (int i=0; i= swapfiles[best].size) best = i; return best; } bool retire_all(void) { bool ok = true; for (int i=0; i= 0); assert(last < MAX_SWAPFILES); int i; for (i = last+1; i < MAX_SWAPFILES && swapfiles[i].size; ++i); if (i >= MAX_SWAPFILES) for (i = 0; i < last && swapfiles[i].size; ++i); return i; } bool alloc_swapfile(memsize_t size) { /* Round request to page size, then add a bit for swapfile overhead. Clever * readers will notice that this relies on getpagesize() returning a power of * two. */ size = trunc_to_page(size) + 2*getpagesize(); const int newswap = find_free(sequence_number); if (unlikely(swapfiles[newswap].size)) return false; // No free slot, sorry! if (unlikely(size > swapfs_free())) return false; // Not enough disk space // We can allocate another swapfile. Great. #ifndef NO_CONFIG if (!quiet) logm(LOG_NOTICE, "Allocating swapfile '%d'", newswap); #endif char file[30]; snprintf(file, sizeof(file), "%d", newswap); swapfiles[newswap].size = make_swapfile(file, size); if (unlikely(!swapfiles[newswap].size)) return false; if (unlikely(!enable_swapfile(file))) { unlink(file); request_diet(); return false; } sequence_number = inc_swapno(sequence_number); return true; } void free_swapfile(memsize_t maxsize) { const int victim = find_retirable(maxsize); if (victim < MAX_SWAPFILES) retire_swapfile(victim); } swapspace-1.10/VERSION0000644000175000017500000000000510470512113012646 0ustar jtvjtv1.10 swapspace-1.10/COPYING0000644000175000017500000004314010444477026012656 0ustar jtvjtv GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. swapspace-1.10/Makefile0000644000175000017500000000321110356646327013262 0ustar jtvjtv#! /usr/bin/make SWAPPARENT=/var/lib SWAPDIR=$(SWAPPARENT)/swapspace all: VERSION DATE +$(MAKE) -C src VERSION="`cat VERSION`" DATE="`cat DATE`" +$(MAKE) -C doc VERSION="`cat VERSION`" DATE="`cat DATE`" clean: +$(MAKE) -C src clean distclean: clean +$(MAKE) -C src distclean install: all install -d $(DESTDIR)/$(SWAPPARENT) install -d -m700 $(DESTDIR)/$(SWAPDIR) install -d $(DESTDIR)/usr/sbin $(DESTDIR)/etc $(DESTDIR)/etc/init.d install -d $(DESTDIR)/usr/share/man/man8 strip src/swapspace || true install -m755 src/swapspace $(DESTDIR)/usr/sbin install -m644 swapspace.conf $(DESTDIR)/etc install -m744 debian/swapspace.init $(DESTDIR)/etc/init.d/swapspace install -m644 doc/swapspace.8 $(DESTDIR)/usr/share/man/man8 uninstall: /usr/sbin/swapspace -e $(RM) -r $(SWAPDIR) $(RM) /usr/sbin/swapspace /etc/swapspace.conf /etc/init.d/swapspace $(RM) /usr/share/man/man8/swapspace.8 dist : distclean VERSION tar -czf swapspace-`cat VERSION`.tar.gz --exclude=debian --exclude=.svn --exclude=.*.swp --exclude=tmp --exclude=swapspace-*.tar.gz . $(RM) -r tmp mkdir -p tmp/swapspace-`cat VERSION` tar -xzf swapspace-`cat VERSION`.tar.gz -C tmp/swapspace-`cat VERSION` $(RM) swapspace-`cat VERSION`.tar.gz tar -czf ../swapspace-`cat VERSION`.tar.gz -C tmp swapspace-`cat VERSION` $(RM) -r tmp # Derive version number and last change date from Debian changelog VERSION: debian/changelog head -n 1 $< | sed -e 's/^.*(\([0-9][^)]*\)).*/\1/' >$@ DATE: debian/changelog grep '^ -- ' $< | sed -e 's/^.*> \([MTWFS][a-z]\{2\}, \{1,2\}[[:digit:]]\{1,2\} [JFBAMSOND][a-z]* 2[[:digit:]]\{3\}\) .*/\1/' | head -n 1 >$@ .PHONY: all clean install uninstall swapspace-1.10/swapspace.conf0000644000175000017500000000344510444477074014467 0ustar jtvjtv# This file is part of Swapspace. # # Swapspace 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. # # Swapspace 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 swapspace; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # Swap path: location where swapspace may create and delete swapfiles. For # security reasons this directory must be accessible to root and to root only. #swappath="/var/lib/swapspace" # Lower free-space threshold: if the percentage of free space drops below this # number, additional swapspace is allocated #lower_freelimit=20 # Upper free-space threshold: if the percentage of free space exceeds this # number, swapspace will attempt to free up swapspace #upper_freelimit=60 # Percentage of free space swapspace should aim for when adding swapspace. This # should fall somewhere between lower_freelimit and upper_freelimit. #freetarget=30 # Smallest allowed size for individual swapfiles #min_swapsize=4m # Greatest allowed size for individual swapfiles #max_swapsize=2t # Duration (roughly in seconds) of the moratorium on swap allocation that is # instated if disk space runs out, or the cooldown time after a new swapfile is # successfully allocated before swapspace will consider deallocating swap space # again. The default cooldown period is about 10 minutes. #cooldown=600 swapspace-1.10/DATE0000644000175000017500000000002110470512113012234 0ustar jtvjtvWed, 16 Aug 2006 swapspace-1.10/README0000644000175000017500000002052110356646327012505 0ustar jtvjtvswapspace - dynamic swap manager for Linux http://thaiopensource.org/development/swapspace/ What it does for you This system daemon for the Linux kernel aims to do away with the need for large, fixed swap partitions or swap files. When installing a Linux-based system (invariably GNU/Linux) with swapspace, the usual swap partition can be omitted, or it can be kept quite small. Whenever swapspace finds, during normal system usage, that more virtual memory is needed, it will automatically claim space from the hard disk. Conversely, swap space that is no longer needed is freed up again for regular use by the filesystem. This means that with swapspace installed, sizing the system's available swap space during installation is no longer a life-or-death choice. It now becomes practical to run GNU/Linux off just a single, big partition--with no disk space lost to regrettable installation choices. The system should also be able to handle the occasional memory-intensive task that takes much more swap space than was originally foreseen, without leaving the same swap space unused and unusable during normal operation as is normally the case. Swapspace is made available for use under the GNU General Public License (GPL). See the file COPYING for an open copyright license. Copyright (C) 2005 Software Industry Promotion Agency (SIPA), Thailand. Written by Jeroen T. Vermeulen How it compares Unlike similar programs such as dynswapd and the older (and more portable) swapd, swapspace also adapts the sizes of the swap files it creates to meet demand. This means it is less dependent on limits that the kernel may impose on the total number of swapfiles, while reducing the need for manual configuration. If the daemon finds that more and more swap files are needed, it will start creating larger ones to anticipate demand. While demand for swap files is modest, it will stick to smaller ones that can be initialized more quickly and so respond more fluently to present requirements. Robustness and user-friendliness are the first priorities in developing this program. For example, all alternatives we looked at perversely needed to allocate multiple chunks of memory in dealing with low-memory situations; allocation failure would typically crash these programs. It turned out that none of these allocations were really necessary, and swapspace manages to avoid them categorically. This kills two birds with one stone when it comes to reliability: (i) the program doesn't ask for memory just when the least memory is available and (ii) it eliminates one of the most important causes of programming bugs as a risk factor. User-friendliness primarily means that no silly questions are asked of the user. The daemon tries to be sensible and figure out what is needed at runtime, by itself, and without user intervention. You should not have any need to learn about the configuration parameters, or tweak them. They exist mostly for development purposes. The swapspace daemon has been in production use for several months on various 32-bit architectures before it was first released to the public, and has been tested with swapfiles larger than can be addressed in 32 bits. Swapspace itself is a small program: about 50 kilobytes on my system--or even less in a special version that only accepts the most basic configuration options and ignores its configuration file. On top of that it allocates no memory at runtime (although the system will allocate some, of course) and does not use much stack space. When not to use it In its current form, swapspace is probably not a good choice for systems that need to remain responsive at all times; depending on the system and the cicrumstances, the creation a large new swapfile can take as long as half a minute and occupy quite a lot of the system's attention. The program minimizes the number of times swapfiles are created, but it wouldn't be very useful if it never created any swapfiles at all! We are hoping to bring further improvements in the future. Since the problem appears to be caused mostly by system code, however, it's hard to be sure that this is really possible. It may turn out to be possible for the kernel, with some modifications, to extend an existing swapfile while it is already in use. That would probably help a great deal, but we don't know at the moment how much work it would take. Where to start The program is available both as a source archive and as a Debian package built from that same source archive. To build and install from source, enter the main swapspace source directory and run "make". Some editing of the Makefile may, but generally shouldn't, be required as long as gcc is used as the C compiler. The program code is written in standard C99 (the 1999 edition of the C standard), plus one POSIX extension. The easiest mode of installation is by running "make install" with root privileges. See the provided manpage for details on how to run swapspace for troubleshooting or debugging purposes. A sample configuration file is also provided, but the average user should not need to take an interest. An init script is provided to start and stop swapspace as a regular system service. The "make install" procedure installs the init script in /etc/init.d, but does not currently ensure that swapspace is run on system startup. Technical details: Installation The installation procedure creates a directory /var/lib/swapspace, which must be accessible to the system's superuser (root) only; granting any kind of access for this directory to an untrusted user is likely to constitute a serious security hole. According to the Filesystem Hierarchy Standard, other appropriate places for this kind of file might be /var/tmp or /var/run. The former was not deemed appropriate because it is accessible to all users, and the latter because most system administrators would probably expect it to occupy very little space and may confine it to a partition that isn't large enough to hold useful swap space. Also, files in /var/lib survive reboots whereas those in /var/tmp and /var/run need not. Obviously any data in swap files can be safely erased on reboot (it's even tempting to think that that would be safer, though I don't think it really is). But consider a system consistently short on physical memory: during boot, swapspace will see the swapfiles left by the previous session, and reinstate them immediately at very little cost, ready for use when they are needed. If they had been erased during reboot, swapspace would have to allocate new ones on disk when it recognized the need for more virtual memory, which takes much more time and resources. Technical details: Algorithm Choices of allocation and deallocation are driven by a "finite state machine" consisting of four states: steady, hungry, overfed, and diet. The program will alternate through these states as circumstances dictate, applying different policies depending on current state. This was done to achieve a good tradeoff between willingness to free up unused swap space on the one hand, and avoidance of "thrashing" between deallocation and re-allocation of swapfiles on the other. For those interested, here is a quick description of these states and their associated policies: Steady - normal operation; no additional swap space is needed, nor do we have more than is needed. However, the program can go into the hungry or overfed states at the drop of a hat. Hungry - more swap space was recently allocated. The program is willing to allocate even more if needed, but it will not consider dropping unneeded swap files. After a certain timeout period, the program reverts to the steady state. Overfed - significantly more virtual memory is available than is needed. If this situation persists for a certain timeout period, a swapfile is deallocated and the program returns to the steady state. Diet - a recent allocation attempt has run into resource limits, e.g. because the filesystem used for swap files was full. No more swapspace can be allocated but excess files can be freed up very rapidly. Afer timeout, reverts to steady. State information can be queried by sending the program the SIGUSR1 signal (see "man 7 signal" to get the number for your architecture) which will cause it to log debug information to the system's daemon log and/or standard output, as appropriate. The program can also be made to log state transitions by starting it with the -v or --verbose option.