Commit | Line | Data |
---|---|---|
e310e5f9 | 1 | #include "imager.h" |
02d1d628 | 2 | #include "iolayer.h" |
af070d99 | 3 | #include "imerror.h" |
02d1d628 AMH |
4 | #include "log.h" |
5 | #include <stdlib.h> | |
6 | #include <stdio.h> | |
7 | #ifdef _MSC_VER | |
8 | #include <io.h> | |
9 | #endif | |
35891892 | 10 | #include <string.h> |
2691d220 | 11 | #include <errno.h> |
02d1d628 AMH |
12 | |
13 | #define IOL_DEB(x) | |
14 | ||
15 | ||
16 | char *io_type_names[] = { "FDSEEK", "FDNOSEEK", "BUFFER", "CBSEEK", "CBNOSEEK", "BUFCHAIN" }; | |
17 | ||
18 | ||
19 | /* | |
20 | =head1 NAME | |
21 | ||
22 | iolayer.c - encapsulates different source of data into a single framework. | |
23 | ||
24 | =head1 SYNOPSIS | |
25 | ||
26 | io_glue *ig = io_new_fd( fileno(stdin) ); | |
27 | method = io_reqmeth( IOL_NOSEEK | IOL_MMAP ); // not implemented yet | |
28 | io_glue_commit_types(ig); // always assume IOL_SEEK for now | |
29 | switch (method) { | |
30 | case IOL_NOSEEK: | |
31 | code that uses ig->readcb() | |
32 | to read data goes here. | |
33 | break; | |
34 | case IOL_MMAP: | |
35 | code that uses ig->readcb() | |
36 | to read data goes here. | |
37 | break; | |
38 | } | |
39 | ||
40 | io_glue_DESTROY(ig); | |
41 | // and much more | |
42 | ||
43 | =head1 DESCRIPTION | |
44 | ||
45 | iolayer.c implements the basic functions to create and destroy io_glue | |
46 | objects for Imager. The typical usage pattern for data sources is: | |
47 | ||
48 | 1. Create the source (io_new_fd) | |
49 | 2. Define how you want to get data from it (io_reqmeth) | |
50 | 3. read from it using the interface requested (ig->readdb, ig->mmapcb) | |
51 | 4. Close the source, which | |
52 | shouldn't really close the underlying source. (io_glue DESTROY) | |
53 | ||
54 | =head1 FUNCTION REFERENCE | |
55 | ||
56 | Some of these functions are internal. | |
57 | ||
b8c2033e | 58 | =over |
02d1d628 AMH |
59 | |
60 | =cut | |
61 | */ | |
62 | ||
10461f9a TC |
63 | static ssize_t fd_read(io_glue *ig, void *buf, size_t count); |
64 | static ssize_t fd_write(io_glue *ig, const void *buf, size_t count); | |
65 | static off_t fd_seek(io_glue *ig, off_t offset, int whence); | |
66 | static void fd_close(io_glue *ig); | |
67 | static ssize_t fd_size(io_glue *ig); | |
40d75d5a | 68 | static const char *my_strerror(int err); |
02d1d628 AMH |
69 | |
70 | /* | |
71 | * Callbacks for sources that cannot seek | |
72 | */ | |
73 | ||
74 | /* fakeseek_read: read method for when emulating a seekable source | |
75 | static | |
76 | ssize_t | |
77 | fakeseek_read(io_glue *ig, void *buf, size_t count) { | |
78 | io_ex_fseek *exdata = ig->exdata; | |
79 | return 0; | |
80 | } | |
81 | */ | |
82 | ||
83 | ||
84 | ||
85 | /* | |
86 | * Callbacks for sources that can seek | |
87 | */ | |
88 | ||
89 | /* | |
90 | =item realseek_read(ig, buf, count) | |
91 | ||
92 | Does the reading from a source that can be seeked on | |
93 | ||
94 | ig - io_glue object | |
95 | buf - buffer to return data in | |
96 | count - number of bytes to read into buffer max | |
97 | ||
98 | =cut | |
99 | */ | |
100 | ||
101 | static | |
102 | ssize_t | |
103 | realseek_read(io_glue *ig, void *buf, size_t count) { | |
104 | io_ex_rseek *ier = ig->exdata; | |
10461f9a | 105 | void *p = ig->source.cb.p; |
02d1d628 AMH |
106 | ssize_t rc = 0; |
107 | size_t bc = 0; | |
108 | char *cbuf = buf; | |
109 | ||
10461f9a TC |
110 | IOL_DEB( printf("realseek_read: fd = %d, ier->cpos = %ld, buf = %p, " |
111 | "count = %d\n", fd, (long) ier->cpos, buf, count) ); | |
112 | /* Is this a good idea? Would it be better to handle differently? | |
113 | skip handling? */ | |
114 | while( count!=bc && (rc = ig->source.cb.readcb(p,cbuf+bc,count-bc))>0 ) { | |
115 | bc+=rc; | |
116 | } | |
02d1d628 AMH |
117 | |
118 | ier->cpos += bc; | |
119 | IOL_DEB( printf("realseek_read: rc = %d, bc = %d\n", rc, bc) ); | |
120 | return bc; | |
121 | } | |
122 | ||
123 | ||
124 | /* | |
125 | =item realseek_write(ig, buf, count) | |
126 | ||
127 | Does the writing to a 'source' that can be seeked on | |
128 | ||
129 | ig - io_glue object | |
130 | buf - buffer that contains data | |
131 | count - number of bytes to write | |
132 | ||
133 | =cut | |
134 | */ | |
135 | ||
136 | static | |
137 | ssize_t | |
138 | realseek_write(io_glue *ig, const void *buf, size_t count) { | |
139 | io_ex_rseek *ier = ig->exdata; | |
10461f9a | 140 | void *p = ig->source.cb.p; |
02d1d628 AMH |
141 | ssize_t rc = 0; |
142 | size_t bc = 0; | |
143 | char *cbuf = (char*)buf; | |
144 | ||
10461f9a TC |
145 | IOL_DEB( printf("realseek_write: ig = %p, ier->cpos = %ld, buf = %p, " |
146 | "count = %d\n", ig, (long) ier->cpos, buf, count) ); | |
02d1d628 | 147 | |
10461f9a TC |
148 | /* Is this a good idea? Would it be better to handle differently? |
149 | skip handling? */ | |
150 | while( count!=bc && (rc = ig->source.cb.writecb(p,cbuf+bc,count-bc))>0 ) { | |
151 | bc+=rc; | |
152 | } | |
02d1d628 AMH |
153 | |
154 | ier->cpos += bc; | |
155 | IOL_DEB( printf("realseek_write: rc = %d, bc = %d\n", rc, bc) ); | |
156 | return bc; | |
157 | } | |
158 | ||
159 | ||
160 | /* | |
161 | =item realseek_close(ig) | |
162 | ||
10461f9a TC |
163 | Closes a source that can be seeked on. Not sure if this should be an |
164 | actual close or not. Does nothing for now. Should be fixed. | |
02d1d628 AMH |
165 | |
166 | ig - data source | |
167 | ||
10461f9a | 168 | =cut */ |
02d1d628 AMH |
169 | |
170 | static | |
171 | void | |
172 | realseek_close(io_glue *ig) { | |
173 | mm_log((1, "realseek_close(ig %p)\n", ig)); | |
10461f9a TC |
174 | if (ig->source.cb.closecb) |
175 | ig->source.cb.closecb(ig->source.cb.p); | |
02d1d628 AMH |
176 | } |
177 | ||
178 | ||
179 | /* realseek_seek(ig, offset, whence) | |
180 | ||
181 | Implements seeking for a source that is seekable, the purpose of having this is to be able to | |
182 | have an offset into a file that is different from what the underlying library thinks. | |
183 | ||
184 | ig - data source | |
185 | offset - offset into stream | |
186 | whence - whence argument a la lseek | |
187 | ||
188 | =cut | |
189 | */ | |
190 | ||
191 | static | |
192 | off_t | |
193 | realseek_seek(io_glue *ig, off_t offset, int whence) { | |
194 | /* io_ex_rseek *ier = ig->exdata; Needed later */ | |
10461f9a | 195 | void *p = ig->source.cb.p; |
02d1d628 | 196 | int rc; |
930c67c8 | 197 | IOL_DEB( printf("realseek_seek(ig %p, offset %ld, whence %d)\n", ig, (long) offset, whence) ); |
10461f9a | 198 | rc = ig->source.cb.seekcb(p, offset, whence); |
02d1d628 AMH |
199 | |
200 | IOL_DEB( printf("realseek_seek: rc %ld\n", (long) rc) ); | |
201 | return rc; | |
202 | /* FIXME: How about implementing this offset handling stuff? */ | |
203 | } | |
204 | ||
4dfa5522 AMH |
205 | /* |
206 | * Callbacks for sources that are a fixed size buffer | |
207 | */ | |
208 | ||
209 | /* | |
210 | =item buffer_read(ig, buf, count) | |
211 | ||
212 | Does the reading from a buffer source | |
213 | ||
214 | ig - io_glue object | |
215 | buf - buffer to return data in | |
216 | count - number of bytes to read into buffer max | |
217 | ||
218 | =cut | |
219 | */ | |
220 | ||
221 | static | |
222 | ssize_t | |
223 | buffer_read(io_glue *ig, void *buf, size_t count) { | |
224 | io_ex_buffer *ieb = ig->exdata; | |
4dfa5522 AMH |
225 | |
226 | IOL_DEB( printf("buffer_read: fd = %d, ier->cpos = %ld, buf = %p, count = %d\n", fd, (long) ier->cpos, buf, count) ); | |
227 | ||
228 | if ( ieb->cpos+count > ig->source.buffer.len ) { | |
229 | mm_log((1,"buffer_read: short read: cpos=%d, len=%d, count=%d\n", ieb->cpos, ig->source.buffer.len)); | |
230 | count = ig->source.buffer.len - ieb->cpos; | |
231 | } | |
232 | ||
233 | memcpy(buf, ig->source.buffer.data+ieb->cpos, count); | |
234 | ieb->cpos += count; | |
235 | IOL_DEB( printf("buffer_read: rc = %d, count = %d\n", rc, count) ); | |
236 | return count; | |
237 | } | |
238 | ||
239 | ||
240 | /* | |
241 | =item buffer_write(ig, buf, count) | |
242 | ||
243 | Does nothing, returns -1 | |
244 | ||
245 | ig - io_glue object | |
246 | buf - buffer that contains data | |
247 | count - number of bytes to write | |
248 | ||
249 | =cut | |
250 | */ | |
251 | ||
252 | static | |
253 | ssize_t | |
254 | buffer_write(io_glue *ig, const void *buf, size_t count) { | |
255 | mm_log((1, "buffer_write called, this method should never be called.\n")); | |
256 | return -1; | |
257 | } | |
258 | ||
259 | ||
260 | /* | |
261 | =item buffer_close(ig) | |
262 | ||
263 | Closes a source that can be seeked on. Not sure if this should be an actual close | |
264 | or not. Does nothing for now. Should be fixed. | |
265 | ||
266 | ig - data source | |
267 | ||
268 | =cut | |
269 | */ | |
270 | ||
271 | static | |
272 | void | |
273 | buffer_close(io_glue *ig) { | |
274 | mm_log((1, "buffer_close(ig %p)\n", ig)); | |
275 | /* FIXME: Do stuff here */ | |
276 | } | |
277 | ||
278 | ||
279 | /* buffer_seek(ig, offset, whence) | |
280 | ||
281 | Implements seeking for a buffer source. | |
282 | ||
283 | ig - data source | |
284 | offset - offset into stream | |
285 | whence - whence argument a la lseek | |
286 | ||
287 | =cut | |
288 | */ | |
289 | ||
290 | static | |
291 | off_t | |
292 | buffer_seek(io_glue *ig, off_t offset, int whence) { | |
293 | io_ex_buffer *ieb = ig->exdata; | |
294 | off_t reqpos = offset | |
295 | + (whence == SEEK_CUR)*ieb->cpos | |
296 | + (whence == SEEK_END)*ig->source.buffer.len; | |
297 | ||
298 | if (reqpos > ig->source.buffer.len) { | |
299 | mm_log((1, "seeking out of readable range\n")); | |
300 | return (off_t)-1; | |
301 | } | |
302 | ||
303 | ieb->cpos = reqpos; | |
304 | IOL_DEB( printf("buffer_seek(ig %p, offset %ld, whence %d)\n", ig, (long) offset, whence) ); | |
305 | ||
306 | return reqpos; | |
307 | /* FIXME: How about implementing this offset handling stuff? */ | |
308 | } | |
309 | ||
310 | ||
311 | ||
312 | ||
313 | ||
02d1d628 AMH |
314 | /* |
315 | * Callbacks for sources that are a chain of variable sized buffers | |
316 | */ | |
317 | ||
318 | ||
319 | ||
320 | /* Helper functions for buffer chains */ | |
321 | ||
322 | static | |
323 | io_blink* | |
faa9b3e7 | 324 | io_blink_new(void) { |
02d1d628 AMH |
325 | io_blink *ib; |
326 | ||
327 | mm_log((1, "io_blink_new()\n")); | |
328 | ||
329 | ib = mymalloc(sizeof(io_blink)); | |
330 | ||
331 | ib->next = NULL; | |
332 | ib->prev = NULL; | |
333 | ib->len = BBSIZ; | |
334 | ||
335 | memset(&ib->buf, 0, ib->len); | |
336 | return ib; | |
337 | } | |
338 | ||
339 | ||
340 | ||
341 | /* | |
342 | =item io_bchain_advance(ieb) | |
343 | ||
344 | Advances the buffer chain to the next link - extending if | |
345 | necessary. Also adjusts the cpos and tfill counters as needed. | |
346 | ||
347 | ieb - buffer chain object | |
348 | ||
349 | =cut | |
350 | */ | |
351 | ||
352 | static | |
353 | void | |
354 | io_bchain_advance(io_ex_bchain *ieb) { | |
355 | if (ieb->cp->next == NULL) { | |
356 | ieb->tail = io_blink_new(); | |
357 | ieb->tail->prev = ieb->cp; | |
358 | ieb->cp->next = ieb->tail; | |
359 | ||
360 | ieb->tfill = 0; /* Only set this if we added a new slice */ | |
361 | } | |
362 | ieb->cp = ieb->cp->next; | |
363 | ieb->cpos = 0; | |
364 | } | |
365 | ||
366 | ||
c3cc977e AMH |
367 | |
368 | /* | |
369 | =item io_bchain_destroy() | |
370 | ||
371 | frees all resources used by a buffer chain. | |
372 | ||
373 | =cut | |
374 | */ | |
375 | ||
376 | void | |
377 | io_destroy_bufchain(io_ex_bchain *ieb) { | |
4dfa5522 AMH |
378 | io_blink *cp; |
379 | mm_log((1, "io_destroy_bufchain(ieb %p)\n", ieb)); | |
380 | cp = ieb->head; | |
381 | ||
c3cc977e AMH |
382 | while(cp) { |
383 | io_blink *t = cp->next; | |
4dfa5522 | 384 | myfree(cp); |
c3cc977e AMH |
385 | cp = t; |
386 | } | |
387 | } | |
388 | ||
389 | ||
390 | ||
391 | ||
02d1d628 AMH |
392 | /* |
393 | ||
394 | static | |
395 | void | |
396 | bufchain_dump(io_ex_bchain *ieb) { | |
397 | mm_log((1, " buf_chain_dump(ieb %p)\n")); | |
398 | mm_log((1, " buf_chain_dump: ieb->offset = %d\n", ieb->offset)); | |
399 | mm_log((1, " buf_chain_dump: ieb->length = %d\n", ieb->length)); | |
400 | mm_log((1, " buf_chain_dump: ieb->head = %p\n", ieb->head )); | |
401 | mm_log((1, " buf_chain_dump: ieb->tail = %p\n", ieb->tail )); | |
402 | mm_log((1, " buf_chain_dump: ieb->tfill = %d\n", ieb->tfill )); | |
403 | mm_log((1, " buf_chain_dump: ieb->cp = %p\n", ieb->cp )); | |
404 | mm_log((1, " buf_chain_dump: ieb->cpos = %d\n", ieb->cpos )); | |
405 | mm_log((1, " buf_chain_dump: ieb->gpos = %d\n", ieb->gpos )); | |
406 | } | |
407 | */ | |
408 | ||
409 | /* | |
410 | * TRUE if lengths are NOT equal | |
411 | */ | |
412 | ||
413 | /* | |
414 | static | |
415 | void | |
416 | chainlencert( io_glue *ig ) { | |
417 | int clen; | |
418 | int cfl = 0; | |
419 | size_t csize = 0; | |
420 | size_t cpos = 0; | |
421 | io_ex_bchain *ieb = ig->exdata; | |
422 | io_blink *cp = ieb->head; | |
423 | ||
424 | ||
425 | if (ieb->gpos > ieb->length) mm_log((1, "BBAR : ieb->gpos = %d, ieb->length = %d\n", ieb->gpos, ieb->length)); | |
426 | ||
427 | while(cp) { | |
428 | clen = (cp == ieb->tail) ? ieb->tfill : cp->len; | |
429 | if (ieb->head == cp && cp->prev) mm_log((1, "Head of chain has a non null prev\n")); | |
430 | if (ieb->tail == cp && cp->next) mm_log((1, "Tail of chain has a non null next\n")); | |
431 | ||
432 | if (ieb->head != cp && !cp->prev) mm_log((1, "Middle of chain has a null prev\n")); | |
433 | if (ieb->tail != cp && !cp->next) mm_log((1, "Middle of chain has a null next\n")); | |
434 | ||
435 | if (cp->prev && cp->prev->next != cp) mm_log((1, "%p = cp->prev->next != cp\n", cp->prev->next)); | |
436 | if (cp->next && cp->next->prev != cp) mm_log((1, "%p cp->next->prev != cp\n", cp->next->prev)); | |
437 | ||
438 | if (cp == ieb->cp) { | |
439 | cfl = 1; | |
440 | cpos += ieb->cpos; | |
441 | } | |
442 | ||
443 | if (!cfl) cpos += clen; | |
444 | ||
445 | csize += clen; | |
446 | cp = cp->next; | |
447 | } | |
448 | if (( csize != ieb->length )) mm_log((1, "BAR : csize = %d, ieb->length = %d\n", csize, ieb->length)); | |
449 | if (( cpos != ieb->gpos )) mm_log((1, "BAR : cpos = %d, ieb->gpos = %d\n", cpos, ieb->gpos )); | |
450 | } | |
451 | ||
452 | ||
453 | static | |
454 | void | |
455 | chaincert( io_glue *ig) { | |
456 | size_t csize = 0; | |
457 | io_ex_bchain *ieb = ig->exdata; | |
458 | io_blink *cp = ieb->head; | |
459 | ||
460 | mm_log((1, "Chain verification.\n")); | |
461 | ||
462 | mm_log((1, " buf_chain_dump: ieb->offset = %d\n", ieb->offset)); | |
463 | mm_log((1, " buf_chain_dump: ieb->length = %d\n", ieb->length)); | |
464 | mm_log((1, " buf_chain_dump: ieb->head = %p\n", ieb->head )); | |
465 | mm_log((1, " buf_chain_dump: ieb->tail = %p\n", ieb->tail )); | |
466 | mm_log((1, " buf_chain_dump: ieb->tfill = %d\n", ieb->tfill )); | |
467 | mm_log((1, " buf_chain_dump: ieb->cp = %p\n", ieb->cp )); | |
468 | mm_log((1, " buf_chain_dump: ieb->cpos = %d\n", ieb->cpos )); | |
469 | mm_log((1, " buf_chain_dump: ieb->gpos = %d\n", ieb->gpos )); | |
470 | ||
471 | while(cp) { | |
472 | int clen = cp == ieb->tail ? ieb->tfill : cp->len; | |
473 | mm_log((1, "link: %p <- %p -> %p\n", cp->prev, cp, cp->next)); | |
474 | if (ieb->head == cp && cp->prev) mm_log((1, "Head of chain has a non null prev\n")); | |
475 | if (ieb->tail == cp && cp->next) mm_log((1, "Tail of chain has a non null next\n")); | |
476 | ||
477 | if (ieb->head != cp && !cp->prev) mm_log((1, "Middle of chain has a null prev\n")); | |
478 | if (ieb->tail != cp && !cp->next) mm_log((1, "Middle of chain has a null next\n")); | |
479 | ||
480 | if (cp->prev && cp->prev->next != cp) mm_log((1, "%p = cp->prev->next != cp\n", cp->prev->next)); | |
481 | if (cp->next && cp->next->prev != cp) mm_log((1, "%p cp->next->prev != cp\n", cp->next->prev)); | |
482 | ||
483 | csize += clen; | |
484 | cp = cp->next; | |
485 | } | |
486 | ||
487 | mm_log((1, "csize = %d %s ieb->length = %d\n", csize, csize == ieb->length ? "==" : "!=", ieb->length)); | |
488 | } | |
489 | */ | |
490 | ||
491 | ||
492 | ||
493 | ||
494 | ||
495 | ||
496 | ||
497 | ||
498 | ||
499 | ||
500 | ||
501 | /* | |
502 | =item bufchain_read(ig, buf, count) | |
503 | ||
504 | Does the reading from a source that can be seeked on | |
505 | ||
506 | ig - io_glue object | |
507 | buf - buffer to return data in | |
508 | count - number of bytes to read into buffer max | |
509 | ||
510 | =cut | |
511 | */ | |
512 | ||
513 | static | |
514 | ssize_t | |
515 | bufchain_read(io_glue *ig, void *buf, size_t count) { | |
516 | io_ex_bchain *ieb = ig->exdata; | |
517 | size_t scount = count; | |
518 | char *cbuf = buf; | |
519 | size_t sk; | |
520 | ||
521 | mm_log((1, "bufchain_read(ig %p, buf %p, count %ld)\n", ig, buf, count)); | |
02d1d628 AMH |
522 | |
523 | while( scount ) { | |
524 | int clen = (ieb->cp == ieb->tail) ? ieb->tfill : ieb->cp->len; | |
525 | if (clen == ieb->cpos) { | |
526 | if (ieb->cp == ieb->tail) break; /* EOF */ | |
527 | ieb->cp = ieb->cp->next; | |
528 | ieb->cpos = 0; | |
529 | clen = (ieb->cp == ieb->tail) ? ieb->tfill : ieb->cp->len; | |
530 | } | |
531 | ||
532 | sk = clen - ieb->cpos; | |
533 | sk = sk > scount ? scount : sk; | |
534 | ||
535 | memcpy(&cbuf[count-scount], &ieb->cp->buf[ieb->cpos], sk); | |
536 | scount -= sk; | |
537 | ieb->cpos += sk; | |
538 | ieb->gpos += sk; | |
539 | } | |
540 | ||
541 | mm_log((1, "bufchain_read: returning %d\n", count-scount)); | |
542 | return count-scount; | |
543 | } | |
544 | ||
545 | ||
546 | ||
547 | ||
548 | ||
549 | /* | |
550 | =item bufchain_write(ig, buf, count) | |
551 | ||
552 | Does the writing to a 'source' that can be seeked on | |
553 | ||
554 | ig - io_glue object | |
555 | buf - buffer that contains data | |
556 | count - number of bytes to write | |
557 | ||
558 | =cut | |
559 | */ | |
560 | ||
561 | static | |
562 | ssize_t | |
563 | bufchain_write(io_glue *ig, const void *buf, size_t count) { | |
564 | char *cbuf = (char *)buf; | |
565 | io_ex_bchain *ieb = ig->exdata; | |
566 | size_t ocount = count; | |
567 | size_t sk; | |
568 | ||
930c67c8 | 569 | mm_log((1, "bufchain_write: ig = %p, buf = %p, count = %d\n", ig, buf, count)); |
02d1d628 | 570 | |
930c67c8 | 571 | IOL_DEB( printf("bufchain_write: ig = %p, ieb->cpos = %ld, buf = %p, count = %d\n", ig, (long) ieb->cpos, buf, count) ); |
02d1d628 AMH |
572 | |
573 | while(count) { | |
574 | mm_log((2, "bufchain_write: - looping - count = %d\n", count)); | |
575 | if (ieb->cp->len == ieb->cpos) { | |
576 | mm_log((1, "bufchain_write: cp->len == ieb->cpos = %d - advancing chain\n", (long) ieb->cpos)); | |
577 | io_bchain_advance(ieb); | |
578 | } | |
579 | ||
580 | sk = ieb->cp->len - ieb->cpos; | |
581 | sk = sk > count ? count : sk; | |
582 | memcpy(&ieb->cp->buf[ieb->cpos], &cbuf[ocount-count], sk); | |
583 | ||
584 | if (ieb->cp == ieb->tail) { | |
585 | int extend = ieb->cpos + sk - ieb->tfill; | |
586 | mm_log((2, "bufchain_write: extending tail by %d\n", extend)); | |
587 | if (extend > 0) { | |
588 | ieb->length += extend; | |
589 | ieb->tfill += extend; | |
590 | } | |
591 | } | |
592 | ||
593 | ieb->cpos += sk; | |
594 | ieb->gpos += sk; | |
595 | count -= sk; | |
596 | } | |
597 | return ocount; | |
598 | } | |
599 | ||
600 | /* | |
601 | =item bufchain_close(ig) | |
602 | ||
603 | Closes a source that can be seeked on. Not sure if this should be an actual close | |
604 | or not. Does nothing for now. Should be fixed. | |
605 | ||
606 | ig - data source | |
607 | ||
608 | =cut | |
609 | */ | |
610 | ||
611 | static | |
612 | void | |
613 | bufchain_close(io_glue *ig) { | |
614 | mm_log((1, "bufchain_close(ig %p)\n",ig)); | |
930c67c8 | 615 | IOL_DEB( printf("bufchain_close(ig %p)\n", ig) ); |
02d1d628 AMH |
616 | /* FIXME: Commit a seek point here */ |
617 | ||
618 | } | |
619 | ||
620 | ||
621 | /* bufchain_seek(ig, offset, whence) | |
622 | ||
623 | Implements seeking for a source that is seekable, the purpose of having this is to be able to | |
624 | have an offset into a file that is different from what the underlying library thinks. | |
625 | ||
626 | ig - data source | |
627 | offset - offset into stream | |
628 | whence - whence argument a la lseek | |
629 | ||
630 | =cut | |
631 | */ | |
632 | ||
633 | static | |
634 | off_t | |
635 | bufchain_seek(io_glue *ig, off_t offset, int whence) { | |
636 | io_ex_bchain *ieb = ig->exdata; | |
637 | io_blink *ib = NULL; | |
638 | int wrlen; | |
639 | ||
640 | off_t cof = 0; | |
641 | off_t scount = offset; | |
642 | off_t sk; | |
643 | ||
644 | mm_log((1, "bufchain_seek(ig %p, offset %ld, whence %d)\n", ig, offset, whence)); | |
645 | ||
646 | switch (whence) { | |
647 | case SEEK_SET: /* SEEK_SET = 0, From the top */ | |
648 | ieb->cp = ieb->head; | |
649 | ieb->cpos = 0; | |
650 | ieb->gpos = 0; | |
651 | ||
652 | while( scount ) { | |
653 | int clen = (ieb->cp == ieb->tail) ? ieb->tfill : ieb->cp->len; | |
654 | if (clen == ieb->cpos) { | |
655 | if (ieb->cp == ieb->tail) break; /* EOF */ | |
656 | ieb->cp = ieb->cp->next; | |
657 | ieb->cpos = 0; | |
658 | clen = (ieb->cp == ieb->tail) ? ieb->tfill : ieb->cp->len; | |
659 | } | |
660 | ||
661 | sk = clen - ieb->cpos; | |
662 | sk = sk > scount ? scount : sk; | |
663 | ||
664 | scount -= sk; | |
665 | ieb->cpos += sk; | |
666 | ieb->gpos += sk; | |
667 | } | |
668 | ||
669 | wrlen = scount; | |
670 | ||
671 | if (wrlen > 0) { | |
672 | /* | |
673 | * extending file - get ieb into consistent state and then | |
674 | * call write which will get it to the correct position | |
675 | */ | |
676 | char TB[BBSIZ]; | |
677 | memset(TB, 0, BBSIZ); | |
678 | ieb->gpos = ieb->length; | |
679 | ieb->cpos = ieb->tfill; | |
680 | ||
681 | while(wrlen > 0) { | |
b33c08f8 | 682 | ssize_t rc, wl = i_min(wrlen, BBSIZ); |
02d1d628 AMH |
683 | mm_log((1, "bufchain_seek: wrlen = %d, wl = %d\n", wrlen, wl)); |
684 | rc = bufchain_write( ig, TB, wl ); | |
685 | if (rc != wl) m_fatal(0, "bufchain_seek: Unable to extend file\n"); | |
686 | wrlen -= rc; | |
687 | } | |
688 | } | |
689 | ||
690 | break; | |
691 | ||
692 | case SEEK_CUR: | |
693 | m_fatal(123, "SEEK_CUR IS NOT IMPLEMENTED\n"); | |
694 | ||
695 | /* | |
696 | case SEEK_CUR: | |
697 | ib = ieb->cp; | |
698 | if (cof < 0) { | |
699 | cof += ib->cpos; | |
700 | cpos = 0; | |
701 | while(cof < 0 && ib->prev) { | |
702 | ib = ib->prev; | |
703 | cof += ib->len; | |
704 | } | |
705 | */ | |
706 | ||
707 | case SEEK_END: /* SEEK_END = 2 */ | |
708 | if (cof>0) m_fatal(0, "bufchain_seek: SEEK_END + %d : Extending files via seek not supported!\n", cof); | |
709 | ||
710 | ieb->cp = ieb->tail; | |
711 | ieb->cpos = ieb->tfill; | |
712 | ||
713 | if (cof<0) { | |
714 | cof += ieb->cpos; | |
715 | ieb->cpos = 0; | |
716 | ||
717 | while(cof<0 && ib->prev) { | |
718 | ib = ib->prev; | |
719 | cof += ib->len; | |
720 | } | |
721 | ||
722 | if (cof<0) m_fatal(0, "bufchain_seek: Tried to seek before start of file\n"); | |
723 | ieb->gpos = ieb->length+offset; | |
724 | ieb->cpos = cof; | |
725 | } | |
726 | break; | |
727 | default: | |
728 | m_fatal(0, "bufchain_seek: Unhandled seek request: whence = %d\n", whence ); | |
729 | } | |
730 | ||
731 | mm_log((2, "bufchain_seek: returning ieb->gpos = %d\n", ieb->gpos)); | |
732 | return ieb->gpos; | |
733 | } | |
734 | ||
735 | ||
736 | ||
737 | ||
738 | ||
739 | ||
740 | /* | |
741 | * Methods for setting up data source | |
742 | */ | |
743 | ||
744 | /* | |
745 | =item io_obj_setp_buffer(io, p, len) | |
746 | ||
747 | Sets an io_object for reading from a buffer source | |
748 | ||
749 | io - io object that describes a source | |
750 | p - pointer to buffer | |
751 | len - length of buffer | |
752 | ||
753 | =cut | |
754 | */ | |
755 | ||
756 | void | |
4dfa5522 AMH |
757 | io_obj_setp_buffer(io_obj *io, char *p, size_t len, closebufp closecb, void *closedata) { |
758 | io->buffer.type = BUFFER; | |
759 | io->buffer.data = p; | |
760 | io->buffer.len = len; | |
761 | io->buffer.closecb = closecb; | |
762 | io->buffer.closedata = closedata; | |
02d1d628 AMH |
763 | } |
764 | ||
765 | ||
766 | /* | |
4dfa5522 | 767 | =item io_obj_setp_buchain(io) |
02d1d628 AMH |
768 | |
769 | Sets an io_object for reading/writing from a buffer source | |
770 | ||
771 | io - io object that describes a source | |
772 | p - pointer to buffer | |
773 | len - length of buffer | |
774 | ||
775 | =cut | |
776 | */ | |
777 | ||
778 | void | |
779 | io_obj_setp_bufchain(io_obj *io) { | |
780 | io->type = BUFCHAIN; | |
781 | } | |
782 | ||
783 | ||
784 | /* | |
10461f9a | 785 | =item io_obj_setp_cb2(io, p, readcb, writecb, seekcb, closecb, destroycb) |
02d1d628 AMH |
786 | |
787 | Sets an io_object for reading from a source that uses callbacks | |
788 | ||
789 | io - io object that describes a source | |
10461f9a TC |
790 | p - pointer to data for callbacks |
791 | readcb - read callback to read from source | |
792 | writecb - write callback to write to source | |
793 | seekcb - seek callback to seek on source | |
794 | closecb - flush any pending data | |
795 | destroycb - release any extra resources | |
02d1d628 AMH |
796 | |
797 | =cut | |
798 | */ | |
799 | ||
800 | void | |
10461f9a TC |
801 | io_obj_setp_cb2(io_obj *io, void *p, readl readcb, writel writecb, seekl seekcb, closel closecb, destroyl destroycb) { |
802 | io->cb.type = CBSEEK; | |
803 | io->cb.p = p; | |
804 | io->cb.readcb = readcb; | |
805 | io->cb.writecb = writecb; | |
806 | io->cb.seekcb = seekcb; | |
807 | io->cb.closecb = closecb; | |
808 | io->cb.destroycb = destroycb; | |
809 | } | |
810 | ||
811 | void | |
812 | io_obj_setp_cb(io_obj *io, void *p, readl readcb, writel writecb, | |
813 | seekl seekcb) { | |
814 | io_obj_setp_cb2(io, p, readcb, writecb, seekcb, NULL, NULL); | |
02d1d628 AMH |
815 | } |
816 | ||
817 | /* | |
818 | =item io_glue_commit_types(ig) | |
819 | ||
820 | Creates buffers and initializes structures to read with the chosen interface. | |
821 | ||
822 | ig - io_glue object | |
823 | ||
824 | =cut | |
825 | */ | |
826 | ||
827 | void | |
828 | io_glue_commit_types(io_glue *ig) { | |
829 | io_type inn = ig->source.type; | |
527c0c3e | 830 | |
930c67c8 | 831 | mm_log((1, "io_glue_commit_types(ig %p)\n", ig)); |
02d1d628 | 832 | mm_log((1, "io_glue_commit_types: source type %d (%s)\n", inn, io_type_names[inn])); |
527c0c3e AMH |
833 | |
834 | if (ig->flags & 0x01) { | |
835 | mm_log((1, "io_glue_commit_types: type already set up\n")); | |
836 | return; | |
837 | } | |
838 | ||
02d1d628 AMH |
839 | switch (inn) { |
840 | case BUFCHAIN: | |
841 | { | |
842 | io_ex_bchain *ieb = mymalloc(sizeof(io_ex_bchain)); | |
843 | ||
844 | ieb->offset = 0; | |
845 | ieb->length = 0; | |
846 | ieb->cpos = 0; | |
847 | ieb->gpos = 0; | |
848 | ieb->tfill = 0; | |
c3cc977e | 849 | |
02d1d628 AMH |
850 | ieb->head = io_blink_new(); |
851 | ieb->cp = ieb->head; | |
852 | ieb->tail = ieb->head; | |
853 | ||
854 | ig->exdata = ieb; | |
855 | ig->readcb = bufchain_read; | |
856 | ig->writecb = bufchain_write; | |
857 | ig->seekcb = bufchain_seek; | |
858 | ig->closecb = bufchain_close; | |
859 | } | |
860 | break; | |
861 | case CBSEEK: | |
02d1d628 AMH |
862 | { |
863 | io_ex_rseek *ier = mymalloc(sizeof(io_ex_rseek)); | |
864 | ||
865 | ier->offset = 0; | |
866 | ier->cpos = 0; | |
867 | ||
868 | ig->exdata = ier; | |
869 | ig->readcb = realseek_read; | |
870 | ig->writecb = realseek_write; | |
871 | ig->seekcb = realseek_seek; | |
872 | ig->closecb = realseek_close; | |
873 | } | |
4dfa5522 AMH |
874 | break; |
875 | case BUFFER: | |
876 | { | |
877 | io_ex_buffer *ieb = mymalloc(sizeof(io_ex_buffer)); | |
878 | ieb->offset = 0; | |
879 | ieb->cpos = 0; | |
880 | ||
881 | ig->exdata = ieb; | |
882 | ig->readcb = buffer_read; | |
883 | ig->writecb = buffer_write; | |
884 | ig->seekcb = buffer_seek; | |
885 | ig->closecb = buffer_close; | |
886 | } | |
887 | break; | |
10461f9a TC |
888 | case FDSEEK: |
889 | { | |
890 | ig->exdata = NULL; | |
891 | ig->readcb = fd_read; | |
892 | ig->writecb = fd_write; | |
893 | ig->seekcb = fd_seek; | |
894 | ig->closecb = fd_close; | |
af070d99 | 895 | ig->sizecb = fd_size; |
10461f9a TC |
896 | break; |
897 | } | |
02d1d628 | 898 | } |
527c0c3e | 899 | ig->flags |= 0x01; /* indicate source has been setup already */ |
02d1d628 AMH |
900 | } |
901 | ||
902 | /* | |
903 | =item io_glue_gettypes(ig, reqmeth) | |
904 | ||
905 | Returns a set of compatible interfaces to read data with. | |
906 | ||
907 | ig - io_glue object | |
908 | reqmeth - request mask | |
909 | ||
910 | The request mask is a bit mask (of something that hasn't been implemented yet) | |
911 | of interfaces that it would like to read data from the source which the ig | |
912 | describes. | |
913 | ||
914 | =cut | |
915 | */ | |
916 | ||
917 | void | |
918 | io_glue_gettypes(io_glue *ig, int reqmeth) { | |
919 | ||
920 | ig = NULL; | |
921 | reqmeth = 0; | |
922 | ||
923 | /* FIXME: Implement this function! */ | |
924 | /* if (ig->source.type = | |
925 | if (reqmeth & IO_BUFF) */ | |
926 | ||
927 | } | |
928 | ||
929 | ||
930 | /* | |
931 | =item io_new_bufchain() | |
932 | ||
933 | returns a new io_glue object that has the 'empty' source and but can | |
934 | be written to and read from later (like a pseudo file). | |
935 | ||
936 | =cut | |
937 | */ | |
938 | ||
939 | io_glue * | |
940 | io_new_bufchain() { | |
4dfa5522 AMH |
941 | io_glue *ig; |
942 | mm_log((1, "io_new_bufchain()\n")); | |
943 | ig = mymalloc(sizeof(io_glue)); | |
527c0c3e | 944 | memset(ig, 0, sizeof(*ig)); |
02d1d628 AMH |
945 | io_obj_setp_bufchain(&ig->source); |
946 | return ig; | |
947 | } | |
948 | ||
949 | ||
c3cc977e AMH |
950 | |
951 | ||
952 | ||
4dfa5522 AMH |
953 | /* |
954 | =item io_new_buffer(data, len) | |
955 | ||
956 | Returns a new io_glue object that has the source defined as reading | |
957 | from specified buffer. Note that the buffer is not copied. | |
958 | ||
959 | data - buffer to read from | |
960 | len - length of buffer | |
c3cc977e | 961 | |
4dfa5522 AMH |
962 | =cut |
963 | */ | |
964 | ||
965 | io_glue * | |
966 | io_new_buffer(char *data, size_t len, closebufp closecb, void *closedata) { | |
967 | io_glue *ig; | |
968 | mm_log((1, "io_new_buffer(data %p, len %d, closecb %p, closedata %p)\n", data, len, closecb, closedata)); | |
969 | ig = mymalloc(sizeof(io_glue)); | |
970 | memset(ig, 0, sizeof(*ig)); | |
971 | io_obj_setp_buffer(&ig->source, data, len, closecb, closedata); | |
527c0c3e | 972 | ig->flags = 0; |
4dfa5522 AMH |
973 | return ig; |
974 | } | |
c3cc977e AMH |
975 | |
976 | ||
02d1d628 AMH |
977 | /* |
978 | =item io_new_fd(fd) | |
979 | ||
980 | returns a new io_glue object that has the source defined as reading | |
981 | from specified filedescriptor. Note that the the interface to recieving | |
982 | data from the io_glue callbacks hasn't been done yet. | |
983 | ||
984 | fd - file descriptor to read/write from | |
985 | ||
986 | =cut | |
987 | */ | |
988 | ||
989 | io_glue * | |
990 | io_new_fd(int fd) { | |
4dfa5522 AMH |
991 | io_glue *ig; |
992 | mm_log((1, "io_new_fd(fd %d)\n", fd)); | |
993 | ig = mymalloc(sizeof(io_glue)); | |
261f91c5 | 994 | memset(ig, 0, sizeof(*ig)); |
10461f9a TC |
995 | ig->source.type = FDSEEK; |
996 | ig->source.fdseek.fd = fd; | |
527c0c3e | 997 | ig->flags = 0; |
10461f9a | 998 | #if 0 |
02d1d628 AMH |
999 | #ifdef _MSC_VER |
1000 | io_obj_setp_cb(&ig->source, (void*)fd, _read, _write, _lseek); | |
1001 | #else | |
1002 | io_obj_setp_cb(&ig->source, (void*)fd, read, write, lseek); | |
10461f9a | 1003 | #endif |
02d1d628 | 1004 | #endif |
4dfa5522 | 1005 | mm_log((1, "(%p) <- io_new_fd\n", ig)); |
02d1d628 AMH |
1006 | return ig; |
1007 | } | |
1008 | ||
10461f9a TC |
1009 | io_glue *io_new_cb(void *p, readl readcb, writel writecb, seekl seekcb, |
1010 | closel closecb, destroyl destroycb) { | |
1011 | io_glue *ig; | |
02d1d628 | 1012 | |
10461f9a TC |
1013 | mm_log((1, "io_new_cb(p %p, readcb %p, writecb %p, seekcb %p, closecb %p, " |
1014 | "destroycb %p)\n", p, readcb, writecb, seekcb, closecb, destroycb)); | |
1015 | ig = mymalloc(sizeof(io_glue)); | |
527c0c3e | 1016 | memset(ig, 0, sizeof(*ig)); |
10461f9a TC |
1017 | io_obj_setp_cb2(&ig->source, p, readcb, writecb, seekcb, closecb, destroycb); |
1018 | mm_log((1, "(%p) <- io_new_cb\n", ig)); | |
1019 | ||
1020 | return ig; | |
1021 | } | |
02d1d628 AMH |
1022 | |
1023 | /* | |
1024 | =item io_slurp(ig) | |
1025 | ||
1026 | Takes the source that the io_glue is bound to and allocates space | |
1027 | for a return buffer and returns the entire content in a single buffer. | |
1028 | Note: This only works for io_glue objects that contain a bufchain. It | |
1029 | is usefull for saving to scalars and such. | |
1030 | ||
1031 | ig - io_glue object | |
1032 | c - pointer to a pointer to where data should be copied to | |
1033 | ||
1034 | =cut | |
1035 | */ | |
1036 | ||
1037 | size_t | |
1038 | io_slurp(io_glue *ig, unsigned char **c) { | |
1039 | ssize_t rc; | |
1040 | off_t orgoff; | |
1041 | io_ex_bchain *ieb; | |
1042 | unsigned char *cc; | |
1043 | io_type inn = ig->source.type; | |
1044 | ||
1045 | if ( inn != BUFCHAIN ) { | |
1046 | m_fatal(0, "io_slurp: called on a source that is not from a bufchain\n"); | |
1047 | } | |
1048 | ||
1049 | ieb = ig->exdata; | |
1050 | cc = *c = mymalloc( ieb->length ); | |
1051 | ||
1052 | orgoff = ieb->gpos; | |
1053 | ||
1054 | bufchain_seek(ig, 0, SEEK_SET); | |
1055 | ||
1056 | rc = bufchain_read(ig, cc, ieb->length); | |
1057 | ||
1058 | if (rc != ieb->length) | |
1059 | m_fatal(1, "io_slurp: bufchain_read returned an incomplete read: rc = %d, request was %d\n", rc, ieb->length); | |
1060 | ||
1061 | return rc; | |
1062 | } | |
1063 | ||
10461f9a TC |
1064 | /* |
1065 | =item fd_read(ig, buf, count) | |
1066 | ||
1067 | =cut | |
1068 | */ | |
1069 | static ssize_t fd_read(io_glue *ig, void *buf, size_t count) { | |
5f8f8e17 | 1070 | ssize_t result; |
10461f9a | 1071 | #ifdef _MSC_VER |
5f8f8e17 | 1072 | result = _read(ig->source.fdseek.fd, buf, count); |
10461f9a | 1073 | #else |
5f8f8e17 | 1074 | result = read(ig->source.fdseek.fd, buf, count); |
10461f9a | 1075 | #endif |
5f8f8e17 TC |
1076 | |
1077 | /* 0 is valid - means EOF */ | |
1078 | if (result < 0) { | |
40d75d5a | 1079 | i_push_errorf(0, "read() failure: %s (%d)", my_strerror(errno), errno); |
5f8f8e17 TC |
1080 | } |
1081 | ||
1082 | return result; | |
10461f9a TC |
1083 | } |
1084 | ||
1085 | static ssize_t fd_write(io_glue *ig, const void *buf, size_t count) { | |
2691d220 | 1086 | ssize_t result; |
10461f9a | 1087 | #ifdef _MSC_VER |
2691d220 | 1088 | result = _write(ig->source.fdseek.fd, buf, count); |
10461f9a | 1089 | #else |
2691d220 | 1090 | result = write(ig->source.fdseek.fd, buf, count); |
10461f9a | 1091 | #endif |
2691d220 TC |
1092 | |
1093 | if (result <= 0) { | |
40d75d5a | 1094 | i_push_errorf(errno, "write() failure: %s (%d)", my_strerror(errno), errno); |
2691d220 TC |
1095 | } |
1096 | ||
1097 | return result; | |
10461f9a TC |
1098 | } |
1099 | ||
1100 | static off_t fd_seek(io_glue *ig, off_t offset, int whence) { | |
2691d220 | 1101 | off_t result; |
10461f9a | 1102 | #ifdef _MSC_VER |
2691d220 | 1103 | result = _lseek(ig->source.fdseek.fd, offset, whence); |
10461f9a | 1104 | #else |
2691d220 | 1105 | result = lseek(ig->source.fdseek.fd, offset, whence); |
10461f9a | 1106 | #endif |
2691d220 TC |
1107 | |
1108 | if (result == (off_t)-1) { | |
40d75d5a | 1109 | i_push_errorf(errno, "lseek() failure: %s (%d)", my_strerror(errno), errno); |
2691d220 TC |
1110 | } |
1111 | ||
1112 | return result; | |
10461f9a TC |
1113 | } |
1114 | ||
1115 | static void fd_close(io_glue *ig) { | |
1116 | /* no, we don't close it */ | |
1117 | } | |
1118 | ||
1119 | static ssize_t fd_size(io_glue *ig) { | |
1120 | mm_log((1, "fd_size(ig %p) unimplemented\n", ig)); | |
1121 | ||
1122 | return -1; | |
1123 | } | |
02d1d628 AMH |
1124 | |
1125 | /* | |
1126 | =item io_glue_DESTROY(ig) | |
1127 | ||
1128 | A destructor method for io_glue objects. Should clean up all related buffers. | |
1129 | Might leave us with a dangling pointer issue on some buffers. | |
1130 | ||
1131 | ig - io_glue object to destroy. | |
1132 | ||
1133 | =cut | |
1134 | */ | |
1135 | ||
1136 | void | |
1137 | io_glue_DESTROY(io_glue *ig) { | |
c3cc977e AMH |
1138 | io_type inn = ig->source.type; |
1139 | mm_log((1, "io_glue_DESTROY(ig %p)\n", ig)); | |
1140 | ||
1141 | switch (inn) { | |
1142 | case BUFCHAIN: | |
1143 | { | |
1144 | io_ex_bchain *ieb = ig->exdata; | |
1145 | io_destroy_bufchain(ieb); | |
4dfa5522 | 1146 | myfree(ieb); |
c3cc977e AMH |
1147 | } |
1148 | break; | |
1149 | case CBSEEK: | |
c3cc977e AMH |
1150 | { |
1151 | io_ex_rseek *ier = ig->exdata; | |
10461f9a TC |
1152 | if (ig->source.cb.destroycb) |
1153 | ig->source.cb.destroycb(ig->source.cb.p); | |
c3cc977e AMH |
1154 | myfree(ier); |
1155 | } | |
4dfa5522 AMH |
1156 | break; |
1157 | case BUFFER: | |
1158 | { | |
1159 | io_ex_buffer *ieb = ig->exdata; | |
1160 | if (ig->source.buffer.closecb) { | |
1161 | mm_log((1,"calling close callback %p for io_buffer\n", ig->source.buffer.closecb)); | |
1162 | ig->source.buffer.closecb(ig->source.buffer.closedata); | |
1163 | } | |
1164 | myfree(ieb); | |
1165 | } | |
1166 | break; | |
10461f9a TC |
1167 | default: |
1168 | break; | |
c3cc977e AMH |
1169 | } |
1170 | myfree(ig); | |
02d1d628 | 1171 | } |
b8c2033e | 1172 | |
40d75d5a TC |
1173 | /* |
1174 | =back | |
1175 | ||
1176 | =head1 INTERNAL FUNCTIONS | |
1177 | ||
1178 | =over | |
1179 | ||
1180 | =item my_strerror | |
1181 | ||
1182 | Calls strerror() and ensures we don't return NULL. | |
1183 | ||
1184 | On some platforms it's possible for strerror() to return NULL, this | |
1185 | wrapper ensures we only get non-NULL values. | |
1186 | ||
1187 | =cut | |
1188 | */ | |
1189 | ||
1190 | static | |
1191 | const char *my_strerror(int err) { | |
1192 | const char *result = strerror(err); | |
1193 | ||
1194 | if (!result) | |
1195 | result = "Unknown error"; | |
1196 | ||
1197 | return result; | |
1198 | } | |
b8c2033e AMH |
1199 | |
1200 | /* | |
1201 | =back | |
1202 | ||
1203 | =head1 AUTHOR | |
1204 | ||
1205 | Arnar M. Hrafnkelsson <addi@umich.edu> | |
1206 | ||
1207 | =head1 SEE ALSO | |
1208 | ||
1209 | Imager(3) | |
1210 | ||
1211 | =cut | |
1212 | */ |