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
45 printf("sponge <file>: soak up all input from stdin and write it to <file>\n");
49 /* all the signal stuff copied from gnu sort */
51 /* The set of signals that are caught. */
52 static sigset_t caught_signals
;
54 /* Critical section status. */
56 int valid
; // was bool
60 /* Enter a critical section. */
61 static struct cs_status
cs_enter (void) {
62 struct cs_status status
;
63 status
.valid
= (sigprocmask(SIG_BLOCK
, &caught_signals
, &status
.sigs
) == 0);
67 /* Leave a critical section. */
68 static void cs_leave (struct cs_status status
) {
70 /* Ignore failure when restoring the signal mask. */
71 sigprocmask(SIG_SETMASK
, &status
.sigs
, NULL
);
75 static void cleanup () {
81 static void onexit_cleanup (void) {
82 struct cs_status cs
= cs_enter();
87 static void sighandler (int sig
) {
97 /* taken from coreutils sort */
98 static size_t default_sponge_size (void) {
99 /* Let MEM be available memory or 1/8 of total memory, whichever
101 double avail
= physmem_available();
102 double total
= physmem_total();
103 double mem
= MAX(avail
, total
/ 8);
104 struct rlimit rlimit
;
106 /* Let SIZE be MEM, but no more than the maximum object size or
107 system resource limits. Avoid the MIN macro here, as it is not
108 quite right when only one argument is floating point. Don't
109 bother to check for values like RLIM_INFINITY since in practice
110 they are not much less than SIZE_MAX. */
111 size_t size
= SIZE_MAX
;
114 if (getrlimit(RLIMIT_DATA
, &rlimit
) == 0 && rlimit
.rlim_cur
< size
)
115 size
= rlimit
.rlim_cur
;
117 if (getrlimit(RLIMIT_AS
, &rlimit
) == 0 && rlimit
.rlim_cur
< size
)
118 size
= rlimit
.rlim_cur
;
121 /* Leave a large safety margin for the above limits, as failure can
122 occur when they are exceeded. */
126 /* Leave a 1/16 margin for RSS to leave room for code, stack, etc.
127 Exceeding RSS is not fatal, but can be quite slow. */
128 if (getrlimit(RLIMIT_RSS
, &rlimit
) == 0 && rlimit
.rlim_cur
/ 16 * 15 < size
)
129 size
= rlimit
.rlim_cur
/ 16 * 15;
132 /* Use no less than the minimum. */
133 return MAX (size
, MIN_SPONGE_SIZE
);
136 void trapsignals (void) {
138 static int const sig
[] = {
139 /* The usual suspects. */
140 SIGALRM
, SIGHUP
, SIGINT
, SIGPIPE
, SIGQUIT
, SIGTERM
,
157 int nsigs
= sizeof(sig
) / sizeof(sig
[0]);
160 struct sigaction act
;
162 sigemptyset(&caught_signals
);
163 for (i
= 0; i
< nsigs
; i
++) {
164 sigaction(sig
[i
], NULL
, &act
);
165 if (act
.sa_handler
!= SIG_IGN
)
166 sigaddset(&caught_signals
, sig
[i
]);
169 act
.sa_handler
= sighandler
;
170 act
.sa_mask
= caught_signals
;
173 for (i
= 0; i
< nsigs
; i
++)
174 if (sigismember(&caught_signals
, sig
[i
]))
175 sigaction(sig
[i
], &act
, NULL
);
177 for (i
= 0; i
< nsigs
; i
++)
178 if (signal(sig
[i
], SIG_IGN
) != SIG_IGN
) {
179 signal(sig
[i
], sighandler
);
180 siginterrupt (sig
[i
], 1);
185 static void write_buff_tmp(char* buff
, size_t length
, FILE *fd
) {
186 if (fwrite(buff
, length
, 1, fd
) < 1) {
187 perror("error writing buffer to temporary file");
193 static void write_buff_tmp_finish (char* buff
, size_t length
, FILE *fd
) {
195 write_buff_tmp(buff
, length
, fd
);
196 if (fflush(fd
) != 0) {
202 static void write_buff_out (char* buff
, size_t length
, FILE *fd
) {
203 if (fwrite(buff
, length
, 1, fd
) < 1) {
204 perror("error writing buffer to output file");
210 static void copy_tmpfile (FILE *tmpfile
, FILE *outfile
, char *buf
, size_t size
) {
212 if (lseek(fileno(tmpfile
), 0, SEEK_SET
)) {
213 perror("could to seek to start of temporary file");
217 while ((i
= read(fileno(tmpfile
), buf
, size
)) > 0) {
218 write_buff_out(buf
, i
, outfile
);
221 perror("read temporary file");
225 if (fclose(tmpfile
) != 0) {
226 perror("read temporary file");
229 if (fclose(outfile
) != 0) {
230 perror("error writing buffer to output file");
235 FILE *open_tmpfile (void) {
241 char const * const template="%s/sponge.XXXXXX";
245 tmpdir
= getenv("TMPDIR");
248 /* Subtract 2 for `%s' and add 1 for the trailing NULL. */
249 tmpname
=malloc(strlen(tmpdir
) + strlen(template) - 2 + 1);
251 perror("failed to allocate memory");
254 sprintf(tmpname
, template, tmpdir
);
256 tmpfd
= mkstemp(tmpname
);
258 atexit(onexit_cleanup
); // solaris on_exit(onexit_cleanup, 0);
262 perror("mkstemp failed");
265 tmpfile
= fdopen(tmpfd
, "w+");
273 int main (int argc
, char **argv
) {
274 char *buf
, *bufstart
, *outname
= NULL
;
275 size_t bufsize
= BUFF_SIZE
;
277 FILE *outfile
, *tmpfile
= 0;
279 size_t mem_available
= default_sponge_size();
282 if (argc
> 2 || (argc
== 2 && strcmp(argv
[1], "-h") == 0)) {
289 tmpfile
= open_tmpfile();
290 bufstart
= buf
= malloc(bufsize
);
292 perror("failed to allocate memory");
295 while ((i
= read(0, buf
, bufsize
- bufused
)) > 0) {
297 if (bufused
== bufsize
) {
298 if ((bufsize
*2) >= mem_available
) {
299 write_buff_tmp(bufstart
, bufused
, tmpfile
);
305 bufstart
= realloc(bufstart
, bufsize
);
307 perror("failed to realloc memory");
312 buf
= bufstart
+ bufused
;
315 perror("failed to read from stdin");
322 int exists
= (lstat(outname
, &statbuf
) == 0);
324 write_buff_tmp_finish(bufstart
, bufused
, tmpfile
);
326 /* Set temp file mode to match either
327 * the old file mode, or the default file
328 * mode for a newly created file. */
330 mode
= statbuf
.st_mode
;
333 mode_t mask
= umask(0);
337 if (chmod(tmpname
, mode
) != 0) {
342 /* If it's a regular file, or does not yet exist,
343 * attempt a fast rename of the temp file. */
345 S_ISREG(statbuf
.st_mode
) &&
346 ! S_ISLNK(statbuf
.st_mode
)
348 rename(tmpname
, outname
) == 0) {
349 tmpname
=NULL
; /* don't try to cleanup tmpname */
352 /* Fall back to slow copy. */
353 outfile
= fopen(outname
, "w");
355 perror("error opening output file");
358 copy_tmpfile(tmpfile
, outfile
, bufstart
, bufsize
);
363 write_buff_tmp_finish(bufstart
, bufused
, tmpfile
);
364 copy_tmpfile(tmpfile
, stdout
, bufstart
, bufsize
);
367 /* buffer direct to stdout, no tmpfile */
368 write_buff_out(bufstart
, bufused
, stdout
);