Post Scarcity
A prototype for a post scarcity programming environment
Loading...
Searching...
No Matches
fopen.c
Go to the documentation of this file.
1/*
2 * fopen.c
3 *
4 * adapted from https://curl.haxx.se/libcurl/c/fopen.html.
5 *
6 * Modifications to read/write wide character streams by
7 * Simon Brooke.
8 *
9 * NOTE THAT: for my purposes, I'm only interested in wide characters,
10 * and I always read them one character at a time.
11 *
12 * Copyright (c) 2003, 2017 Simtec Electronics
13 * Some portions (c) 2019 Simon Brooke <simon@journeyman.cc>
14 *
15 * Redistribution and use in source and binary forms, with or without
16 * modification, are permitted provided that the following conditions
17 * are met:
18 * 1. Redistributions of source code must retain the above copyright
19 * notice, this list of conditions and the following disclaimer.
20 * 2. Redistributions in binary form must reproduce the above copyright
21 * notice, this list of conditions and the following disclaimer in the
22 * documentation and/or other materials provided with the distribution.
23 * 3. The name of the author may not be used to endorse or promote products
24 * derived from this software without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
27 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
28 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
29 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
30 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
31 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
35 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 *
37 * This example requires libcurl 7.9.7 or later.
38 */
39
40#include <stdio.h>
41#include <string.h>
42#ifndef WIN32
43#include <sys/time.h>
44#endif
45#include <stdlib.h>
46#include <errno.h>
47
48#include <curl/curl.h>
49
50#include "io/fopen.h"
51#ifdef FOPEN_STANDALONE
52CURLSH *io_share;
53#else
55#include "io/io.h"
56#include "utils.h"
57#endif
58
59
60/* exported functions */
61URL_FILE *url_fopen( const char *url, const char *operation );
62int url_fclose( URL_FILE * file );
63int url_feof( URL_FILE * file );
64size_t url_fread( void *ptr, size_t size, size_t nmemb, URL_FILE * file );
65char *url_fgets( char *ptr, size_t size, URL_FILE * file );
66void url_rewind( URL_FILE * file );
67
68/* we use a global one for convenience */
69static CURLM *multi_handle;
70
71/* curl calls this routine to get more data */
72static size_t write_callback( char *buffer,
73 size_t size, size_t nitems, void *userp ) {
74 char *newbuff;
75 size_t rembuff;
76
77 URL_FILE *url = ( URL_FILE * ) userp;
78 size *= nitems;
79
80 rembuff = url->buffer_len - url->buffer_pos; /* remaining space in buffer */
81
82 if ( size > rembuff ) {
83 /* not enough space in buffer */
84 newbuff = realloc( url->buffer, url->buffer_len + ( size - rembuff ) );
85 if ( newbuff == NULL ) {
86 fprintf( stderr, "callback buffer grow failed\n" );
87 size = rembuff;
88 } else {
89 /* realloc succeeded increase buffer size */
90 url->buffer_len += size - rembuff;
91 url->buffer = newbuff;
92 }
93 }
94
95 memcpy( &url->buffer[url->buffer_pos], buffer, size );
96 url->buffer_pos += size;
97
98 return size;
99}
100
101/* use to attempt to fill the read buffer up to requested number of bytes */
102static int fill_buffer( URL_FILE *file, size_t want ) {
103 fd_set fdread;
104 fd_set fdwrite;
105 fd_set fdexcep;
106 struct timeval timeout;
107 int rc;
108 CURLMcode mc; /* curl_multi_fdset() return code */
109
110 /* only attempt to fill buffer if transactions still running and buffer
111 * doesn't exceed required size already
112 */
113 if ( ( !file->still_running ) || ( file->buffer_pos > want ) )
114 return 0;
115
116 /* attempt to fill buffer */
117 do {
118 int maxfd = -1;
119 long curl_timeo = -1;
120
121 FD_ZERO( &fdread );
122 FD_ZERO( &fdwrite );
123 FD_ZERO( &fdexcep );
124
125 /* set a suitable timeout to fail on */
126 timeout.tv_sec = 60; /* 1 minute */
127 timeout.tv_usec = 0;
128
129 curl_multi_timeout( multi_handle, &curl_timeo );
130 if ( curl_timeo >= 0 ) {
131 timeout.tv_sec = curl_timeo / 1000;
132 if ( timeout.tv_sec > 1 )
133 timeout.tv_sec = 1;
134 else
135 timeout.tv_usec = ( curl_timeo % 1000 ) * 1000;
136 }
137
138 /* get file descriptors from the transfers */
139 mc = curl_multi_fdset( multi_handle, &fdread, &fdwrite, &fdexcep,
140 &maxfd );
141
142 if ( mc != CURLM_OK ) {
143 fprintf( stderr, "curl_multi_fdset() failed, code %d.\n", mc );
144 break;
145 }
146
147 /* On success the value of maxfd is guaranteed to be >= -1. We call
148 select(maxfd + 1, ...); specially in case of (maxfd == -1) there are
149 no fds ready yet so we call select(0, ...) --or Sleep() on Windows--
150 to sleep 100ms, which is the minimum suggested value in the
151 curl_multi_fdset() doc. */
152
153 if ( maxfd == -1 ) {
154#ifdef _WIN32
155 Sleep( 100 );
156 rc = 0;
157#else
158 /* Portable sleep for platforms other than Windows. */
159 struct timeval wait = { 0, 100 * 1000 }; /* 100ms */
160 rc = select( 0, NULL, NULL, NULL, &wait );
161#endif
162 } else {
163 /* Note that on some platforms 'timeout' may be modified by select().
164 If you need access to the original value save a copy beforehand. */
165 rc = select( maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout );
166 }
167
168 switch ( rc ) {
169 case -1:
170 /* select error */
171 break;
172
173 case 0:
174 default:
175 /* timeout or readable/writable sockets */
176 curl_multi_perform( multi_handle, &file->still_running );
177 break;
178 }
179 } while ( file->still_running && ( file->buffer_pos < want ) );
180 return 1;
181}
182
183/* use to remove want bytes from the front of a files buffer */
184static int use_buffer( URL_FILE *file, size_t want ) {
185 /* sort out buffer */
186 if ( ( file->buffer_pos - want ) <= 0 ) {
187 /* ditch buffer - write will recreate */
188 free( file->buffer );
189 file->buffer = NULL;
190 file->buffer_pos = 0;
191 file->buffer_len = 0;
192 } else {
193 /* move rest down make it available for later */
194 memmove( file->buffer,
195 &file->buffer[want], ( file->buffer_pos - want ) );
196
197 file->buffer_pos -= want;
198 }
199 return 0;
200}
201
202URL_FILE *url_fopen( const char *url, const char *operation ) {
203 /* this code could check for URLs or types in the 'url' and
204 basically use the real fopen() for standard files */
205
206 URL_FILE *file;
207 ( void ) operation;
208
209 file = calloc( 1, sizeof( URL_FILE ) );
210 if ( !file )
211 return NULL;
212
213 file->handle.file = fopen( url, operation );
214 if ( file->handle.file ) {
215 file->type = CFTYPE_FILE; /* marked as file */
216 } else if ( index_of( ':', url ) > -1 ) {
217 file->type = CFTYPE_CURL; /* marked as URL */
218 file->handle.curl = curl_easy_init( );
219
220 curl_easy_setopt( file->handle.curl, CURLOPT_URL, url );
221 curl_easy_setopt( file->handle.curl, CURLOPT_WRITEDATA, file );
222 curl_easy_setopt( file->handle.curl, CURLOPT_VERBOSE, 0L );
223 curl_easy_setopt( file->handle.curl, CURLOPT_WRITEFUNCTION,
224 write_callback );
225 /* use the share object */
226 curl_easy_setopt( file->handle.curl, CURLOPT_SHARE, io_share );
227
228
229 if ( !multi_handle )
230 multi_handle = curl_multi_init( );
231
232 curl_multi_add_handle( multi_handle, file->handle.curl );
233
234 /* lets start the fetch */
235 curl_multi_perform( multi_handle, &file->still_running );
236
237 if ( ( file->buffer_pos == 0 ) && ( !file->still_running ) ) {
238 /* if still_running is 0 now, we should return NULL */
239
240 /* make sure the easy handle is not in the multi handle anymore */
241 curl_multi_remove_handle( multi_handle, file->handle.curl );
242
243 /* cleanup */
244 curl_easy_cleanup( file->handle.curl );
245
246 free( file );
247
248 file = NULL;
249 }
250 } else {
251 file->type = CFTYPE_NONE;
252 /* not a file, and doesn't look like a URL. */
253 }
254
255 return file;
256}
257
258int url_fclose( URL_FILE *file ) {
259 int ret = 0; /* default is good return */
260
261 switch ( file->type ) {
262 case CFTYPE_FILE:
263 ret = fclose( file->handle.file ); /* passthrough */
264 break;
265
266 case CFTYPE_CURL:
267 /* make sure the easy handle is not in the multi handle anymore */
268 curl_multi_remove_handle( multi_handle, file->handle.curl );
269
270 /* cleanup */
271 curl_easy_cleanup( file->handle.curl );
272 break;
273
274 default: /* unknown or supported type - oh dear */
275 ret = EOF;
276 errno = EBADF;
277 break;
278 }
279
280 free( file->buffer ); /* free any allocated buffer space */
281 free( file );
282
283 return ret;
284}
285
286int url_feof( URL_FILE *file ) {
287 int ret = 0;
288
289 switch ( file->type ) {
290 case CFTYPE_FILE:
291 ret = feof( file->handle.file );
292 break;
293
294 case CFTYPE_CURL:
295 if ( ( file->buffer_pos == 0 ) && ( !file->still_running ) )
296 ret = 1;
297 break;
298
299 default: /* unknown or supported type - oh dear */
300 ret = -1;
301 errno = EBADF;
302 break;
303 }
304 return ret;
305}
306
307size_t url_fread( void *ptr, size_t size, size_t nmemb, URL_FILE *file ) {
308 size_t want;
309
310 switch ( file->type ) {
311 case CFTYPE_FILE:
312 want = fread( ptr, size, nmemb, file->handle.file );
313 break;
314
315 case CFTYPE_CURL:
316 want = nmemb * size;
317
318 fill_buffer( file, want );
319
320 /* check if there's data in the buffer - if not fill_buffer()
321 * either errored or EOF */
322 if ( !file->buffer_pos )
323 return 0;
324
325 /* ensure only available data is considered */
326 if ( file->buffer_pos < want )
327 want = file->buffer_pos;
328
329 /* xfer data to caller */
330 memcpy( ptr, file->buffer, want );
331
332 use_buffer( file, want );
333
334 want = want / size; /* number of items */
335 break;
336
337 default: /* unknown or supported type - oh dear */
338 want = 0;
339 errno = EBADF;
340 break;
341
342 }
343 return want;
344}
345
346char *url_fgets( char *ptr, size_t size, URL_FILE *file ) {
347 size_t want = size - 1; /* always need to leave room for zero termination */
348 size_t loop;
349
350 switch ( file->type ) {
351 case CFTYPE_FILE:
352 ptr = fgets( ptr, ( int ) size, file->handle.file );
353 break;
354
355 case CFTYPE_CURL:
356 fill_buffer( file, want );
357
358 /* check if there's data in the buffer - if not fill either errored or
359 * EOF */
360 if ( !file->buffer_pos )
361 return NULL;
362
363 /* ensure only available data is considered */
364 if ( file->buffer_pos < want )
365 want = file->buffer_pos;
366
367 /*buffer contains data */
368 /* look for newline or eof */
369 for ( loop = 0; loop < want; loop++ ) {
370 if ( file->buffer[loop] == '\n' ) {
371 want = loop + 1; /* include newline */
372 break;
373 }
374 }
375
376 /* xfer data to caller */
377 memcpy( ptr, file->buffer, want );
378 ptr[want] = 0; /* always null terminate */
379
380 use_buffer( file, want );
381
382 break;
383
384 default: /* unknown or supported type - oh dear */
385 ptr = NULL;
386 errno = EBADF;
387 break;
388 }
389
390 return ptr; /*success */
391}
392
393void url_rewind( URL_FILE *file ) {
394 switch ( file->type ) {
395 case CFTYPE_FILE:
396 rewind( file->handle.file ); /* passthrough */
397 break;
398
399 case CFTYPE_CURL:
400 /* halt transaction */
401 curl_multi_remove_handle( multi_handle, file->handle.curl );
402
403 /* restart */
404 curl_multi_add_handle( multi_handle, file->handle.curl );
405
406 /* ditch buffer - write will recreate - resets stream pos */
407 free( file->buffer );
408 file->buffer = NULL;
409 file->buffer_pos = 0;
410 file->buffer_len = 0;
411
412 break;
413
414 default: /* unknown or supported type - oh dear */
415 break;
416 }
417}
418
419#ifdef FOPEN_STANDALONE
420#define FGETSFILE "fgets.test"
421#define FREADFILE "fread.test"
422#define REWINDFILE "rewind.test"
423
424/* Small main program to retrieve from a url using fgets and fread saving the
425 * output to two test files (note the fgets method will corrupt binary files if
426 * they contain 0 chars */
427int main( int argc, char *argv[] ) {
428 URL_FILE *handle;
429 FILE *outf;
430
431 size_t nread;
432 char buffer[256];
433 const char *url;
434
435 CURL *curl;
436 CURLcode res;
437
438 curl_global_init( CURL_GLOBAL_DEFAULT );
439
440 curl = curl_easy_init( );
441
442
443 if ( argc < 2 )
444 url = "http://192.168.7.3/testfile"; /* default to testurl */
445 else
446 url = argv[1]; /* use passed url */
447
448 /* copy from url line by line with fgets */
449 outf = fopen( FGETSFILE, "wb+" );
450 if ( !outf ) {
451 perror( "couldn't open fgets output file\n" );
452 return 1;
453 }
454
455 handle = url_fopen( url, "r" );
456 if ( !handle ) {
457 printf( "couldn't url_fopen() %s\n", url );
458 fclose( outf );
459 return 2;
460 }
461
462 while ( !url_feof( handle ) ) {
463 url_fgets( buffer, sizeof( buffer ), handle );
464 fwrite( buffer, 1, strlen( buffer ), outf );
465 }
466
467 url_fclose( handle );
468
469 fclose( outf );
470
471
472 /* Copy from url with fread */
473 outf = fopen( FREADFILE, "wb+" );
474 if ( !outf ) {
475 perror( "couldn't open fread output file\n" );
476 return 1;
477 }
478
479 handle = url_fopen( "testfile", "r" );
480 if ( !handle ) {
481 printf( "couldn't url_fopen() testfile\n" );
482 fclose( outf );
483 return 2;
484 }
485
486 do {
487 nread = url_fread( buffer, 1, sizeof( buffer ), handle );
488 fwrite( buffer, 1, nread, outf );
489 } while ( nread );
490
491 url_fclose( handle );
492
493 fclose( outf );
494
495
496 /* Test rewind */
497 outf = fopen( REWINDFILE, "wb+" );
498 if ( !outf ) {
499 perror( "couldn't open fread output file\n" );
500 return 1;
501 }
502
503 handle = url_fopen( "testfile", "r" );
504 if ( !handle ) {
505 printf( "couldn't url_fopen() testfile\n" );
506 fclose( outf );
507 return 2;
508 }
509
510 nread = url_fread( buffer, 1, sizeof( buffer ), handle );
511 fwrite( buffer, 1, nread, outf );
512 url_rewind( handle );
513
514 buffer[0] = '\n';
515 fwrite( buffer, 1, 1, outf );
516
517 nread = url_fread( buffer, 1, sizeof( buffer ), handle );
518 fwrite( buffer, 1, nread, outf );
519
520 url_fclose( handle );
521
522 fclose( outf );
523
524 return 0; /* all done */
525}
526#endif
void url_rewind(URL_FILE *file)
Definition fopen.c:393
int url_feof(URL_FILE *file)
Definition fopen.c:286
URL_FILE * url_fopen(const char *url, const char *operation)
Definition fopen.c:202
char * url_fgets(char *ptr, size_t size, URL_FILE *file)
Definition fopen.c:346
size_t url_fread(void *ptr, size_t size, size_t nmemb, URL_FILE *file)
Definition fopen.c:307
int url_fclose(URL_FILE *file)
Definition fopen.c:258
int still_running
Definition fopen.h:70
enum fcurl_type_e type
Definition fopen.h:61
@ CFTYPE_CURL
Definition fopen.h:57
@ CFTYPE_FILE
Definition fopen.h:56
@ CFTYPE_NONE
Definition fopen.h:55
union fcurl_data::@0 handle
size_t buffer_len
Definition fopen.h:68
size_t buffer_pos
Definition fopen.h:69
char * buffer
Definition fopen.h:67
int main(int argc, char *argv[])
main entry point; parse command line arguments, initialise the environment, and enter the read-eval-p...
Definition init.c:206
CURLSH * io_share
The sharing hub for all connections.
Definition io.c:46
int index_of(char c, const char *s)
Definition utils.c:15