Quell a GCC warning about a NULL format string on FreeBSD.
[polipo.git] / local.c
blobf59ab63fb62e9bacc04c9d141e2f0434dcbae976
1 /*
2 Copyright (c) 2003-2006 by Juliusz Chroboczek
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
23 #include "polipo.h"
25 int disableLocalInterface = 0;
26 int disableConfiguration = 0;
27 int disableIndexing = 1;
28 int disableServersList = 1;
30 AtomPtr atomInitForbidden;
31 AtomPtr atomReopenLog;
32 AtomPtr atomDiscardObjects;
33 AtomPtr atomWriteoutObjects;
34 AtomPtr atomFreeChunkArenas;
36 void
37 preinitLocal()
39 atomInitForbidden = internAtom("init-forbidden");
40 atomReopenLog = internAtom("reopen-log");
41 atomDiscardObjects = internAtom("discard-objects");
42 atomWriteoutObjects = internAtom("writeout-objects");
43 atomFreeChunkArenas = internAtom("free-chunk-arenas");
45 /* These should not be settable for obvious reasons */
46 CONFIG_VARIABLE(disableLocalInterface, CONFIG_BOOLEAN,
47 "Disable the local configuration pages.");
48 CONFIG_VARIABLE(disableConfiguration, CONFIG_BOOLEAN,
49 "Disable reconfiguring Polipo at runtime.");
50 CONFIG_VARIABLE(disableIndexing, CONFIG_BOOLEAN,
51 "Disable indexing of the local cache.");
52 CONFIG_VARIABLE(disableServersList, CONFIG_BOOLEAN,
53 "Disable the list of known servers.");
56 static void fillSpecialObject(ObjectPtr, void (*)(FILE*, char*), void*);
58 int
59 httpLocalRequest(ObjectPtr object, int method, int from, int to,
60 HTTPRequestPtr requestor, void *closure)
62 if(object->requestor == NULL)
63 object->requestor = requestor;
65 if(!disableLocalInterface && urlIsSpecial(object->key, object->key_size))
66 return httpSpecialRequest(object, method, from, to,
67 requestor, closure);
69 if(method >= METHOD_POST) {
70 httpClientDiscardBody(requestor->connection);
71 httpClientError(requestor, 405, internAtom("Method not allowed"));
72 return 1;
75 /* objectFillFromDisk already did the real work but we have to
76 make sure we don't get into an infinite loop. */
77 if(object->flags & OBJECT_INITIAL) {
78 abortObject(object, 404, internAtom("Not found"));
80 object->age = current_time.tv_sec;
81 object->date = current_time.tv_sec;
83 object->flags &= ~OBJECT_VALIDATING;
84 notifyObject(object);
85 return 1;
88 void
89 alternatingHttpStyle(FILE *out, char *id)
91 fprintf(out,
92 "<style type=\"text/css\">\n"
93 "#%s tbody tr.even td { background-color: #eee; }\n"
94 "#%s tbody tr.odd td { background-color: #fff; }\n"
95 "</style>\n", id, id);
98 static void
99 printConfig(FILE *out, char *dummy)
101 fprintf(out,
102 "<!DOCTYPE HTML PUBLIC "
103 "\"-//W3C//DTD HTML 4.01 Transitional//EN\" "
104 "\"http://www.w3.org/TR/html4/loose.dtd\">\n"
105 "<html><head>\n"
106 "<title>Polipo configuration</title>\n"
107 "</head><body>\n"
108 "<h1>Polipo configuration</h1>\n");
109 printConfigVariables(out, 1);
110 fprintf(out, "<p><a href=\"/polipo/\">back</a></p>");
111 fprintf(out, "</body></html>\n");
114 #ifndef NO_DISK_CACHE
116 static void
117 recursiveIndexDiskObjects(FILE *out, char *root)
119 indexDiskObjects(out, root, 1);
122 static void
123 plainIndexDiskObjects(FILE *out, char *root)
125 indexDiskObjects(out, root, 0);
127 #endif
129 static void
130 serversList(FILE *out, char *dummy)
132 listServers(out);
135 static int
136 matchUrl(char *base, ObjectPtr object)
138 int n = strlen(base);
139 if(object->key_size < n)
140 return 0;
141 if(memcmp(base, object->key, n) != 0)
142 return 0;
143 return (object->key_size == n) || (((char*)object->key)[n] == '?');
146 int
147 httpSpecialRequest(ObjectPtr object, int method, int from, int to,
148 HTTPRequestPtr requestor, void *closure)
150 char buffer[1024];
151 int hlen;
153 if(method >= METHOD_POST) {
154 return httpSpecialSideRequest(object, method, from, to,
155 requestor, closure);
158 if(!(object->flags & OBJECT_INITIAL)) {
159 privatiseObject(object, 0);
160 supersedeObject(object);
161 object->flags &= ~(OBJECT_VALIDATING | OBJECT_INPROGRESS);
162 notifyObject(object);
163 return 1;
166 hlen = snnprintf(buffer, 0, 1024,
167 "\r\nServer: polipo"
168 "\r\nContent-Type: text/html");
169 object->date = current_time.tv_sec;
170 object->age = current_time.tv_sec;
171 object->headers = internAtomN(buffer, hlen);
172 object->code = 200;
173 object->message = internAtom("Okay");
174 object->flags &= ~OBJECT_INITIAL;
175 object->flags |= OBJECT_DYNAMIC;
177 if(object->key_size == 8 && memcmp(object->key, "/polipo/", 8) == 0) {
178 objectPrintf(object, 0,
179 "<!DOCTYPE HTML PUBLIC "
180 "\"-//W3C//DTD HTML 4.01 Transitional//EN\" "
181 "\"http://www.w3.org/TR/html4/loose.dtd\">\n"
182 "<html><head>\n"
183 "<title>Polipo</title>\n"
184 "</head><body>\n"
185 "<h1>Polipo</h1>\n"
186 "<p><a href=\"status?\">Status report</a>.</p>\n"
187 "<p><a href=\"config?\">Current configuration</a>.</p>\n"
188 "<p><a href=\"servers?\">Known servers</a>.</p>\n"
189 #ifndef NO_DISK_CACHE
190 "<p><a href=\"index?\">Disk cache index</a>.</p>\n"
191 #endif
192 "</body></html>\n");
193 object->length = object->size;
194 } else if(matchUrl("/polipo/status", object)) {
195 objectPrintf(object, 0,
196 "<!DOCTYPE HTML PUBLIC "
197 "\"-//W3C//DTD HTML 4.01 Transitional//EN\" "
198 "\"http://www.w3.org/TR/html4/loose.dtd\">\n"
199 "<html><head>\n"
200 "<title>Polipo status report</title>\n"
201 "</head><body>\n"
202 "<h1>Polipo proxy on %s:%d: status report</h1>\n"
203 "<p>The %s proxy on %s:%d is %s.</p>\n"
204 "<p>There are %d public and %d private objects "
205 "currently in memory using %d KB in %d chunks "
206 "(%d KB allocated).</p>\n"
207 "<p>There are %d atoms.</p>"
208 "<p><form method=POST action=\"/polipo/status?\">"
209 "<input type=submit name=\"init-forbidden\" "
210 "value=\"Read forbidden file\"></form>\n"
211 "<form method=POST action=\"/polipo/status?\">"
212 "<input type=submit name=\"writeout-objects\" "
213 "value=\"Write out in-memory cache\"></form>\n"
214 "<form method=POST action=\"/polipo/status?\">"
215 "<input type=submit name=\"discard-objects\" "
216 "value=\"Discard in-memory cache\"></form>\n"
217 "<form method=POST action=\"/polipo/status?\">"
218 "<input type=submit name=\"reopen-log\" "
219 "value=\"Reopen log file\"></form>\n"
220 "<form method=POST action=\"/polipo/status?\">"
221 "<input type=submit name=\"free-chunk-arenas\" "
222 "value=\"Free chunk arenas\"></form></p>\n"
223 "<p><a href=\"/polipo/\">back</a></p>"
224 "</body></html>\n",
225 proxyName->string, proxyPort,
226 cacheIsShared ? "shared" : "private",
227 proxyName->string, proxyPort,
228 proxyOffline ? "off line" :
229 (relaxTransparency ?
230 "on line (transparency relaxed)" :
231 "on line"),
232 publicObjectCount, privateObjectCount,
233 used_chunks * CHUNK_SIZE / 1024, used_chunks,
234 totalChunkArenaSize() / 1024,
235 used_atoms);
236 object->expires = current_time.tv_sec;
237 object->length = object->size;
238 } else if(matchUrl("/polipo/config", object)) {
239 fillSpecialObject(object, printConfig, NULL);
240 object->expires = current_time.tv_sec + 5;
241 #ifndef NO_DISK_CACHE
242 } else if(matchUrl("/polipo/index", object)) {
243 int len;
244 char *root;
245 if(disableIndexing) {
246 abortObject(object, 403, internAtom("Action not allowed"));
247 notifyObject(object);
248 return 1;
250 len = MAX(0, object->key_size - 14);
251 root = strdup_n((char*)object->key + 14, len);
252 if(root == NULL) {
253 abortObject(object, 503, internAtom("Couldn't allocate root"));
254 notifyObject(object);
255 return 1;
257 writeoutObjects(1);
258 fillSpecialObject(object, plainIndexDiskObjects, root);
259 free(root);
260 object->expires = current_time.tv_sec + 5;
261 } else if(matchUrl("/polipo/recursive-index", object)) {
262 int len;
263 char *root;
264 if(disableIndexing) {
265 abortObject(object, 403, internAtom("Action not allowed"));
266 notifyObject(object);
267 return 1;
269 len = MAX(0, object->key_size - 24);
270 root = strdup_n((char*)object->key + 24, len);
271 if(root == NULL) {
272 abortObject(object, 503, internAtom("Couldn't allocate root"));
273 notifyObject(object);
274 return 1;
276 writeoutObjects(1);
277 fillSpecialObject(object, recursiveIndexDiskObjects, root);
278 free(root);
279 object->expires = current_time.tv_sec + 20;
280 #endif
281 } else if(matchUrl("/polipo/servers", object)) {
282 if(disableServersList) {
283 abortObject(object, 403, internAtom("Action not allowed"));
284 notifyObject(object);
285 return 1;
287 fillSpecialObject(object, serversList, NULL);
288 object->expires = current_time.tv_sec + 2;
289 } else {
290 abortObject(object, 404, internAtom("Not found"));
293 object->flags &= ~OBJECT_VALIDATING;
294 notifyObject(object);
295 return 1;
298 #define MAXBODY(c) (((c)->flags & CONN_BIGREQBUF)? bigBufferSize : CHUNK_SIZE)
300 static void
301 writeContinue(HTTPConnectionPtr client)
303 static char httpContinue[] = "HTTP/1.1 100 Continue\r\n\r\n";
305 /* don't bother writing continue if the post is already completed */
306 if (client->reqlen - client->reqbegin < client->bodylen) {
307 do_stream(IO_WRITE, client->fd, 0, httpContinue, 25,
308 httpErrorNofinishStreamHandler, client);
312 static int
313 validateRequest(HTTPConnectionPtr client)
315 ObjectPtr object = client->request->object;
317 if (disableConfiguration) {
318 abortObject(object, 403, internAtom("Action not allowed"));
319 } else if (client->bodylen > MAXBODY(client)) {
320 abortObject(object, 411, internAtom("POST too large"));
321 } else if (!matchUrl("/polipo/status", object) &&
322 !matchUrl("/polipo/config", object)) {
323 abortObject(object, 404, internAtom("Not found"));
324 } else
325 return 0;
327 httpClientDiscardBody(client);
328 notifyObject(object);
330 return -1;
333 int
334 httpSpecialSideRequest(ObjectPtr object, int method, int from, int to,
335 HTTPRequestPtr requestor, void *closure)
337 HTTPConnectionPtr client = requestor->connection;
338 int waiting = 0;
340 assert(client->request == requestor);
342 if(method != METHOD_POST) {
343 httpClientError(requestor, 405, internAtom("Method not allowed"));
344 requestor->connection->flags &= ~CONN_READER;
345 return 1;
348 if (requestor->flags & REQUEST_WAIT_CONTINUE) {
349 requestor->flags &= ~REQUEST_WAIT_CONTINUE;
350 waiting = 1;
353 if (validateRequest(client) < 0)
354 return 1;
356 if (waiting)
357 writeContinue(client);
359 return httpSpecialDoSide(requestor);
362 static int
363 readFinished(HTTPConnectionPtr client)
365 HTTPRequestPtr request = client->request;
367 if(client->reqlen - client->reqbegin >= client->bodylen) {
368 AtomPtr data;
369 data = internAtomN(client->reqbuf + client->reqbegin,
370 client->reqlen - client->reqbegin);
371 client->reqbegin = 0;
372 client->reqlen = 0;
373 if(data == NULL) {
374 do_log(L_ERROR, "Couldn't allocate data.\n");
375 httpClientError(request, 500,
376 internAtom("Couldn't allocate data"));
377 return 1;
379 httpSpecialDoSideFinish(data, request);
380 return 1;
383 return 0;
387 httpSpecialDoSide(HTTPRequestPtr requestor)
389 HTTPConnectionPtr client = requestor->connection;
391 if (readFinished(client))
392 return 1;
394 if(client->reqbegin > 0 && client->reqlen > client->reqbegin) {
395 memmove(client->reqbuf, client->reqbuf + client->reqbegin,
396 client->reqlen - client->reqbegin);
398 client->reqlen -= client->reqbegin;
399 client->reqbegin = 0;
401 do_stream(IO_READ | IO_NOTNOW, client->fd,
402 client->reqlen, client->reqbuf, MAXBODY(client),
403 httpSpecialClientSideHandler, client);
404 return 1;
408 httpSpecialClientSideHandler(int status,
409 FdEventHandlerPtr event,
410 StreamRequestPtr srequest)
412 HTTPConnectionPtr connection = srequest->data;
413 HTTPRequestPtr request = connection->request;
415 if(status) {
416 connection->flags &= ~CONN_READER;
417 if (request->chandler) {
418 unregisterConditionHandler(request->chandler);
419 request->chandler = NULL;
421 do_log(L_ERROR, "Incomplete client request.\n");
422 httpClientRawError(connection, 502,
423 internAtom("Incomplete client request"), 1);
424 return 1;
427 connection->reqlen = srequest->offset;
429 return readFinished(connection);
433 httpSpecialDoSideFinish(AtomPtr data, HTTPRequestPtr requestor)
435 ObjectPtr object = requestor->object;
437 if(matchUrl("/polipo/config", object)) {
438 AtomListPtr list = NULL;
439 int i, rc;
441 if(disableConfiguration) {
442 abortObject(object, 403, internAtom("Action not allowed"));
443 goto out;
446 list = urlDecode(data->string, data->length);
447 if(list == NULL) {
448 abortObject(object, 400,
449 internAtom("Couldn't parse variable to set"));
450 goto out;
452 for(i = 0; i < list->length; i++) {
453 rc = parseConfigLine(list->list[i]->string, NULL, 0, 1);
454 if(rc < 0) {
455 abortObject(object, 400,
456 rc == -1 ?
457 internAtom("Couldn't parse variable to set") :
458 internAtom("Variable is not settable"));
459 destroyAtomList(list);
460 goto out;
463 destroyAtomList(list);
464 object->date = current_time.tv_sec;
465 object->age = current_time.tv_sec;
466 object->headers = internAtom("\r\nLocation: /polipo/config?");
467 object->code = 303;
468 object->message = internAtom("Done");
469 object->flags &= ~OBJECT_INITIAL;
470 object->length = 0;
471 } else if(matchUrl("/polipo/status", object)) {
472 AtomListPtr list = NULL;
473 int i;
475 if(disableConfiguration) {
476 abortObject(object, 403, internAtom("Action not allowed"));
477 goto out;
480 list = urlDecode(data->string, data->length);
481 if(list == NULL) {
482 abortObject(object, 400,
483 internAtom("Couldn't parse action"));
484 goto out;
486 for(i = 0; i < list->length; i++) {
487 char *equals =
488 memchr(list->list[i]->string, '=', list->list[i]->length);
489 AtomPtr name =
490 equals ?
491 internAtomN(list->list[i]->string,
492 equals - list->list[i]->string) :
493 retainAtom(list->list[i]);
494 if(name == atomInitForbidden)
495 initForbidden();
496 else if(name == atomReopenLog)
497 reopenLog();
498 else if(name == atomDiscardObjects)
499 discardObjects(1, 0);
500 else if(name == atomWriteoutObjects)
501 writeoutObjects(1);
502 else if(name == atomFreeChunkArenas)
503 free_chunk_arenas();
504 else {
505 abortObject(object, 400, internAtomF("Unknown action %s",
506 name->string));
507 releaseAtom(name);
508 destroyAtomList(list);
509 goto out;
511 releaseAtom(name);
513 destroyAtomList(list);
514 object->date = current_time.tv_sec;
515 object->age = current_time.tv_sec;
516 object->headers = internAtom("\r\nLocation: /polipo/status?");
517 object->code = 303;
518 object->message = internAtom("Done");
519 object->flags &= ~OBJECT_INITIAL;
520 object->length = 0;
521 } else {
522 abortObject(object, 404, internAtom("Not found"));
525 out:
526 releaseAtom(data);
527 notifyObject(object);
528 requestor->connection->flags &= ~CONN_READER;
529 return 1;
532 #ifdef HAVE_FORK
533 static void
534 fillSpecialObject(ObjectPtr object, void (*fn)(FILE*, char*), void* closure)
536 int rc;
537 int filedes[2];
538 pid_t pid;
539 sigset_t ss, old_mask;
541 if(object->flags & OBJECT_INPROGRESS)
542 return;
544 rc = pipe(filedes);
545 if(rc < 0) {
546 do_log_error(L_ERROR, errno, "Couldn't create pipe");
547 abortObject(object, 503,
548 internAtomError(errno, "Couldn't create pipe"));
549 return;
552 fflush(stdout);
553 fflush(stderr);
554 flushLog();
556 /* Block signals that we handle specially until the child can
557 disable the handlers. */
558 interestingSignals(&ss);
559 /* I'm a little confused. POSIX doesn't allow EINTR here, but I
560 think that both Linux and SVR4 do. */
561 do {
562 rc = sigprocmask(SIG_BLOCK, &ss, &old_mask);
563 } while (rc < 0 && errno == EINTR);
564 if(rc < 0) {
565 do_log_error(L_ERROR, errno, "Sigprocmask failed");
566 abortObject(object, 503, internAtomError(errno, "Sigprocmask failed"));
567 close(filedes[0]);
568 close(filedes[1]);
569 return;
572 pid = fork();
573 if(pid < 0) {
574 do_log_error(L_ERROR, errno, "Couldn't fork");
575 abortObject(object, 503, internAtomError(errno, "Couldn't fork"));
576 close(filedes[0]);
577 close(filedes[1]);
578 do {
579 rc = sigprocmask(SIG_SETMASK, &old_mask, NULL);
580 } while (rc < 0 && errno == EINTR);
581 if(rc < 0) {
582 do_log_error(L_ERROR, errno, "Couldn't restore signal mask");
583 polipoExit();
585 return;
588 if(pid > 0) {
589 SpecialRequestPtr request;
590 close(filedes[1]);
591 do {
592 rc = sigprocmask(SIG_SETMASK, &old_mask, NULL);
593 } while (rc < 0 && errno == EINTR);
594 if(rc < 0) {
595 do_log_error(L_ERROR, errno, "Couldn't restore signal mask");
596 polipoExit();
597 return;
600 request = malloc(sizeof(SpecialRequestRec));
601 if(request == NULL) {
602 kill(pid, SIGTERM);
603 close(filedes[0]);
604 abortObject(object, 503,
605 internAtom("Couldn't allocate request\n"));
606 notifyObject(object);
607 /* specialRequestHandler will take care of the rest. */
608 } else {
609 request->buf = get_chunk();
610 if(request->buf == NULL) {
611 kill(pid, SIGTERM);
612 close(filedes[0]);
613 free(request);
614 abortObject(object, 503,
615 internAtom("Couldn't allocate request\n"));
616 notifyObject(object);
619 object->flags |= OBJECT_INPROGRESS;
620 retainObject(object);
621 request->object = object;
622 request->fd = filedes[0];
623 request->pid = pid;
624 request->offset = 0;
625 /* Under any sensible scheduler, the child will run before the
626 parent. So no need for IO_NOTNOW. */
627 do_stream(IO_READ, filedes[0], 0, request->buf, CHUNK_SIZE,
628 specialRequestHandler, request);
629 } else {
630 /* child */
631 close(filedes[0]);
632 uninitEvents();
633 do {
634 rc = sigprocmask(SIG_SETMASK, &old_mask, NULL);
635 } while (rc < 0 && errno == EINTR);
636 if(rc < 0)
637 exit(1);
639 if(filedes[1] != 1)
640 dup2(filedes[1], 1);
642 (*fn)(stdout, closure);
643 exit(0);
648 specialRequestHandler(int status,
649 FdEventHandlerPtr event, StreamRequestPtr srequest)
651 SpecialRequestPtr request = srequest->data;
652 int rc;
653 int killed = 0;
655 if(status < 0) {
656 kill(request->pid, SIGTERM);
657 killed = 1;
658 request->object->flags &= ~OBJECT_INPROGRESS;
659 abortObject(request->object, 502,
660 internAtomError(-status, "Couldn't read from client"));
661 goto done;
664 if(srequest->offset > 0) {
665 rc = objectAddData(request->object, request->buf,
666 request->offset, srequest->offset);
667 if(rc < 0) {
668 kill(request->pid, SIGTERM);
669 killed = 1;
670 request->object->flags &= ~OBJECT_INPROGRESS;
671 abortObject(request->object, 503,
672 internAtom("Couldn't add data to connection"));
673 goto done;
675 request->offset += srequest->offset;
677 if(status) {
678 request->object->flags &= ~OBJECT_INPROGRESS;
679 request->object->length = request->object->size;
680 goto done;
683 /* If we're the only person interested in this object, let's abort
684 it now. */
685 if(request->object->refcount <= 1) {
686 kill(request->pid, SIGTERM);
687 killed = 1;
688 request->object->flags &= ~OBJECT_INPROGRESS;
689 abortObject(request->object, 500, internAtom("Aborted"));
690 goto done;
692 notifyObject(request->object);
693 do_stream(IO_READ | IO_NOTNOW, request->fd, 0, request->buf, CHUNK_SIZE,
694 specialRequestHandler, request);
695 return 1;
697 done:
698 close(request->fd);
699 dispose_chunk(request->buf);
700 releaseNotifyObject(request->object);
701 /* That's a blocking wait. It shouldn't block for long, as we've
702 either already killed the child, or else we got EOF from it. */
703 do {
704 rc = waitpid(request->pid, &status, 0);
705 } while(rc < 0 && errno == EINTR);
706 if(rc < 0) {
707 do_log(L_ERROR, "Wait for %d: %d\n", (int)request->pid, errno);
708 } else {
709 int normal =
710 (WIFEXITED(status) && WEXITSTATUS(status) == 0) ||
711 (killed && WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM);
712 char *reason =
713 WIFEXITED(status) ? "with status" :
714 WIFSIGNALED(status) ? "on signal" :
715 "with unknown status";
716 int value =
717 WIFEXITED(status) ? WEXITSTATUS(status) :
718 WIFSIGNALED(status) ? WTERMSIG(status) :
719 status;
720 do_log(normal ? D_CHILD : L_ERROR,
721 "Child %d exited %s %d.\n",
722 (int)request->pid, reason, value);
724 free(request);
725 return 1;
727 #else
728 static void
729 fillSpecialObject(ObjectPtr object, void (*fn)(FILE*, char*), void* closure)
731 FILE *tmp = NULL;
732 char *buf = NULL;
733 int rc, len, offset;
735 if(object->flags & OBJECT_INPROGRESS)
736 return;
738 buf = get_chunk();
739 if(buf == NULL) {
740 abortObject(object, 503, internAtom("Couldn't allocate chunk"));
741 goto done;
744 tmp = tmpfile();
745 if(tmp == NULL) {
746 abortObject(object, 503, internAtom(pstrerror(errno)));
747 goto done;
750 (*fn)(tmp, closure);
751 fflush(tmp);
753 rewind(tmp);
754 offset = 0;
755 while(1) {
756 len = fread(buf, 1, CHUNK_SIZE, tmp);
757 if(len <= 0 && ferror(tmp)) {
758 abortObject(object, 503, internAtom(pstrerror(errno)));
759 goto done;
761 if(len <= 0)
762 break;
764 rc = objectAddData(object, buf, offset, len);
765 if(rc < 0) {
766 abortObject(object, 503, internAtom("Couldn't add data to object"));
767 goto done;
770 offset += len;
773 object->length = offset;
775 done:
776 if(buf)
777 dispose_chunk(buf);
778 if(tmp)
779 fclose(tmp);
780 notifyObject(object);
782 #endif