aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenys Vlasenko <vda.linux@googlemail.com>2026-01-26 04:48:28 +0100
committerDenys Vlasenko <vda.linux@googlemail.com>2026-01-26 04:48:28 +0100
commit4da1badf1319c008397ec258f0601f5014332cb3 (patch)
tree48e41c4380cc7319e7d4fef13fbbccc25ecf3cb4
parent052b25369642b18492933d2f3c8278df55fad7c0 (diff)
downloadbusybox-w32-4da1badf1319c008397ec258f0601f5014332cb3.tar.gz
busybox-w32-4da1badf1319c008397ec258f0601f5014332cb3.tar.bz2
busybox-w32-4da1badf1319c008397ec258f0601f5014332cb3.zip
networking/httpd_indexcgi.c: non-mallocing version
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rwxr-xr-xnetworking/httpd_helpers.sh19
-rw-r--r--networking/httpd_indexcgi.c289
2 files changed, 200 insertions, 108 deletions
diff --git a/networking/httpd_helpers.sh b/networking/httpd_helpers.sh
index 038d2d27f..1962575ab 100755
--- a/networking/httpd_helpers.sh
+++ b/networking/httpd_helpers.sh
@@ -13,18 +13,23 @@ OPTS="-static -static-libgcc \
13-march=i386 -mpreferred-stack-boundary=2 \ 13-march=i386 -mpreferred-stack-boundary=2 \
14-Wl,--warn-common -Wl,--sort-common -Wl,--gc-sections" 14-Wl,--warn-common -Wl,--sort-common -Wl,--gc-sections"
15 15
16${PREFIX}gcc \ 16${PREFIX}gcc ${OPTS} \
17${OPTS} \
18-Wl,-Map -Wl,index.cgi.map \ 17-Wl,-Map -Wl,index.cgi.map \
19httpd_indexcgi.c -o index.cgi && strip index.cgi 18httpd_indexcgi.c -o index.cgi \
19 && \
20${PREFIX}gcc ${OPTS} \
21httpd_indexcgi.c -S -fverbose-asm \
22 && \
23${PREFIX}gcc ${OPTS} \
24httpd_indexcgi.c -c \
25 && \
26strip index.cgi \
20 27
21${PREFIX}gcc \ 28${PREFIX}gcc ${OPTS} \
22${OPTS} \
23-Wl,-Map -Wl,httpd_ssi.map \ 29-Wl,-Map -Wl,httpd_ssi.map \
24httpd_ssi.c -o httpd_ssi && strip httpd_ssi 30httpd_ssi.c -o httpd_ssi && strip httpd_ssi
25 31
26${PREFIX}gcc \ 32${PREFIX}gcc ${OPTS} \
27${OPTS} \
28-Wl,-Map -Wl,httpd_ratelimit_cgi.map \ 33-Wl,-Map -Wl,httpd_ratelimit_cgi.map \
29httpd_ratelimit_cgi.c -o httpd_ratelimit_cgi && strip httpd_ratelimit_cgi 34httpd_ratelimit_cgi.c -o httpd_ratelimit_cgi && strip httpd_ratelimit_cgi
30 35
diff --git a/networking/httpd_indexcgi.c b/networking/httpd_indexcgi.c
index edaaad566..3965bb5e0 100644
--- a/networking/httpd_indexcgi.c
+++ b/networking/httpd_indexcgi.c
@@ -28,11 +28,18 @@ httpd_indexcgi.c -o index.cgi
28/* We don't use printf, as it pulls in >12 kb of code from uclibc (i386). */ 28/* We don't use printf, as it pulls in >12 kb of code from uclibc (i386). */
29/* Currently malloc machinery is the biggest part of libc we pull in. */ 29/* Currently malloc machinery is the biggest part of libc we pull in. */
30/* We have only one realloc and one strdup, any idea how to do without? */ 30/* We have only one realloc and one strdup, any idea how to do without? */
31/* 2026: */
32/* Answer: by using getdents and mmap */
31 33
32/* Size (i386, static uclibc, approximate): 34/* Sizes
35 * 2007: i386, static uclibc, approximate:
33 * text data bss dec hex filename 36 * text data bss dec hex filename
34 * 13036 44 3052 16132 3f04 index.cgi 37 * 13036 44 3052 16132 3f04 index.cgi
35 * 2576 4 2048 4628 1214 index.cgi.o 38 * 2576 4 2048 4628 1214 index.cgi.o
39 * 2026: i386, static musl, rewritten to not use malloc:
40 * 19222 44 2988 22254 56ee index.cgi_before_mmap_rewrite
41 * 12790 40 416 13246 33be index.cgi
42 * 3291 0 4 3295 cdf index.cgi.o
36 */ 43 */
37 44
38#define _GNU_SOURCE 1 /* for strchrnul */ 45#define _GNU_SOURCE 1 /* for strchrnul */
@@ -46,6 +53,23 @@ httpd_indexcgi.c -o index.cgi
46#include <stdio.h> 53#include <stdio.h>
47#include <dirent.h> 54#include <dirent.h>
48#include <time.h> 55#include <time.h>
56#include <sys/mman.h>
57
58/* In order to avoid using malloc, we have to avoid opendir()
59 * which in most implementations allocates its return value.
60 * Thus, have to use Linux's getdents syscall directly.
61 * It has an added benefit of a more efficient storage of filenames
62 * (no pointers needed).
63 */
64#include <fcntl.h>
65#include <sys/syscall.h>
66struct linux_dirent64 {
67 ino64_t d_ino;
68 off64_t d_off;
69 unsigned short d_reclen;
70 unsigned char d_type;
71 char d_name[];
72};
49 73
50/* Appearance of the table is controlled by style sheet *ONLY*, 74/* Appearance of the table is controlled by style sheet *ONLY*,
51 * formatting code uses <TAG class=CLASS> to apply style 75 * formatting code uses <TAG class=CLASS> to apply style
@@ -89,98 +113,123 @@ httpd_indexcgi.c -o index.cgi
89"col.dt { width:1% }" \ 113"col.dt { width:1% }" \
90"</style>" \ 114"</style>" \
91 115
92typedef struct dir_list_t { 116typedef struct linux_dirent64 dir_list_t;
93 char *dl_name; 117/* reuse these fields for storing other values: */
94 mode_t dl_mode; 118#define D_MODE d_reclen
95 off_t dl_size; 119#define D_SIZE d_off
96 time_t dl_mtime; 120#define D_MTIME d_ino
97} dir_list_t;
98 121
99static int compare_dl(dir_list_t *a, dir_list_t *b) 122static int compare_dl(dir_list_t **aa, dir_list_t **bb)
100{ 123{
124 dir_list_t *a = *aa;
125 dir_list_t *b = *bb;
126
101 /* ".." is 'less than' any other dir entry */ 127 /* ".." is 'less than' any other dir entry */
102 if (strcmp(a->dl_name, "..") == 0) { 128 if (strcmp(a->d_name, "..") == 0) {
103 return -1; 129 return -1;
104 } 130 }
105 if (strcmp(b->dl_name, "..") == 0) { 131 if (strcmp(b->d_name, "..") == 0) {
106 return 1; 132 return 1;
107 } 133 }
108 if (S_ISDIR(a->dl_mode) != S_ISDIR(b->dl_mode)) { 134 if (S_ISDIR(a->D_MODE) != S_ISDIR(b->D_MODE)) {
109 /* 1 if b is a dir (and thus a is 'after' b, a > b), 135 /* 1 if b is a dir (and thus a is 'after' b, a > b),
110 * else -1 (a < b) */ 136 * else -1 (a < b) */
111 return (S_ISDIR(b->dl_mode) != 0) ? 1 : -1; 137 return (S_ISDIR(b->D_MODE) != 0) ? 1 : -1;
112 } 138 }
113 return strcmp(a->dl_name, b->dl_name); 139 return strcmp(a->d_name, b->d_name);
114} 140}
115 141
116static char buffer[2*1024 > sizeof(STYLE_STR) ? 2*1024 : sizeof(STYLE_STR)];
117static char *dst = buffer;
118enum { 142enum {
119 BUFFER_SIZE = sizeof(buffer), 143 /* Must be >= 64k (all Linux arches have pages <= 64k) */
144 BUFFER_SIZE = 8 * 1024*1024,
145//one dirent is typically <= 100 bytes, 1M is enough for ~10k files
146//FIXME: change code to *iterate* getdents64 if need to support giant file lists
120 HEADROOM = 64, 147 HEADROOM = 64,
121}; 148};
122 149
150static char *buffer;
151#if 0
152/* Use global "dst" pointer */
153static char *dst;
154# define INIT_DST dst = buffer;
155# define CHARP void
156# define CHARP_DST /*nothing*/
157# define DST /*nothing*/
158# define SET_DST /*nothing*/
159# define RETURN_DST ((void)0)
160#else
161/* Propagate "dst" pointer as parameter */
162/* This is usually more efficient (uses a register for it) */
163# define INIT_DST char *dst = buffer;
164# define CHARP char*
165# define CHARP_DST char *dst,
166# define DST dst,
167# define SET_DST dst =
168# define RETURN_DST return dst
169#endif
170
123/* After this call, you have at least size + HEADROOM bytes available 171/* After this call, you have at least size + HEADROOM bytes available
124 * ahead of dst */ 172 * ahead of dst */
125static void guarantee(int size) 173static CHARP guarantee(CHARP_DST int size)
126{ 174{
127 if (buffer + (BUFFER_SIZE-HEADROOM) - dst >= size) 175 if (buffer + (BUFFER_SIZE-HEADROOM) - dst < size) {
128 return; 176 write(STDOUT_FILENO, buffer, dst - buffer);
129 write(STDOUT_FILENO, buffer, dst - buffer); 177 dst = buffer;
130 dst = buffer; 178 }
179 RETURN_DST;
131} 180}
132 181
133/* NB: formatters do not store terminating NUL! */ 182/* NB: formatters do not store terminating NUL! */
134 183
135/* HEADROOM bytes are available after dst after this call */ 184static CHARP fmt_str(CHARP_DST const char *src)
136static void fmt_str(/*char *dst,*/ const char *src)
137{ 185{
138 unsigned len = strlen(src); 186 unsigned len = strlen(src);
139 guarantee(len); 187 SET_DST guarantee(DST len);
140 memcpy(dst, src, len); 188 dst = mempcpy(dst, src, len);
141 dst += len; 189 RETURN_DST;
142} 190}
143 191
144/* HEADROOM bytes after dst are available after this call */ 192/* HEADROOM bytes after dst are available after this call */
145static void fmt_url(/*char *dst,*/ const char *name) 193static CHARP fmt_url(CHARP_DST const char *name)
146{ 194{
147 while (*name) { 195 while (*name) {
148 unsigned c = *name++; 196 unsigned c = (unsigned char)*name++;
149 guarantee(3); 197 SET_DST guarantee(DST 3);
150 *dst = c;
151 if ((c - '0') > 9 /* not a digit */ 198 if ((c - '0') > 9 /* not a digit */
152 && ((c|0x20) - 'a') > ('z' - 'a') /* not A-Z or a-z */ 199 && ((c|0x20) - 'a') > ('z' - 'a') /* not A-Z or a-z */
153 && !strchr("._-+@", c) 200 && !strchr("._-+@", c)
154 ) { 201 ) {
155 *dst++ = '%'; 202 *dst++ = '%';
156 *dst++ = "0123456789ABCDEF"[c >> 4]; 203 *dst++ = "0123456789ABCDEF"[c >> 4];
157 *dst = "0123456789ABCDEF"[c & 0xf]; 204 c = "0123456789ABCDEF"[c & 0xf];
158 } 205 }
159 dst++; 206 *dst++ = c;
160 } 207 }
208 RETURN_DST;
161} 209}
162 210
163/* HEADROOM bytes are available after dst after this call */ 211/* HEADROOM bytes are available after dst after this call */
164static void fmt_html(/*char *dst,*/ const char *name) 212static CHARP fmt_html(CHARP_DST const char *name)
165{ 213{
166 while (*name) { 214 while (*name) {
167 char c = *name++; 215 char c = *name++;
168 if (c == '<') 216 if (c == '<')
169 fmt_str("&lt;"); 217 SET_DST fmt_str(DST "&lt;");
170 else if (c == '>') 218 else if (c == '>')
171 fmt_str("&gt;"); 219 SET_DST fmt_str(DST "&gt;");
172 else if (c == '&') { 220 else if (c == '&') {
173 fmt_str("&amp;"); 221 SET_DST fmt_str(DST "&amp;");
174 } else { 222 } else {
175 guarantee(1); 223 SET_DST guarantee(DST 1);
176 *dst++ = c; 224 *dst++ = c;
177 continue; 225 continue;
178 } 226 }
179 } 227 }
228 RETURN_DST;
180} 229}
181 230
182/* HEADROOM bytes are available after dst after this call */ 231/* HEADROOM bytes are available after dst after this call */
183static void fmt_ull(/*char *dst,*/ unsigned long long n) 232static CHARP fmt_ull(CHARP_DST unsigned long long n)
184{ 233{
185 char buf[sizeof(n)*3 + 2]; 234 char buf[sizeof(n)*3 + 2];
186 char *p; 235 char *p;
@@ -191,36 +240,40 @@ static void fmt_ull(/*char *dst,*/ unsigned long long n)
191 *--p = (n % 10) + '0'; 240 *--p = (n % 10) + '0';
192 n /= 10; 241 n /= 10;
193 } while (n); 242 } while (n);
194 fmt_str(/*dst,*/ p); 243 SET_DST fmt_str(DST p);
244 RETURN_DST;
195} 245}
196 246
197/* Does not call guarantee - eats into headroom instead */ 247/* Does not call guarantee - eats into headroom instead */
198static void fmt_02u(/*char *dst,*/ unsigned n) 248static CHARP fmt_02u(CHARP_DST unsigned n)
199{ 249{
200 /* n %= 100; - not needed, callers don't pass big n */ 250 /* n %= 100; - not needed, callers don't pass big n */
201 dst[0] = (n / 10) + '0'; 251 dst[0] = (n / 10) + '0';
202 dst[1] = (n % 10) + '0'; 252 dst[1] = (n % 10) + '0';
203 dst += 2; 253 dst += 2;
254 RETURN_DST;
204} 255}
205 256
206/* Does not call guarantee - eats into headroom instead */ 257/* Does not call guarantee - eats into headroom instead */
207static void fmt_04u(/*char *dst,*/ unsigned n) 258static CHARP fmt_04u(CHARP_DST unsigned n)
208{ 259{
209 /* n %= 10000; - not needed, callers don't pass big n */ 260 /* n %= 10000; - not needed, callers don't pass big n */
210 fmt_02u(n / 100); 261 SET_DST fmt_02u(DST n / 100);
211 fmt_02u(n % 100); 262 SET_DST fmt_02u(DST n % 100);
263 RETURN_DST;
212} 264}
213 265
214int main(int argc, char **argv) 266int main(int argc, char **argv)
215{ 267{
216 dir_list_t *dir_list; 268 char *location;
269 dir_list_t **dir_list;
217 dir_list_t *cdir; 270 dir_list_t *cdir;
218 unsigned dir_list_count; 271 int dir_list_count;
219 unsigned count_dirs; 272 unsigned count_dirs;
220 unsigned count_files; 273 unsigned count_files;
221 unsigned long long size_total; 274 unsigned long long size_total;
222 DIR *dirp; 275 int dirfd;
223 char *location; 276 long nread, byte_pos;
224 277
225 location = getenv("REQUEST_URI"); 278 location = getenv("REQUEST_URI");
226 if (!location) 279 if (!location)
@@ -243,47 +296,75 @@ int main(int argc, char **argv)
243 return 1; 296 return 1;
244 } 297 }
245 298
246 dirp = opendir("."); 299 dirfd = open(".", O_RDONLY | O_DIRECTORY);
247 if (!dirp) 300 if (dirfd == -1)
301 return - dirfd;
302
303 /* Allocate 3 BUFFER_SIZE regions, with holes between them:
304 * 1: *dir_list_t[] array
305 * 2: sequence of dir_list_t's as read by getdents (not an array - variable length items)
306 * 3: output char buffer
307 * Importantly, the mmap size is large, but mmap is NOT physically preallocated.
308 * THis way, we'll use only three pages of data is there are not that many files
309 * (the rest of mapped regions will be left unmapped, since it is not accessed).
310 */
311//TODO: add MAP_NORESERVE, MAP_UNINITIALIZED flags?
312 buffer = mmap(NULL, 5*BUFFER_SIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
313 if (buffer == MAP_FAILED)
248 return 1; 314 return 1;
249 dir_list = NULL; 315
316 dir_list = (void*)buffer;
317 munmap(buffer + BUFFER_SIZE, BUFFER_SIZE);
318
319 buffer += 2*BUFFER_SIZE;
320 munmap(buffer + BUFFER_SIZE, BUFFER_SIZE);
321
322 nread = syscall(SYS_getdents64, dirfd, buffer, BUFFER_SIZE);
323 if (nread <= 0)
324 return - nread;
325//FIXME: we simply won't see any files which did not fit into BUFFER_SIZE
326
250 dir_list_count = 0; 327 dir_list_count = 0;
251 while (1) { 328 byte_pos = 0;
252 struct dirent *dp; 329 while (byte_pos < nread) {
330 struct linux_dirent64 *dp;
253 struct stat sb; 331 struct stat sb;
254 332
255 dp = readdir(dirp); 333 dp = (struct linux_dirent64 *) (buffer + byte_pos);
256 if (!dp) 334 byte_pos += dp->d_reclen; /* NB: using this before overwriting it with mode! */
257 break; 335
258 if (dp->d_name[0] == '.' && !dp->d_name[1]) 336 if (dp->d_name[0] == '.' && !dp->d_name[1])
259 continue; 337 continue;
260 if (stat(dp->d_name, &sb) != 0) 338 if (stat(dp->d_name, &sb) != 0)
261 continue; 339 continue;
262 dir_list = realloc(dir_list, (dir_list_count + 1) * sizeof(dir_list[0])); 340//fprintf(stderr, "%d '%s'\n", dir_list_count, dp->d_name);
263 dir_list[dir_list_count].dl_name = strdup(dp->d_name); 341
264 dir_list[dir_list_count].dl_mode = sb.st_mode; 342 dp->d_off = sb.st_size;
265 dir_list[dir_list_count].dl_size = sb.st_size; 343 dp->d_reclen = sb.st_mode;
266 dir_list[dir_list_count].dl_mtime = sb.st_mtime; 344 dp->d_ino = sb.st_mtime;
345 dir_list[dir_list_count] = dp;
267 dir_list_count++; 346 dir_list_count++;
268 } 347 }
269 closedir(dirp); 348 //close(dirfd);
270
271 qsort(dir_list, dir_list_count, sizeof(dir_list[0]), (void*)compare_dl); 349 qsort(dir_list, dir_list_count, sizeof(dir_list[0]), (void*)compare_dl);
272 350
273 fmt_str( 351 buffer += 2*BUFFER_SIZE;
352 {
353 INIT_DST
354 SET_DST fmt_str(DST
274 "" /* Additional headers (currently none) */ 355 "" /* Additional headers (currently none) */
275 "\r\n" /* Mandatory empty line after headers */ 356 "\r\n" /* Mandatory empty line after headers */
276 "<html><head><title>Index of "); 357 "<html><head><title>Index of ");
277 /* Guard against directories with &, > etc */ 358 /* Guard against directories with &, > etc */
278 fmt_html(location); 359 SET_DST fmt_html(DST location);
279 fmt_str( 360 SET_DST fmt_str(DST
280 "</title>\n" 361 "</title>\n"
281 STYLE_STR 362 STYLE_STR
282 "</head>" "\n" 363 "</head>" "\n"
283 "<body>" "\n" 364 "<body>" "\n"
284 "<h1>Index of "); 365 "<h1>Index of ");
285 fmt_html(location); 366 SET_DST fmt_html(DST location);
286 fmt_str( 367 SET_DST fmt_str(DST
287 "</h1>" "\n" 368 "</h1>" "\n"
288 "<table>" "\n" 369 "<table>" "\n"
289 "<col class=nm><col class=sz><col class=dt>" "\n" 370 "<col class=nm><col class=sz><col class=dt>" "\n"
@@ -292,53 +373,59 @@ int main(int argc, char **argv)
292 count_dirs = 0; 373 count_dirs = 0;
293 count_files = 0; 374 count_files = 0;
294 size_total = 0; 375 size_total = 0;
295 cdir = dir_list; 376 while (--dir_list_count >= 0) {
296 while (dir_list_count--) {
297 struct tm *ptm; 377 struct tm *ptm;
378 time_t tt;
298 379
299 if (S_ISDIR(cdir->dl_mode)) { 380 cdir = *dir_list++;
381//fprintf(stderr, "%d '%s'\n", dir_list_count, cdir->d_name);
382 if (S_ISDIR(cdir->D_MODE)) {
300 count_dirs++; 383 count_dirs++;
301 } else if (S_ISREG(cdir->dl_mode)) { 384 } else if (S_ISREG(cdir->D_MODE)) {
302 count_files++; 385 count_files++;
303 size_total += cdir->dl_size; 386 size_total += cdir->D_SIZE;
304 } else 387 } else
305 goto next; 388 continue;
389//fprintf(stderr, "%d '%s'\n", dir_list_count, cdir->d_name);
306 390
307 fmt_str("<tr><td class=nm><a href='"); 391 SET_DST fmt_str(DST "<tr><td class=nm><a href='");
308 fmt_url(cdir->dl_name); /* %20 etc */ 392 SET_DST fmt_url(DST cdir->d_name); /* %20 etc */
309 if (S_ISDIR(cdir->dl_mode)) 393 if (S_ISDIR(cdir->D_MODE))
310 *dst++ = '/'; 394 *dst++ = '/';
311 fmt_str("'>"); 395 SET_DST fmt_str(DST "'>");
312 fmt_html(cdir->dl_name); /* &lt; etc */ 396 SET_DST fmt_html(DST cdir->d_name); /* &lt; etc */
313 if (S_ISDIR(cdir->dl_mode)) 397 if (S_ISDIR(cdir->D_MODE))
314 *dst++ = '/'; 398 *dst++ = '/';
315 fmt_str("</a><td class=sz>"); 399 SET_DST fmt_str(DST "</a><td class=sz>");
316 if (S_ISREG(cdir->dl_mode)) 400 if (S_ISREG(cdir->D_MODE))
317 fmt_ull(cdir->dl_size); 401 SET_DST fmt_ull(DST cdir->D_SIZE);
318 fmt_str("<td class=dt>"); 402 SET_DST fmt_str(DST "<td class=dt>");
319 ptm = gmtime(&cdir->dl_mtime); 403 if (sizeof(cdir->D_MTIME) == sizeof(tt))
320 fmt_04u(1900 + ptm->tm_year); *dst++ = '-'; 404 ptm = gmtime(&cdir->D_MTIME);
321 fmt_02u(ptm->tm_mon + 1); *dst++ = '-'; 405 else {
322 fmt_02u(ptm->tm_mday); *dst++ = ' '; 406 tt = cdir->D_MTIME;
323 fmt_02u(ptm->tm_hour); *dst++ = ':'; 407 ptm = gmtime(&tt);
324 fmt_02u(ptm->tm_min); *dst++ = ':'; 408 }
325 fmt_02u(ptm->tm_sec); 409 SET_DST fmt_04u(DST 1900 + ptm->tm_year); *dst++ = '-';
410 SET_DST fmt_02u(DST ptm->tm_mon + 1); *dst++ = '-';
411 SET_DST fmt_02u(DST ptm->tm_mday); *dst++ = ' ';
412 SET_DST fmt_02u(DST ptm->tm_hour); *dst++ = ':';
413 SET_DST fmt_02u(DST ptm->tm_min); *dst++ = ':';
414 SET_DST fmt_02u(DST ptm->tm_sec);
326 *dst++ = '\n'; 415 *dst++ = '\n';
327
328 next:
329 cdir++;
330 } 416 }
331 417
332 fmt_str("<tr class=foot><th class=cnt>Files: "); 418 SET_DST fmt_str(DST "<tr class=foot><th class=cnt>Files: ");
333 fmt_ull(count_files); 419 SET_DST fmt_ull(DST count_files);
334 /* count_dirs - 1: we don't want to count ".." */ 420 /* count_dirs - 1: we don't want to count ".." */
335 fmt_str(", directories: "); 421 SET_DST fmt_str(DST ", directories: ");
336 fmt_ull(count_dirs - 1); 422 SET_DST fmt_ull(DST count_dirs - 1);
337 fmt_str("<th class=sz>"); 423 SET_DST fmt_str(DST "<th class=sz>");
338 fmt_ull(size_total); 424 SET_DST fmt_ull(DST size_total);
339 fmt_str("<th class=dt>\n"); 425 SET_DST fmt_str(DST "<th class=dt>\n");
340 /* "</table></body></html>" - why bother? */ 426 /* "</table></body></html>" - why bother? */
341 guarantee(BUFFER_SIZE * 2); /* flush */ 427 SET_DST guarantee(DST BUFFER_SIZE * 2); /* flush */
342 428
343 return 0; 429 return 0;
430 }
344} 431}