-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathteetail.c
208 lines (182 loc) · 6.57 KB
/
teetail.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// teetail - tee standard input to standard output and a file, but limit file size
// Copyright (C) 2022 Sergei Lewis
//
// 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 3 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, see <http://www.gnu.org/licenses/>.
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
int usage() {
fprintf(stderr,
"tee standard input to standard output and a file, but limit file size\n"
"\n"
"Usage: teetail -o filename -c buffer_size [-Pq] [-B block_size]\n"
"\n"
"teetail duplicates standard input to standard output\n"
"and also writes the last buffer_size bytes to the named file\n"
"when its standard input reaches EOF\n"
"\n"
"Arguments below are optional.\n"
"-B block_size teetail attempts to perform IO in block_size chunks.\n"
"-P if supplied, teetail tracks current progress\n"
" on standard error.\n"
"-q if supplied, teetail is quiet - *nothing* is echoed\n"
" to stdout.\n"
"\n"
"e.g.\nteetail -o last_log -c 1024\n"
);
return -1;
}
typedef long long int millisecond_t;
millisecond_t current_time_millis(void) {
struct timeval tv;
gettimeofday(&tv,NULL);
return (((millisecond_t)tv.tv_sec)*1000)+(tv.tv_usec/1000);
}
typedef long long unsigned int byte_size_t;
int main( int argc, char **argv ) {
byte_size_t requested_size = 0;
size_t block_size = 1024*1024;
size_t buffer_size = 0;
char const *destfilename = NULL;
int quiet = 0;
int progress = 0;
millisecond_t last_ptime = current_time_millis();
millisecond_t const start_ptime = last_ptime;
if( argc < 5 ) {
return usage();
}
int arg = 1;
while( arg < argc ) {
if( argv[arg][0] != '-' ) {
fprintf(stderr, "failed to parse argument: %s\n", argv[arg]);
return usage();
}
switch(argv[arg][1]) {
case 'o':
if( ++arg >= argc )
return usage();
destfilename = argv[arg];
break;
case 'c':
if( ++arg >= argc )
return usage();
requested_size = atoll(argv[arg]);
buffer_size = (size_t)requested_size;
if( buffer_size != requested_size ) {
fprintf(stderr, "buffer size doesn't fit in size_t!");
return -1;
}
break;
case 'q':
quiet=1;
break;
case 'P':
progress=1;
break;
case 'B':
if( ++arg >= argc )
return usage();
requested_size = atoll(argv[arg]);
block_size = (size_t)requested_size;
if( block_size != requested_size ) {
fprintf(stderr, "block size doesn't fit in size_t!");
return -1;
}
break;
default:
fprintf(stderr, "failed to parse argument: %s\n", argv[arg]);
return usage();
}
++arg;
}
if( !buffer_size || !destfilename ) {
return usage();
}
char *buffer = malloc(buffer_size);
if( !buffer ) {
fprintf(stderr, "error allocating buffer memory\n");
return -1;
}
byte_size_t total = 0;
if( progress && !quiet ) {
fprintf(stderr, "\n");
}
int return_code = 0;
for(;;) {
size_t const head = total % buffer_size;
size_t const available_space = buffer_size - head;
size_t const bytes_to_read = block_size < available_space ? block_size : available_space;
size_t const bytes_read = fread( &buffer[head], sizeof(char), bytes_to_read, stdin );
total += bytes_read;
if(progress) {
millisecond_t ptime = current_time_millis();
if( (ptime - last_ptime) > 250 )
{
char const * unit = "bytes";
byte_size_t displayed_total = total;
if( displayed_total > 1024 ) {
unit = "Kb";
displayed_total = displayed_total/1024;
}
if( displayed_total > 1024 ) {
unit = "Mb";
displayed_total = displayed_total/1024;
}
if( displayed_total > 1024 ) {
unit = "Gb";
displayed_total = displayed_total/1024;
}
double rate = displayed_total / ((ptime - start_ptime)/1000.0);
fprintf( stderr, "\r%llu %s read, %6.2f %s/s ", displayed_total, unit, rate, unit );
fflush( stderr );
last_ptime = ptime;
}
}
if( !quiet ) {
size_t const bytes_written = fwrite( &buffer[head], sizeof(char), bytes_read, stdout );
if( ( bytes_written < bytes_read ) || fflush( stdout ) ) {
fprintf(stderr, "error writing to stdout\n");
return_code = -1;
break;
}
}
if( feof(stdin) )
break;
if( ferror(stdin) ) {
fprintf(stderr, "error reading from stdin\n");
return_code = -1;
break;
}
}
FILE * destfile = fopen(destfilename, "wb");
if( !destfile ) {
fprintf(stderr, "error opening %s for writing\n", destfilename);
return -1;
}
if( total > buffer_size ) {
size_t const head = total % buffer_size;
fwrite( &buffer[head], sizeof(char), buffer_size-head, destfile );
fwrite( &buffer[0], sizeof(char), head, destfile );
} else {
fwrite( &buffer[0], sizeof(char), total, destfile );
}
fflush( destfile );
if( ferror( destfile ) ) {
fprintf(stderr, "error writing to %s\n", destfilename);
return -1;
}
fclose( destfile );
return return_code;
}