Post Scarcity
A prototype for a post scarcity programming environment
Loading...
Searching...
No Matches
io.c
Go to the documentation of this file.
1/*
2 * io.c
3 *
4 * Communication between PSSE and the outside world, via libcurl. NOTE
5 * that this file destructively changes metadata on URL connections,
6 * because the metadata is not available until the stream has been read
7 * from. It would be better to find a workaround!
8 *
9 * (c) 2019 Simon Brooke <simon@journeyman.cc>
10 * Licensed under GPL version 2.0, or, at your option, any later version.
11 */
12
13#include <grp.h>
14#include <langinfo.h>
15#include <pwd.h>
16#include <stdlib.h>
17#include <string.h>
18#include <time.h>
19#include <sys/stat.h>
20#include <sys/types.h>
21#include <unistd.h>
22#include <uuid/uuid.h>
23/*
24 * wide characters
25 */
26#include <wchar.h>
27#include <wctype.h>
28
29#include <curl/curl.h>
30
31#include "arith/integer.h"
32#include "debug.h"
33#include "io/fopen.h"
34#include "io/io.h"
35#include "memory/conspage.h"
37#include "ops/intern.h"
38#include "ops/lispops.h"
39#include "utils.h"
40
41/**
42 * The sharing hub for all connections. TODO: Ultimately this probably doesn't
43 * work for a multi-user environment and we will need one sharing hub for each
44 * user, or else we will need to not share at least cookies and ssl sessions.
45 */
46CURLSH *io_share;
47
48/**
49 * @brief bound to the Lisp string representing C_IO_IN in initialisation.
50 */
52/**
53 * @brief bound to the Lisp string representing C_IO_OUT in initialisation.
54 */
56
57
58/**
59 * Allow a one-character unget facility. This may not be enough - we may need
60 * to allocate a buffer.
61 */
62wint_t ungotten = 0;
63
64/**
65 * Initialise the I/O subsystem.
66 *
67 * @return 0 on success; any other value means failure.
68 */
69int io_init( ) {
70 int result = curl_global_init( CURL_GLOBAL_SSL );
71
72 io_share = curl_share_init( );
73
74 if ( result == 0 ) {
75 curl_share_setopt( io_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT );
76 curl_share_setopt( io_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE );
77 curl_share_setopt( io_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS );
78 curl_share_setopt( io_share, CURLSHOPT_SHARE,
79 CURL_LOCK_DATA_SSL_SESSION );
80 curl_share_setopt( io_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL );
81 }
82
83 return result;
84}
85
86/**
87 * Convert this lisp string-like-thing (also works for symbols, and, later
88 * keywords) into a UTF-8 string. NOTE that the returned value has been
89 * malloced and must be freed. TODO: candidate to moving into a utilities
90 * file.
91 *
92 * @param s the lisp string or symbol;
93 * @return the c string.
94 */
96 char *result = NULL;
97
98 if ( stringp( s ) || symbolp( s ) ) {
99 int len = 0;
100
101 for ( struct cons_pointer c = s; !nilp( c );
102 c = pointer2cell( c ).payload.string.cdr ) {
103 len++;
104 }
105
106 wchar_t *buffer = calloc( len + 1, sizeof( wchar_t ) );
107 /* worst case, one wide char = four utf bytes */
108 result = calloc( ( len * 4 ) + 1, sizeof( char ) );
109
110 int i = 0;
111 for ( struct cons_pointer c = s; !nilp( c );
112 c = pointer2cell( c ).payload.string.cdr ) {
113 buffer[i++] = pointer2cell( c ).payload.string.character;
114 }
115
116 wcstombs( result, buffer, len );
117 free( buffer );
118 }
119
120 debug_print( L"lisp_string_to_c_string( ", DEBUG_IO );
122 debug_printf( DEBUG_IO, L") => '%s'\n", result );
123
124 return result;
125}
126
127
128/**
129 * given this file handle f, return a new url_file handle wrapping it.
130 *
131 * @param f the file to be wrapped;
132 * @return the new handle, or null if no such handle could be allocated.
133 */
135 URL_FILE *result = ( URL_FILE * ) malloc( sizeof( URL_FILE ) );
136
137 if ( result != NULL ) {
138 result->type = CFTYPE_FILE, result->handle.file = f;
139 }
140
141 return result;
142}
143
144
145/**
146 * get one wide character from the buffer.
147 *
148 * @param file the stream to read from;
149 * @return the next wide character on the stream, or zero if no more.
150 */
151wint_t url_fgetwc( URL_FILE *input ) {
152 wint_t result = -1;
153
154 if ( ungotten != 0 ) {
155 /* TODO: not thread safe */
156 result = ungotten;
157 ungotten = 0;
158 } else {
159 switch ( input->type ) {
160 case CFTYPE_FILE:
161 fwide( input->handle.file, 1 ); /* wide characters */
162 result = fgetwc( input->handle.file ); /* passthrough */
163 break;
164
165 case CFTYPE_CURL:{
166 char *cbuff =
167 calloc( sizeof( wchar_t ) + 2, sizeof( char ) );
168 wchar_t *wbuff = calloc( 2, sizeof( wchar_t ) );
169
170 size_t count = 0;
171
172 debug_print( L"url_fgetwc: about to call url_fgets\n",
173 DEBUG_IO );
174 url_fgets( cbuff, 2, input );
175 debug_print( L"url_fgetwc: back from url_fgets\n",
176 DEBUG_IO );
177 int c = ( int ) cbuff[0];
178 // TODO: risk of reading off cbuff?
180 L"url_fgetwc: cbuff is '%s'; (first) character = %d (%c)\n",
181 cbuff, c, c & 0xf7 );
182 /* The value of each individual byte indicates its UTF-8 function, as follows:
183 *
184 * 00 to 7F hex (0 to 127): first and only byte of a sequence.
185 * 80 to BF hex (128 to 191): continuing byte in a multi-byte sequence.
186 * C2 to DF hex (194 to 223): first byte of a two-byte sequence.
187 * E0 to EF hex (224 to 239): first byte of a three-byte sequence.
188 * F0 to FF hex (240 to 255): first byte of a four-byte sequence.
189 */
190 if ( c <= 0xf7 ) {
191 count = 1;
192 } else if ( c >= 0xc2 && c <= 0xdf ) {
193 count = 2;
194 } else if ( c >= 0xe0 && c <= 0xef ) {
195 count = 3;
196 } else if ( c >= 0xf0 && c <= 0xff ) {
197 count = 4;
198 }
199
200 if ( count > 1 ) {
201 url_fgets( ( char * ) &cbuff[1], count, input );
202 }
203 mbstowcs( wbuff, cbuff, 2 ); //(char *)(&input->buffer[input->buffer_pos]), 1 );
204 result = wbuff[0];
205
206 free( wbuff );
207 free( cbuff );
208 }
209 break;
210 case CFTYPE_NONE:
211 break;
212 }
213 }
214
215 debug_printf( DEBUG_IO, L"url_fgetwc returning %d (%C)\n", result,
216 result );
217 return result;
218}
219
220wint_t url_ungetwc( wint_t wc, URL_FILE *input ) {
221 wint_t result = -1;
222
223 switch ( input->type ) {
224 case CFTYPE_FILE:
225 fwide( input->handle.file, 1 ); /* wide characters */
226 result = ungetwc( wc, input->handle.file ); /* passthrough */
227 break;
228
229 case CFTYPE_CURL:{
230 ungotten = wc;
231 break;
232 case CFTYPE_NONE:
233 break;
234 }
235 }
236
237 return result;
238}
239
240
241/**
242 * Function, sort-of: close the file indicated by my first arg, and return
243 * nil. If the first arg is not a stream, does nothing. All other args are
244 * ignored.
245 *
246 * * (close stream)
247 *
248 * @param frame my stack_frame.
249 * @param frame_pointer a pointer to my stack_frame.
250 * @param env my environment.
251 * @return T if the stream was successfully closed, else NIL.
252 */
253struct cons_pointer
254lisp_close( struct stack_frame *frame, struct cons_pointer frame_pointer,
255 struct cons_pointer env ) {
256 struct cons_pointer result = NIL;
257
258 if ( readp( frame->arg[0] ) || writep( frame->arg[0] ) ) {
259 if ( url_fclose( pointer2cell( frame->arg[0] ).payload.stream.stream )
260 == 0 ) {
261 result = TRUE;
262 }
263 }
264
265 return result;
266}
267
268struct cons_pointer add_meta_integer( struct cons_pointer meta, wchar_t *key,
269 long int value ) {
270 return
273 make_integer( value, NIL ) ), meta );
274}
275
276struct cons_pointer add_meta_string( struct cons_pointer meta, wchar_t *key,
277 char *value ) {
278 value = trim( value );
279 wchar_t buffer[strlen( value ) + 1];
280 mbstowcs( buffer, value, strlen( value ) + 1 );
281
283 c_string_to_lisp_string( buffer ) ), meta );
284}
285
286struct cons_pointer add_meta_time( struct cons_pointer meta, wchar_t *key,
287 time_t *value ) {
288 /* I don't yet have a concept of a date-time object, which is a
289 * bit of an oversight! */
290 char datestring[256];
291
292 strftime( datestring,
293 sizeof( datestring ),
294 nl_langinfo( D_T_FMT ), localtime( value ) );
295
296 return add_meta_string( meta, key, datestring );
297}
298
299/**
300 * Callback to assemble metadata for a URL stream. This is naughty because
301 * it modifies data, but it's really the only way to create metadata.
302 */
303static size_t write_meta_callback( char *string, size_t size, size_t nmemb,
304 struct cons_pointer stream ) {
305 struct cons_space_object *cell = &pointer2cell( stream );
306
307 /* make a copy of the string that we can destructively change */
308 char *s = calloc( strlen( string ), sizeof( char ) );
309
310 strcpy( s, string );
311
312 if ( strncmp( &cell->tag.bytes[0], READTAG, 4 ) ||
313 strncmp( &cell->tag.bytes[0], WRITETAG, 4 ) ) {
314 int offset = index_of( ':', s );
315
316 if ( offset != -1 ) {
317 s[offset] = ( char ) 0;
318 char *name = trim( s );
319 char *value = trim( &s[++offset] );
320 wchar_t wname[strlen( name )];
321
322 mbstowcs( wname, name, strlen( name ) + 1 );
323
324 cell->payload.stream.meta =
325 add_meta_string( cell->payload.stream.meta, wname, value );
326
328 L"write_meta_callback: added header '%s': value '%s'\n",
329 name, value );
330 } else if ( strncmp( "HTTP", s, 4 ) == 0 ) {
331 int offset = index_of( ' ', s );
332 char *value = trim( &s[offset] );
333
334 cell->payload.stream.meta =
336 ( cell->payload.stream.meta, L"status",
337 value ), L"status-code", strtol( value,
338 NULL,
339 10 ) );
340
342 L"write_meta_callback: added header 'status': value '%s'\n",
343 value );
344 } else {
346 L"write_meta_callback: header passed with no colon: '%s'\n",
347 s );
348 }
349 } else {
351 ( L"Pointer passed to write_meta_callback did not point to a stream: ",
352 DEBUG_IO );
353 debug_dump_object( stream, DEBUG_IO );
354 }
355
356 free( s );
357 return strlen( string );
358}
359
360void collect_meta( struct cons_pointer stream, char *url ) {
361 struct cons_space_object *cell = &pointer2cell( stream );
362 URL_FILE *s = pointer2cell( stream ).payload.stream.stream;
363 struct cons_pointer meta =
364 add_meta_string( cell->payload.stream.meta, L"url", url );
365 struct stat statbuf;
366 int result = stat( url, &statbuf );
367 struct passwd *pwd;
368 struct group *grp;
369
370 switch ( s->type ) {
371 case CFTYPE_NONE:
372 break;
373 case CFTYPE_FILE:
374 if ( result == 0 ) {
375 if ( ( pwd = getpwuid( statbuf.st_uid ) ) != NULL ) {
376 meta = add_meta_string( meta, L"owner", pwd->pw_name );
377 } else {
378 meta = add_meta_integer( meta, L"owner", statbuf.st_uid );
379 }
380
381 if ( ( grp = getgrgid( statbuf.st_gid ) ) != NULL ) {
382 meta = add_meta_string( meta, L"group", grp->gr_name );
383 } else {
384 meta = add_meta_integer( meta, L"group", statbuf.st_gid );
385 }
386
387 meta =
388 add_meta_integer( meta, L"size",
389 ( intmax_t ) statbuf.st_size );
390
391 meta = add_meta_time( meta, L"modified", &statbuf.st_mtime );
392 }
393 break;
394 case CFTYPE_CURL:
395 curl_easy_setopt( s->handle.curl, CURLOPT_VERBOSE, 1L );
396 curl_easy_setopt( s->handle.curl, CURLOPT_HEADERFUNCTION,
397 write_meta_callback );
398 curl_easy_setopt( s->handle.curl, CURLOPT_HEADERDATA, stream );
399 break;
400 }
401
402 /* this is destructive change before the cell is released into the
403 * wild, and consequently permissible, just. */
404 cell->payload.stream.meta = meta;
405}
406
407/**
408 * Resutn the current default input, or of `inputp` is false, output stream from
409 * this `env`ironment.
410 */
411struct cons_pointer get_default_stream( bool inputp, struct cons_pointer env ) {
412 struct cons_pointer result = NIL;
413 struct cons_pointer stream_name = inputp ? lisp_io_in : lisp_io_out;
414
415 result = c_assoc( stream_name, env );
416
417 return result;
418}
419
420
421/**
422 * Function: return a stream open on the URL indicated by the first argument;
423 * if a second argument is present and is non-nil, open it for reading. At
424 * present, further arguments are ignored and there is no mechanism to open
425 * to append, or error if the URL is faulty or indicates an unavailable
426 * resource.
427 *
428 * * (open url)
429 *
430 * @param frame my stack_frame.
431 * @param frame_pointer a pointer to my stack_frame.
432 * @param env my environment.
433 * @return a string of one character, namely the next available character
434 * on my stream, if any, else NIL.
435 */
436struct cons_pointer
437lisp_open( struct stack_frame *frame, struct cons_pointer frame_pointer,
438 struct cons_pointer env ) {
439 struct cons_pointer result = NIL;
440
441 if ( stringp( frame->arg[0] ) ) {
442 char *url = lisp_string_to_c_string( frame->arg[0] );
443
444 if ( nilp( frame->arg[1] ) ) {
445 URL_FILE *stream = url_fopen( url, "r" );
446
448 L"lisp_open: stream @ %ld, stream type = %d, stream handle = %ld\n",
449 ( long int ) &stream, ( int ) stream->type,
450 ( long int ) stream->handle.file );
451
452 switch ( stream->type ) {
453 case CFTYPE_NONE:
454 return
456 ( L"Could not open stream" ),
457 frame_pointer );
458 break;
459 case CFTYPE_FILE:
460 if ( stream->handle.file == NULL ) {
461 return
463 ( L"Could not open file" ),
464 frame_pointer );
465 }
466 break;
467 case CFTYPE_CURL:
468 /* can't tell whether a URL is bad without reading it */
469 break;
470 }
471
472 result = make_read_stream( stream, NIL );
473 } else {
474 // TODO: anything more complex is a problem for another day.
475 URL_FILE *stream = url_fopen( url, "w" );
476 result = make_write_stream( stream, NIL );
477 }
478
479 if ( pointer2cell( result ).payload.stream.stream == NULL ) {
480 result = NIL;
481 } else {
482 collect_meta( result, url );
483 }
484
485 free( url );
486 }
487
488 return result;
489}
490
491/**
492 * Function: return the next character from the stream indicated by arg 0;
493 * further arguments are ignored.
494 *
495 * * (read-char stream)
496 *
497 * @param frame my stack_frame.
498 * @param frame_pointer a pointer to my stack_frame.
499 * @param env my environment.
500 * @return a string of one character, namely the next available character
501 * on my stream, if any, else NIL.
502 */
503struct cons_pointer
504lisp_read_char( struct stack_frame *frame, struct cons_pointer frame_pointer,
505 struct cons_pointer env ) {
506 struct cons_pointer result = NIL;
507
508 if ( readp( frame->arg[0] ) ) {
509 result =
511 ( pointer2cell( frame->arg[0] ).payload.stream.
512 stream ), NIL );
513 }
514
515 return result;
516}
517
518/**
519 * Function: return a string representing all characters from the stream
520 * indicated by arg 0; further arguments are ignored.
521 *
522 * TODO: it should be possible to optionally pass a string URL to this function,
523 *
524 * * (slurp stream)
525 *
526 * @param frame my stack_frame.
527 * @param frame_pointer a pointer to my stack_frame.
528 * @param env my environment.
529 * @return a string of one character, namely the next available character
530 * on my stream, if any, else NIL.
531 */
532struct cons_pointer
533lisp_slurp( struct stack_frame *frame, struct cons_pointer frame_pointer,
534 struct cons_pointer env ) {
535 struct cons_pointer result = NIL;
536
537 if ( readp( frame->arg[0] ) ) {
538 URL_FILE *stream = pointer2cell( frame->arg[0] ).payload.stream.stream;
539 struct cons_pointer cursor = make_string( url_fgetwc( stream ), NIL );
540 result = cursor;
541
542 for ( wint_t c = url_fgetwc( stream ); !url_feof( stream ) && c != 0;
543 c = url_fgetwc( stream ) ) {
544 debug_print( L"slurp: cursor is: ", DEBUG_IO );
545 debug_dump_object( cursor, DEBUG_IO );
546 debug_print( L"; result is: ", DEBUG_IO );
547 debug_dump_object( result, DEBUG_IO );
549
550 struct cons_space_object *cell = &pointer2cell( cursor );
551 cursor = make_string( ( wchar_t ) c, NIL );
552 cell->payload.string.cdr = cursor;
553 }
554 }
555
556 return result;
557}
struct cons_pointer c_string_to_lisp_keyword(wchar_t *symbol)
Return a lisp keyword representation of this wide character string.
struct cons_pointer c_string_to_lisp_string(wchar_t *string)
Return a lisp string representation of this wide character string.
struct cons_pointer make_cons(struct cons_pointer car, struct cons_pointer cdr)
Construct a cons cell from this pair of pointers.
#define writep(conspoint)
true if conspoint points to a write stream cell, else false.
union cons_space_object::@2 tag
union cons_space_object::@3 payload
#define NIL
a cons pointer which points to the special NIL cell
struct cons_pointer make_exception(struct cons_pointer message, struct cons_pointer frame_pointer)
Construct an exception cell.
#define READTAG
An open read stream.
#define readp(conspoint)
true if conspoint points to a read stream cell, else false
struct cons_pointer make_string(wint_t c, struct cons_pointer tail)
Construct a string from the character c and this tail.
#define nilp(conspoint)
true if conspoint points to the special cell NIL, else false (there should only be one of these so it...
#define symbolp(conspoint)
true if conspoint points to a symbol cell, else false
struct cons_pointer make_write_stream(URL_FILE *output, struct cons_pointer metadata)
Construct a cell which points to a stream open for writing.
struct cons_pointer c_string_to_lisp_string(wchar_t *string)
Return a lisp string representation of this wide character string.
#define TRUE
a cons pointer which points to the special T cell
#define stringp(conspoint)
true if conspoint points to a string cell, else false
struct cons_pointer make_read_stream(URL_FILE *input, struct cons_pointer metadata)
Construct a cell which points to a stream open for reading.
#define WRITETAG
An open write stream.
#define pointer2cell(pointer)
given a cons_pointer as argument, return the cell.
An indirect pointer to a cons cell.
an object in cons space.
A stack frame.
void debug_println(int level)
print a line feed to stderr, if verbosity matches level.
Definition debug.c:85
void debug_dump_object(struct cons_pointer pointer, int level)
Like dump_object, q.v., but protected by the verbosity mechanism.
Definition debug.c:136
void debug_printf(int level, wchar_t *format,...)
wprintf adapted for the debug logging system.
Definition debug.c:101
void debug_print(wchar_t *message, int level)
print this debug message to stderr, if verbosity matches level.
Definition debug.c:41
void debug_print_object(struct cons_pointer pointer, int level)
print the object indicated by this pointer to stderr, if verbosity matches level.
Definition debug.c:119
#define DEBUG_IO
Print messages debugging input/output operations.
Definition debug.h:56
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
int url_fclose(URL_FILE *file)
Definition fopen.c:258
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
struct cons_pointer make_integer(int64_t value, struct cons_pointer more)
Allocate an integer cell representing this value and return a cons_pointer to it.
Definition integer.c:89
struct cons_pointer c_assoc(struct cons_pointer key, struct cons_pointer store)
Implementation of assoc in C.
Definition intern.c:327
int io_init()
Initialise the I/O subsystem.
Definition io.c:69
struct cons_pointer add_meta_integer(struct cons_pointer meta, wchar_t *key, long int value)
Definition io.c:268
struct cons_pointer add_meta_string(struct cons_pointer meta, wchar_t *key, char *value)
Definition io.c:276
wint_t url_ungetwc(wint_t wc, URL_FILE *input)
Definition io.c:220
struct cons_pointer lisp_io_in
bound to the Lisp string representing C_IO_IN in initialisation.
Definition io.c:51
wint_t url_fgetwc(URL_FILE *input)
get one wide character from the buffer.
Definition io.c:151
struct cons_pointer lisp_open(struct stack_frame *frame, struct cons_pointer frame_pointer, struct cons_pointer env)
Function: return a stream open on the URL indicated by the first argument; if a second argument is pr...
Definition io.c:437
struct cons_pointer lisp_io_out
bound to the Lisp string representing C_IO_OUT in initialisation.
Definition io.c:55
struct cons_pointer lisp_slurp(struct stack_frame *frame, struct cons_pointer frame_pointer, struct cons_pointer env)
Function: return a string representing all characters from the stream indicated by arg 0; further arg...
Definition io.c:533
struct cons_pointer add_meta_time(struct cons_pointer meta, wchar_t *key, time_t *value)
Definition io.c:286
char * lisp_string_to_c_string(struct cons_pointer s)
Convert this lisp string-like-thing (also works for symbols, and, later keywords) into a UTF-8 string...
Definition io.c:95
URL_FILE * file_to_url_file(FILE *f)
given this file handle f, return a new url_file handle wrapping it.
Definition io.c:134
void collect_meta(struct cons_pointer stream, char *url)
Definition io.c:360
struct cons_pointer get_default_stream(bool inputp, struct cons_pointer env)
Resutn the current default input, or of inputp is false, output stream from this environment.
Definition io.c:411
struct cons_pointer lisp_close(struct stack_frame *frame, struct cons_pointer frame_pointer, struct cons_pointer env)
Function, sort-of: close the file indicated by my first arg, and return nil.
Definition io.c:254
CURLSH * io_share
The sharing hub for all connections.
Definition io.c:46
struct cons_pointer lisp_read_char(struct stack_frame *frame, struct cons_pointer frame_pointer, struct cons_pointer env)
Function: return the next character from the stream indicated by arg 0; further arguments are ignored...
Definition io.c:504
wint_t ungotten
Allow a one-character unget facility.
Definition io.c:62
int index_of(char c, const char *s)
Definition utils.c:15
char * trim(char *s)
Definition utils.c:23