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>
41 #define BUFF_SIZE 8192
42 #define MIN_SPONGE_SIZE BUFF_SIZE
46 printf("sponge [-a] <file>: soak up all input from stdin and write it "
51 /* all the signal stuff copied from gnu sort */
53 /* The set of signals that are caught. */
54 static sigset_t caught_signals
;
56 /* Critical section status. */
58 int valid
; // was bool
62 /* Enter a critical section. */
63 static struct cs_status
cs_enter (void) {
64 struct cs_status status
;
65 status
.valid
= (sigprocmask(SIG_BLOCK
, &caught_signals
, &status
.sigs
) == 0);
69 /* Leave a critical section. */
70 static void cs_leave (struct cs_status status
) {
72 /* Ignore failure when restoring the signal mask. */
73 sigprocmask(SIG_SETMASK
, &status
.sigs
, NULL
);
77 static void cleanup () {
83 static void onexit_cleanup (void) {
84 struct cs_status cs
= cs_enter();
89 static void sighandler (int sig
) {
99 /* taken from coreutils sort */
100 static size_t default_sponge_size (void) {
101 /* Let MEM be available memory or 1/8 of total memory, whichever
103 double avail
= physmem_available();
104 double total
= physmem_total();
105 double mem
= MAX(avail
, total
/ 8);
106 struct rlimit rlimit
;
108 /* Let SIZE be MEM, but no more than the maximum object size or
109 system resource limits. Avoid the MIN macro here, as it is not
110 quite right when only one argument is floating point. Don't
111 bother to check for values like RLIM_INFINITY since in practice
112 they are not much less than SIZE_MAX. */
113 size_t size
= SIZE_MAX
;
116 if (getrlimit(RLIMIT_DATA
, &rlimit
) == 0 && rlimit
.rlim_cur
< size
)
117 size
= rlimit
.rlim_cur
;
119 if (getrlimit(RLIMIT_AS
, &rlimit
) == 0 && rlimit
.rlim_cur
< size
)
120 size
= rlimit
.rlim_cur
;
123 /* Leave a large safety margin for the above limits, as failure can
124 occur when they are exceeded. */
128 /* Leave a 1/16 margin for RSS to leave room for code, stack, etc.
129 Exceeding RSS is not fatal, but can be quite slow. */
130 if (getrlimit(RLIMIT_RSS
, &rlimit
) == 0 && rlimit
.rlim_cur
/ 16 * 15 < size
)
131 size
= rlimit
.rlim_cur
/ 16 * 15;
134 /* Use no less than the minimum. */
135 return MAX (size
, MIN_SPONGE_SIZE
);
138 void trapsignals (void) {
140 static int const sig
[] = {
141 /* The usual suspects. */
142 SIGALRM
, SIGHUP
, SIGINT
, SIGPIPE
, SIGQUIT
, SIGTERM
,
159 int nsigs
= sizeof(sig
) / sizeof(sig
[0]);
162 struct sigaction act
;
164 sigemptyset(&caught_signals
);
165 for (i
= 0; i
< nsigs
; i
++) {
166 sigaction(sig
[i
], NULL
, &act
);
167 if (act
.sa_handler
!= SIG_IGN
)
168 sigaddset(&caught_signals
, sig
[i
]);
171 act
.sa_handler
= sighandler
;
172 act
.sa_mask
= caught_signals
;
175 for (i
= 0; i
< nsigs
; i
++)
176 if (sigismember(&caught_signals
, sig
[i
]))
177 sigaction(sig
[i
], &act
, NULL
);
179 for (i
= 0; i
< nsigs
; i
++)
180 if (signal(sig
[i
], SIG_IGN
) != SIG_IGN
) {
181 signal(sig
[i
], sighandler
);
182 siginterrupt (sig
[i
], 1);
187 static void write_buff_tmp(char* buff
, size_t length
, FILE *fd
) {
188 if (fwrite(buff
, length
, 1, fd
) < 1) {
189 perror("error writing buffer to temporary file");
195 static void write_buff_tmp_finish (char* buff
, size_t length
, FILE *fd
) {
197 write_buff_tmp(buff
, length
, fd
);
198 if (fflush(fd
) != 0) {
204 static void write_buff_out (char* buff
, size_t length
, FILE *fd
) {
205 if (fwrite(buff
, length
, 1, fd
) < 1) {
206 perror("error writing buffer to output file");
212 static void copy_file (FILE *infile
, FILE *outfile
, char *buf
, size_t size
) {
214 while ((i
= read(fileno(infile
), buf
, size
)) > 0) {
215 write_buff_out(buf
, i
, outfile
);
224 static void copy_tmpfile (FILE *tmpfile
, FILE *outfile
, char *buf
, size_t size
) {
225 if (lseek(fileno(tmpfile
), 0, SEEK_SET
)) {
226 perror("could to seek to start of file");
230 copy_file(tmpfile
, outfile
, buf
, size
);
231 if (fclose(tmpfile
) != 0) {
232 perror("read temporary file");
235 if (fclose(outfile
) != 0) {
236 perror("error writing buffer to output file");
241 FILE *open_tmpfile (void) {
247 char const * const template="%s/sponge.XXXXXX";
251 tmpdir
= getenv("TMPDIR");
254 /* Subtract 2 for `%s' and add 1 for the trailing NULL. */
255 tmpname
=malloc(strlen(tmpdir
) + strlen(template) - 2 + 1);
257 perror("failed to allocate memory");
260 sprintf(tmpname
, template, tmpdir
);
262 tmpfd
= mkstemp(tmpname
);
264 atexit(onexit_cleanup
); // solaris on_exit(onexit_cleanup, 0);
268 perror("mkstemp failed");
271 tmpfile
= fdopen(tmpfd
, "w+");
279 int main (int argc
, char **argv
) {
280 char *buf
, *bufstart
, *outname
= NULL
;
281 size_t bufsize
= BUFF_SIZE
;
283 FILE *outfile
, *tmpfile
= 0;
285 size_t mem_available
= default_sponge_size();
290 while ((opt
= getopt(argc
, argv
, "ha")) != -1) {
300 outname
= argv
[optind
];
302 tmpfile
= open_tmpfile();
303 bufstart
= buf
= malloc(bufsize
);
305 perror("failed to allocate memory");
308 while ((i
= read(0, buf
, bufsize
- bufused
)) > 0) {
310 if (bufused
== bufsize
) {
311 if ((bufsize
*2) >= mem_available
) {
312 write_buff_tmp(bufstart
, bufused
, tmpfile
);
318 bufstart
= realloc(bufstart
, bufsize
);
320 perror("failed to realloc memory");
325 buf
= bufstart
+ bufused
;
328 perror("failed to read from stdin");
335 int exists
= (lstat(outname
, &statbuf
) == 0);
336 int regfile
= exists
&& S_ISREG(statbuf
.st_mode
) && ! S_ISLNK(statbuf
.st_mode
);
338 if (append
&& regfile
) {
339 char *tmpbuf
= malloc(bufsize
);
341 perror("failed to allocate memory");
344 outfile
= fopen(outname
, "r");
345 copy_file(outfile
, tmpfile
, tmpbuf
, bufsize
);
349 write_buff_tmp_finish(bufstart
, bufused
, tmpfile
);
351 /* Set temp file mode to match either
352 * the old file mode, or the default file
353 * mode for a newly created file. */
355 mode
= statbuf
.st_mode
;
358 mode_t mask
= umask(0);
362 if (chmod(tmpname
, mode
) != 0) {
367 /* If it's a regular file, or does not yet exist,
368 * attempt a fast rename of the temp file. */
369 if ((regfile
|| ! exists
) &&
370 rename(tmpname
, outname
) == 0) {
371 tmpname
=NULL
; /* don't try to cleanup tmpname */
374 /* Fall back to slow copy. */
375 outfile
= fopen(outname
, append
? "a" : "w");
377 perror("error opening output file");
380 copy_tmpfile(tmpfile
, outfile
, bufstart
, bufsize
);
385 write_buff_tmp_finish(bufstart
, bufused
, tmpfile
);
386 copy_tmpfile(tmpfile
, stdout
, bufstart
, bufsize
);
389 /* buffer direct to stdout, no tmpfile */
390 write_buff_out(bufstart
, bufused
, stdout
);