2 * sponge.c - read in all available info from stdin, then output it to
3 * file named on the command line
5 * Copyright © 2006 Tollef Fog Heen
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * version 2 as published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
26 #include <sys/types.h>
29 #include <sys/param.h>
33 #include <sys/resource.h>
40 #define BUFF_SIZE 8192
41 #define MIN_SPONGE_SIZE BUFF_SIZE
42 #define DEFAULT_TMP_NAME "/tmp/sponge.XXXXXX"
43 char tmpname
[] = DEFAULT_TMP_NAME
;
46 printf("sponge <file>: soak up all input from stdin and write it to <file>\n");
50 /* all the signal stuff copied from gnu sort */
52 /* The set of signals that are caught. */
53 static sigset_t caught_signals
;
55 /* Critical section status. */
57 int valid
; // was bool
61 /* Enter a critical section. */
62 static struct cs_status
cs_enter (void) {
63 struct cs_status status
;
64 status
.valid
= (sigprocmask(SIG_BLOCK
, &caught_signals
, &status
.sigs
) == 0);
68 /* Leave a critical section. */
69 static void cs_leave (struct cs_status status
) {
71 /* Ignore failure when restoring the signal mask. */
72 sigprocmask(SIG_SETMASK
, &status
.sigs
, NULL
);
76 static void cleanup() {
77 if (strcmp(tmpname
, DEFAULT_TMP_NAME
)) {
82 static void onexit_cleanup (void) {
83 struct cs_status cs
= cs_enter();
88 static void sighandler (int sig
) {
98 /* taken from coreutils sort */
99 static size_t default_sponge_size (void) {
100 /* Let MEM be available memory or 1/8 of total memory, whichever
102 double avail
= physmem_available();
103 double total
= physmem_total();
104 double mem
= MAX(avail
, total
/ 8);
105 struct rlimit rlimit
;
107 /* Let SIZE be MEM, but no more than the maximum object size or
108 system resource limits. Avoid the MIN macro here, as it is not
109 quite right when only one argument is floating point. Don't
110 bother to check for values like RLIM_INFINITY since in practice
111 they are not much less than SIZE_MAX. */
112 size_t size
= SIZE_MAX
;
115 if (getrlimit(RLIMIT_DATA
, &rlimit
) == 0 && rlimit
.rlim_cur
< size
)
116 size
= rlimit
.rlim_cur
;
118 if (getrlimit(RLIMIT_AS
, &rlimit
) == 0 && rlimit
.rlim_cur
< size
)
119 size
= rlimit
.rlim_cur
;
122 /* Leave a large safety margin for the above limits, as failure can
123 occur when they are exceeded. */
127 /* Leave a 1/16 margin for RSS to leave room for code, stack, etc.
128 Exceeding RSS is not fatal, but can be quite slow. */
129 if (getrlimit(RLIMIT_RSS
, &rlimit
) == 0 && rlimit
.rlim_cur
/ 16 * 15 < size
)
130 size
= rlimit
.rlim_cur
/ 16 * 15;
133 /* Use no less than the minimum. */
134 return MAX (size
, MIN_SPONGE_SIZE
);
137 void trapsignals (void) {
139 static int const sig
[] = {
140 /* The usual suspects. */
141 SIGALRM
, SIGHUP
, SIGINT
, SIGPIPE
, SIGQUIT
, SIGTERM
,
158 int nsigs
= sizeof(sig
) / sizeof(sig
[0]);
161 struct sigaction act
;
163 sigemptyset(&caught_signals
);
164 for (i
= 0; i
< nsigs
; i
++) {
165 sigaction(sig
[i
], NULL
, &act
);
166 if (act
.sa_handler
!= SIG_IGN
)
167 sigaddset(&caught_signals
, sig
[i
]);
170 act
.sa_handler
= sighandler
;
171 act
.sa_mask
= caught_signals
;
174 for (i
= 0; i
< nsigs
; i
++)
175 if (sigismember(&caught_signals
, sig
[i
]))
176 sigaction(sig
[i
], &act
, NULL
);
178 for (i
= 0; i
< nsigs
; i
++)
179 if (signal(sig
[i
], SIG_IGN
) != SIG_IGN
) {
180 signal(sig
[i
], sighandler
);
181 siginterrupt (sig
[i
], 1);
186 static void write_buff_tmp(char* buff
, size_t length
, FILE *fd
) {
187 if (fwrite(buff
, length
, 1, fd
) < 1) {
188 perror("error writing buffer to temporary file");
194 static void write_buff_out(char* buff
, size_t length
, FILE *fd
) {
195 if (fwrite(buff
, length
, 1, fd
) < 1) {
196 perror("error writing buffer to output file");
202 static void copy_tmpfile(FILE *tmpfile
, FILE *outfd
) {
204 if (fseek(tmpfile
, 0, SEEK_SET
)) {
205 perror("could to seek to start of temporary file");
209 while (fread(buf
, BUFF_SIZE
, 1, tmpfile
) > 0) {
210 write_buff_out(buf
, BUFF_SIZE
, outfd
);
212 if (ferror(tmpfile
)) {
213 perror("read temporary file");
221 int main (int argc
, char **argv
) {
222 char *buf
, *bufstart
, *outname
= NULL
;
223 size_t bufsize
= BUFF_SIZE
;
227 size_t mem_available
= default_sponge_size();
229 if (argc
> 2 || (argc
== 2 && strcmp(argv
[1], "-h") == 0)) {
232 bufstart
= buf
= malloc(bufsize
);
234 perror("failed to allocate memory");
237 while ((i
= read(0, buf
, bufsize
- bufused
)) > 0) {
239 if (bufused
== bufsize
) {
240 if ((bufsize
*2) >= mem_available
) {
242 /* umask(077); FIXME: Should we be setting umask, or using default? */
248 tmpfd
= mkstemp(tmpname
);
249 atexit(onexit_cleanup
); // solaris on_exit(onexit_cleanup, 0);
252 perror("mkstemp failed");
255 tmpfile
= fdopen(tmpfd
, "w+");
257 write_buff_tmp(bufstart
, bufused
, tmpfile
);
262 bufstart
= realloc(bufstart
, bufsize
);
264 perror("failed to realloc memory");
269 buf
= bufstart
+ bufused
;
272 perror("failed to read from stdin");
279 /* write whatever we have in memory to tmpfile */
281 write_buff_tmp(bufstart
, bufused
, tmpfile
);
283 if (outname
&& !stat(outname
, &statbuf
)) {
285 if (S_ISREG(statbuf
.st_mode
) && !fclose(tmpfile
)) {
286 if (rename(tmpname
, outname
)) {
287 perror("error renaming temporary file to output file");
292 FILE *outfd
= fopen(outname
, "w");
294 perror("error opening output file");
297 copy_tmpfile(tmpfile
, outfd
);
301 copy_tmpfile(tmpfile
, stdout
);
305 FILE *outfd
= stdout
;
307 outfd
= fopen(outname
, "w");
309 perror("error opening output file");
314 write_buff_out(bufstart
, bufused
, outfd
);