File: gcli.c

package info (click to toggle)
golf 601.4.41-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,824 kB
  • sloc: ansic: 20,020; sh: 1,171; makefile: 292
file content (1228 lines) | stat: -rw-r--r-- 54,270 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
// SPDX-License-Identifier: Apache-2.0
// Copyright 2018-2025 Gliim LLC. 
// Licensed under Apache License v2. See LICENSE file.
// On the web http://golf-lang.com/ - this file is part of Golf framework.

// Golf client library. It's part of Golf for the corresponding call* statements

//
// All memory here is OS, except for a few interface pointers. 
// Golf mem is not used here because for MT, it would have to be made
// MT-safe, which would slow it down too much. Thus, all memory here must be absolutely
// proofed for leaks with tools like asan, valgrind etc.
//

// Public API prototypes, must come before any other include because of _GNU_SOURCE, which
// allows golf.h's memmem, strcasestr and such. Once say string.h is included without _GNU_SOURCE
// it doesn't matter that it's defined further down in golf.h!
#include "gcli.h"

// General includes
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <sys/param.h>
#include <sys/wait.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/time.h>

// If memory allocation fails in Golf, this is handled by gg_* functions
// For a SERVICE client, the handling is explicit here in code
#if GG_GOLFSRV==1
#   include "golf.h"
    // OS malloc is used here, and not Golf, due to multithreading. Golf mem alloc is not
    // MT safe, and would be too slow if made to do so.
    void *gg_cli_one (void *inp);
    // Memory adjustment for Golf memory 
#   define GG_ADD_FOR_GOLF (GG_ALIGN)
#else
#   define GG_ADD_FOR_GOLF 0
#   define GG_EMPTY_STRING NULL
#endif
// If manage-memory is false in Golf, then adjustment is 0
// because in this case memory allocated is just like for Client_API
#define GG_MADJ(x) ((x)+GG_ADD_FOR_GOLF)

// Fcgi end request (the body)
typedef struct {
    unsigned char app_status3;
    unsigned char app_status2;
    unsigned char app_status1;
    unsigned char app_status0;
    unsigned char status; // protocol status
    unsigned char future_use[3];
} fc_end_req_body;

// Fcgi begin request (the body)
typedef struct {
    unsigned char role_1;
    unsigned char role_0;
    unsigned char flags;
    unsigned char future_use[5];
} fc_begin_req_body;

// Fcgi request header (body follows afterwards), and an empty header (cont_len=0) marks the 
// end of a stream
typedef struct {
    unsigned char ver;
    unsigned char type;
    unsigned char req_id_1;
    unsigned char req_id_0;
    unsigned char cont_len_1;
    unsigned char cont_len_0;
    unsigned char pad_len;
    unsigned char future_use;
} fc_header;


// Defines
#define GG_UNUSED(x) (void)(x)
#define GG_CLI_RESPONDER 1 // responder role
#define GG_CLI_VER_1 1 // version of fcgi
#define GG_CLI_BEG_REQ 1 // begin request
#define GG_CLI_END_REQ 3 // end request
#define GG_CLI_PARAM 4 // env params record
#define GG_CLI_STDIN 5 // stdin to server
#define GG_CLI_STDOUT 6 // stdout from server
#define GG_CLI_STDERR 7 // stderr from server
#define GG_CLI_GET_VAL_RES 10 // not used
#define GG_CLI_UNK_TYPE 11 // unknown
#define GG_CLI_REQ_DONE 0 // request done record
#define GG_CLI_REQ_OVERLOADED 2 // not used
#define GG_CLI_MAX_LEN 0xffff // maxium record size


// Local variables used among various functions, more since this is even driven
// These are thread-based, each thread has a copy
typedef struct {
    int param_len; // total length of environment passed to server
    char *env; // one large chunk for all of environment, packed in
    int *env_nlen; // lengths of names in env 
    int *env_vlen; // lengths of values in env
    int env_p; // position where we're writing in env
    int sock;  // socket for sending to server
    bool done_header;  // was header read (true) or not (false)
    int msg_remain;  // how many bytes remains to be read in message
    int padding_len;     // padding length (see fastcgi spec), we don't use it
    int req_id;  // req id of a request, we set to 1 always
    int read_done; // true when reading is done, i.e. the number of bytes sent from server is received
    char *errm[GG_CLI_ERR_TOTAL + 1]; // an array of error messages
    struct timeval tout; // timeout seconds, microseconds
    struct timeval remaining; // time remaining processing request
    struct timeval begin; // time when request began
    volatile bool interrupted; // true when interrupted
    gg_cli *fc; // input/output data
    bool initerrm; // if error messages initialized (true), otherwise false
    char *just_query_path; // NULL if there's no query path (i.e. /param/val/param/val...) in fc_l->fc->url_params, or not-NULL if there is
                      // If there is, then query_string and path_info are malloced, otherwise they are just references of url_params and req
    char *query_string; // either url_params as user supplied (if not query path), or malloced (see just_query_path above), this is query string
    char *path_info; // request name , either the same as user supplied, or malloced (see just_query_path above) together with just_query_path
} fc_local;



// Local Function prototypes
static int gg_write_socket(fc_local *fc_l, char *buf, int len);
static int gg_read_socket(fc_local *fc_l, char *buf, int len);
static void shut_sock(fc_local *fc_l);
static void gg_size_env(fc_local *fc_l, int name_len, int val_len);
static int gg_send_env(fc_local *fc_l, char *name, int name_len, char *val, int val_len);
static void fc_server_read(fc_local *fc_l);
static void check_read(fc_local *fc_l, int read_len);
static int gg_connect_socket(fc_local *fc_l, char *conn_string);
static int fc_edge(fc_local *fc_l, int timeout, bool start);
static fc_header build_hdr(fc_local *fc_l, int content_len, int type);
static bool gg_check_timeout(fc_local *fc_l, bool startup);
static bool gg_set_timeout(fc_local *fc_l);
static void begin_request(fc_begin_req_body *beg_req);
static int parse_payload (fc_local *fc_l, int *query_string_l, int *path_info_l);


//
// Functions
//


//
// Read data from socket. 
// buf is data, len is length to read.
// Returns len if read okay or -1 if could not read.
//
int gg_read_socket(fc_local *fc_l, char *buf, int len)
{
    int res;
    int gotten = 0;
    while (true)
    {
        // read can return -1, 0 or >0
        // -1 is error. 0 is server shutdown orderly. >0 is data read and for blocking can be 1..len
        res = read(fc_l->sock, buf+gotten, len-gotten);
        // If -1 or 0, get out of here, error in reading
        if (res <= 0) 
        { 
            if (errno == EINTR) continue; // if call interrupted, continue
            if (!gg_check_timeout(fc_l, false)) return -1; 
            check_read (fc_l, res); // this checks for negative, 0 
            return res;
        }
        gotten += res;
        if (gotten >= len) break; // if gotten message (>= instead of == just in case), get out
        // if not the whole message received, check if we got partial due to timeout and exit with
        // setting interrupted flag
        if (!gg_check_timeout(fc_l, false)) return -1;
        if (!gg_set_timeout(fc_l)) { check_read(fc_l, -1); return -1;}
    }
    return len;
}

//
// Write data to socket. 
// buf is data, len is length to send.
// Returns len if sent okay or -1 if could not write. 
//
int gg_write_socket(fc_local *fc_l, char *buf, int len)
{
    int res;
    int written = 0;
    while (true)
    {
        // Write data
        res = write(fc_l->sock, buf+written, len-written);
        // If error
        if (res < 0) 
        {
            if (errno == EINTR) continue; // if call interrupted, continue
            // Check if timeout
            if (!gg_check_timeout(fc_l, false)) return -1; 
            return -1; // either way return -1, the gg_check_timeout will set timeout flags
        }
        written += res;
        if (written >= len) break; // check if all message sent
        if (!gg_check_timeout(fc_l, false)) return -1; 
        if (!gg_set_timeout(fc_l)) return -1;
    }
    return len;
}


//
// Make a begin-request record for the server. Connection is not kept open.
// The role is GG_CLI_RESPONDER.
//
void begin_request(fc_begin_req_body *beg_req)
{
    beg_req->flags  = 0; // do not keep connection open
    beg_req->role_1 = (GG_CLI_RESPONDER >>  8) & 0xff;
    beg_req->role_0 = GG_CLI_RESPONDER & 0xff;
    memset(beg_req->future_use, 0, sizeof(beg_req->future_use));
}



//
// Build a header for request. content_len is the length of message, type is either
// begin-request or the input to server.
//
fc_header build_hdr(fc_local *fc_l, int content_len, int type)
{
    fc_header header;
    header.cont_len_1 = (content_len >> 8) & 0xff;
    header.cont_len_0 = content_len & 0xff;
    header.type = type;
    // request id is always 1, higher protocol should serialize if needed
    header.req_id_1 = 0;
    header.req_id_0 = fc_l->req_id;
    header.pad_len = 0;
    header.ver = GG_CLI_VER_1;
    header.future_use = 0;
    return header;
}

//
// Close socket to server.
//
void shut_sock(fc_local *fc_l)
{
    if (fc_l->sock != -1) 
    {
        close(fc_l->sock);
    }
    fc_l->sock = -1;
}

//
// When reading from server, check length of data read. This indicates problem
// with connection or success. read_len is the data read.
// fc_l->fc->internal.read_status is set to negative if there's a problem.
// Returns true if okay, false if problem.
//
void check_read(fc_local *fc_l, int read_len)
{
    if (read_len < 0) { fc_l->fc->internal.read_status = GG_CLI_ERR_SOCK_READ; return; } // error in reading from server

    if (read_len == 0)
    {
        // no bytes available any more, this may be some kind of error, probably closed on
        // server side, close the socket first before returning
        // this is when no more bytes to read, but we haven't reached the end of header or message or padding
        if (fc_l->done_header || fc_l->msg_remain > 0 || fc_l->padding_len > 0) 
        {
            fc_l->fc->internal.read_status = GG_CLI_ERR_PROT_ERR;
            return;
        }
        //
        // spurious zero-length read should not happen, but it's not clear if they sometime do, so
        // just in case, we already read the message, and so do not declare error
        //
        if (!fc_l->read_done) fc_l->fc->internal.read_status = GG_CLI_ERR_PROT_ERR;
        return; // done here
    }
}


//
// Read data from the server (this is a client). 
// fc_l->fc->internal.read_status is GG_CLI_ERR_SOCK_READ if error reading, GG_CLI_ERR_PROT_ERR if protocol error, GG_CLI_ERR_BAD_VER if bad fcgi version.
// GG_CLI_ERR_SRV server cannot complete request, GG_CLI_ERR_UNK server says unknown type, GG_CLI_ERR_OUT_MEM if out of memory,
// 0 if okay
//
void fc_server_read(fc_local *fc_l)
{
    fc_header header; // header of a packet

    // We get all the data we want here, this is not event driven, so all variables (such as header)
    // can remain local.
    while (true)
    {
        // Check if header not complete yet first, cannot get message until header is in
        if (fc_l->done_header) 
        {
            // This is when the header is received, now it's message.
            if (fc_l->msg_remain > 0) // this is the end of stream record for header.type
                                 // nothing to read here if fc_l->msg_remain == 0
            {
                char *inbuff;
                // get the buffer to read into
                if (header.type == GG_CLI_STDOUT) inbuff = GG_MADJ(fc_l->fc->internal.data)+fc_l->fc->data_len;
                else if (header.type == GG_CLI_STDERR) inbuff = GG_MADJ(fc_l->fc->internal.error)+fc_l->fc->error_len;
                else inbuff = fc_l->fc->other+fc_l->fc->other_len;

                // read data, this reads all of it specified (msg_remain)
                int cnt = gg_read_socket (fc_l, inbuff, fc_l->msg_remain);
                if (cnt <= 0) return;
                // advance read pointer
                if (header.type == GG_CLI_STDOUT) 
                {
                    fc_l->fc->data_len += cnt;
                    if (fc_l->fc->out_hook != NULL) (*(fc_l->fc->out_hook))(inbuff, cnt, fc_l->fc); // exec out hook if there
                }
                else if (header.type == GG_CLI_STDERR) 
                {
                    fc_l->fc->error_len += cnt;
                    if (fc_l->fc->err_hook != NULL) (*(fc_l->fc->err_hook))(inbuff, cnt, fc_l->fc); // exec err hook if there
                }
                else if (header.type == GG_CLI_END_REQ)// end record 
                {
                    //finish output with null for convenience
                    GG_MADJ(fc_l->fc->internal.data)[fc_l->fc->data_len] = 0;
                    GG_MADJ(fc_l->fc->internal.error)[fc_l->fc->error_len] = 0;

                    // end of request
                    if (cnt != sizeof(fc_end_req_body)) 
                    {
                        fc_l->fc->internal.read_status = GG_CLI_ERR_PROT_ERR;
                        return;
                    }
                    fc_end_req_body *server_end = (fc_end_req_body*)inbuff; // end of request, when server fulfills the request, it sends this
                    fc_l->read_done = true; // whole reply message from server received
                    if(server_end->status != GG_CLI_REQ_DONE) {
                        // this can be GG_CLI_REQ_OVERLOADED overloaded only
                        fc_l->fc->internal.read_status = GG_CLI_ERR_SRV;
                        return;
                    }
                    fc_l->fc->req_status = (server_end->app_status3 << 24) + (server_end->app_status2 << 16) + (server_end->app_status1 <<  8) + (server_end->app_status0);
                    if (fc_l->fc->out_hook != NULL) (*(fc_l->fc->out_hook))("", 0, fc_l->fc); // exec out hook if there, end request
                }
                else fc_l->fc->other_len += cnt;

                fc_l->msg_remain = 0; // nothing else to read
            }
            else 
            {
                ; // nothing to do. This is a "zero-length" record that marks the end of the stream.
                  // Note that "end of stream" does not necessarily mean end of message - there could
                  // be stdout, then stderr, then stdout, then stdout, i.e. different streams and multiples
                  // of them in no particular order. GG_CLI_END_REQ is the end of message, i.e. end of any
                  // streams in it.
            }
            if (header.type == GG_CLI_UNK_TYPE)
            {
                fc_l->fc->internal.read_status = GG_CLI_ERR_UNK;
                return;
            }
            //
            // Check if there's padding, vacuum it.
            // If that was all in this record, then the whole request is done,
            // so set fc_l->done_header to true, and we can start with the next request's response.
            // if there's padding, read it and it's discarded. We don't use padding.
            // Note that a record may have msg_remaining==0 but padding_len>0 - rare but it can happen! 
            //
            if(fc_l->padding_len > 0) 
            {
                char padb[256]; // max length of paddding is 255
                int cnt = gg_read_socket (fc_l, padb, fc_l->padding_len);
                if (cnt <= 0) return;
                fc_l->padding_len = 0;
            }
            fc_l->done_header = false; // all data read, move on to the next header
        }
        else
        {
            // Get the header first. We read the entire header at once.
            int cnt = gg_read_socket (fc_l, (char*)&header, sizeof(header));
            if (cnt <= 0) return;

            if (header.ver != GG_CLI_VER_1) { fc_l->fc->internal.read_status = GG_CLI_ERR_BAD_VER; return; }

            if (header.req_id_0 + (header.req_id_1 << 8) != fc_l->req_id) 
            { // must be 1 as req ID
                fc_l->fc->internal.read_status = GG_CLI_ERR_PROT_ERR;
                return;
            }
            
            fc_l->msg_remain = header.cont_len_0 + (header.cont_len_1 << 8); // msg size to get next
            fc_l->padding_len =  header.pad_len; // padding to get afterwards

            // allocate memory for the reply and/or error, depending on what this header is
            if (header.type == GG_CLI_STDOUT) 
            {
                // Originally we set fc_l->fc->internal.data to calloc(1,GG_ADD_FOR_GOLF+1) so it can be re-alloced
                // Initially fc_l->fc->data_len is 0
                fc_l->fc->internal.data = realloc(fc_l->fc->internal.data, GG_ADD_FOR_GOLF+fc_l->fc->data_len+fc_l->msg_remain+1);
                if (fc_l->fc->internal.data == NULL)
                {
                    fc_l->fc->internal.read_status = GG_CLI_ERR_OUT_MEM;
                    return;
                }
            }
            else if (header.type == GG_CLI_STDERR) 
            {
                // Originally we set fc_l->fc->internal.error to calloc(1,GG_ADD_FOR_GOLF+1) so it can be re-alloced
                // Initially fc_l->fc->error_len is 0
                fc_l->fc->internal.error = realloc(fc_l->fc->internal.error, GG_ADD_FOR_GOLF+fc_l->fc->error_len+fc_l->msg_remain+1);
                if (fc_l->fc->internal.error == NULL) 
                {
                    fc_l->fc->internal.read_status = GG_CLI_ERR_OUT_MEM;
                    return;
                }
            }
            else
            {
                // Originally we set fc_l->fc->other to calloc(1,1) or GG_EMPTY_STRING so it can be re-alloced
                // Initially fc_l->fc->other_len is 0
                // This is the buffer we get GG_CLI_END_REQ into as well.
                fc_l->fc->other = realloc(fc_l->fc->other, fc_l->fc->other_len+fc_l->msg_remain+1);
                if (fc_l->fc->other == NULL) 
                {
                    fc_l->fc->internal.read_status = GG_CLI_ERR_OUT_MEM;
                    return;
                }
            }
            fc_l->done_header = true; // done with header, now read data
        } 

        // exit loop if reply from server received
        if (fc_l->read_done) break; 
    } 
    return; // this is when message read okay
}

void gg_size_env(fc_local *fc_l, int name_len, int val_len)
{
    if (name_len < 0x80) fc_l->param_len++; else fc_l->param_len += 4; 
    if (val_len < 0x80) fc_l->param_len++; else fc_l->param_len += 4; 
    fc_l->param_len += name_len;
    fc_l->param_len += val_len;
}

//
// Send environment to server, this is done prior to sending a request. name/name_len are env var name and its
// length, val/val_len are its value and length. fcgi_inp_param is the stream that is open to the server.
// Returns number of bytes written or -1 if failed;
//
int gg_send_env(fc_local *fc_l, char *name, int name_len, char *val, int val_len)
{
    // First, setup a header, name, then value lengths
    unsigned char hdr[8+1]; // max possible length of header, plus 1
    int hdr_len = 0;
    if (name_len < 0x80) 
    {
        hdr[hdr_len++] = name_len;
    }
    else 
    {
        hdr[hdr_len++] = (name_len >> 24) | 0x80;
        hdr[hdr_len++] = name_len >> 16;
        hdr[hdr_len++] = name_len >> 8;
        hdr[hdr_len++] = name_len;
    }
    if (val_len < 0x80) 
    {
        hdr[hdr_len++] = val_len;
    }
    else 
    {
        hdr[hdr_len++] = (val_len >> 24) | 0x80;
        hdr[hdr_len++] = val_len >> 16;
        hdr[hdr_len++] = val_len >> 8;
        hdr[hdr_len++] = val_len;
    }

    // Then send header, followed by name and value    
    memcpy (fc_l->env + fc_l->env_p, (char*)hdr, hdr_len);
    fc_l->env_p += hdr_len;
    memcpy (fc_l->env + fc_l->env_p, (char*)name, name_len);
    fc_l->env_p += name_len;
    memcpy (fc_l->env + fc_l->env_p, (char*)val, val_len);
    fc_l->env_p += val_len;
    return fc_l->env_p;
}

//
// Set timeout for socket according to time remaining. If cannot do it (socket disconnected for
// instance), return false, otherwise true;
//
bool gg_set_timeout(fc_local *fc_l)
{
    if (fc_l->fc->timeout == 0) return true;
    if (setsockopt (fc_l->sock, SOL_SOCKET, SO_RCVTIMEO, &(fc_l->remaining), sizeof(fc_l->remaining)) ||
        setsockopt (fc_l->sock, SOL_SOCKET, SO_SNDTIMEO, &(fc_l->remaining), sizeof(fc_l->remaining)))
        return false;
    else return true;
}

//
// Check if time passed since the beginning of the request is up. If it is, return false;
// Otherwise return true. If true, then set recv/send timeout to whatever time is remaining.
// This should be called on startup, before connect and right after any blocking operation (including connect).
// If this is on startup, startup is true.
// We do not check for EAGAIN, EWOULDBLOCK or EINPROGRESS, because if 1) there's an error and time is up or
// 2) there's no error, but not connected, data sent or received, and time is up -- what is the difference?
// We have technically timed out, even if at that very moment some other error happened.
//
bool gg_check_timeout(fc_local *fc_l, bool startup)
{
    if (fc_l->fc->timeout == 0) return true;

    if (startup)
    {
        fc_l->remaining = fc_l->tout; // in the beginning, we have the time
        return true;
    }

    // This is other than startup
    struct timeval curr;
    struct timeval elapsed;
    // Get current time
    gettimeofday (&(curr), NULL);
    // See how much time elapsed
    timersub (&curr, &(fc_l->begin), &elapsed);
    // Is elapsed lesser than timeout
    if (timercmp (&elapsed, &(fc_l->tout) ,<)) 
    {
        // find out remaining time
        timersub(&(fc_l->tout), &elapsed, &(fc_l->remaining));
        // setting socket to block at most the remaining time will be done in gg_set_timeout()
        return true;
    }
    else
    {
        // timeout, it's over
        fc_l->interrupted = true;
        return false;
    }
}

//
// Connect socket to conn_string, which can be IP:port of unix socket.
// If there is : in conn_string, it's IP:port. Unix socket path must not have :
// Return GG_CLI_ERR_RESOLVE_ADDR if IP cannot be resolved, GG_CLI_ERR_PATH_TOO_LONG if sock path too long, GG_CLI_ERR_CONNECT if cannot
// connect or GG_OKAY if okay. Global fc_l->sock is set if socket created; but connect may have failed.
//
int gg_connect_socket(fc_local *fc_l, char *conn_string)
{
    // we support both IP4 and IP6 addresses
    struct sockaddr_in tcp_sock;
    struct sockaddr_in6 tcp_sock6;
    // this is for Unix sockets
    struct sockaddr_un unix_sock;

    int st;
    char *col;
    char *port = NULL;
    char *hn;
    fc_l->sock = -1; // default when not created
    if ((col = strchr(conn_string, ':')) != NULL) 
    {
        hn = strdup (conn_string);
        if (hn == NULL) { return GG_CLI_ERR_OUT_MEM; }
        hn[col-conn_string] = 0;
        port = col+1;
    }
    // Connecting is given all of timeout. It should use just a fraction, but if it doesn't, we
    // will measure what was used and set the remainder for the following operation. 
    if (port != NULL) 
    {
        struct addrinfo* res = NULL;
        struct addrinfo* cres = NULL;
        // Get all possible addresses for hn:port
        if (getaddrinfo(hn, port, 0, &res)) { return GG_CLI_ERR_RESOLVE_ADDR; }
        free (hn); // free host name
        int len;
        // Init IP4 and IP6 ports to nothing
        tcp_sock.sin_port = 0;
        tcp_sock6.sin6_port = 0;
        // Find the first address that is INET, be it IPv4 or IPv6. 
        for(cres=res; cres!=NULL; cres=cres->ai_next)
        {
            if (cres->ai_addr->sa_family == AF_INET) 
            {
                memcpy (&tcp_sock, (struct sockaddr_in *)cres->ai_addr, sizeof(struct sockaddr_in));
                len = sizeof(tcp_sock6);
                break;
            }
            else if (cres->ai_addr->sa_family == AF_INET6) 
            {
                memcpy (&tcp_sock6, (struct sockaddr_in6 *)cres->ai_addr, sizeof(struct sockaddr_in6));
                len = sizeof(tcp_sock6);
                break;
            }
        }
        freeaddrinfo (res); // free OS allocated memory for host name resolution
        // Check we have one good address
        if (tcp_sock.sin_port == 0 && tcp_sock6.sin6_port == 0) return GG_CLI_ERR_RESOLVE_ADDR;
        // socket is non-blocking
        fc_l->sock = socket(AF_INET, SOCK_STREAM, 0);
        if (fc_l->sock == -1) return GG_CLI_ERR_SOCKET;
        if (!gg_check_timeout(fc_l, false)) return GG_CLI_ERR_TIMEOUT;
        if (!gg_set_timeout(fc_l)) return GG_CLI_ERR_SOCKET; // this shouldn't happen, since we're just starting;
        st = connect(fc_l->sock, tcp_sock.sin_port != 0? (struct sockaddr*)&tcp_sock:(struct sockaddr*)&tcp_sock6, len);
    } 
    else {
        int conn_str_len = strlen(conn_string);
        if(conn_str_len > (int)sizeof(unix_sock.sun_path)) return GG_CLI_ERR_PATH_TOO_LONG;
        memset((char *) &(unix_sock), 0, sizeof(unix_sock));
        unix_sock.sun_family = AF_UNIX;
        memcpy(unix_sock.sun_path, conn_string, conn_str_len);
        int len = sizeof(unix_sock);
        fc_l->sock = socket(AF_UNIX, SOCK_STREAM, 0);
        if (fc_l->sock == -1) return GG_CLI_ERR_SOCKET;
        if (!gg_check_timeout(fc_l, false)) return GG_CLI_ERR_TIMEOUT; 
        if (!gg_set_timeout(fc_l)) return GG_CLI_ERR_SOCKET; // this shouldn't happen, since we're just starting;
        st = connect(fc_l->sock, (struct sockaddr*)&unix_sock, len);
    }

    if(st >= 0) 
    {
        return GG_OKAY;
    }
    else 
    {
        if (!gg_check_timeout(fc_l, false)) return GG_CLI_ERR_TIMEOUT; 
        shut_sock(fc_l);
        return GG_CLI_ERR_CONNECT;
    }
}

//
// Startup/Shutdown. If start is true, this is startup, otherwise shutdown.
// timeout is the timeout for a request
//
int fc_edge(fc_local *fc_l, int timeout, bool start)
{

    if (!fc_l->initerrm) 
    {
        fc_l->initerrm = true;
        // Error messages
        fc_l->errm[-GG_CLI_MAX_ERRS+GG_CLI_ERR_SOCK_READ] = "Error reading from server";
        fc_l->errm[-GG_CLI_MAX_ERRS+GG_CLI_ERR_PROT_ERR] = "Protocol error";
        fc_l->errm[-GG_CLI_MAX_ERRS+GG_CLI_ERR_BAD_VER] = "Bad FastCGI version";
        fc_l->errm[-GG_CLI_MAX_ERRS+GG_CLI_ERR_SRV] = "Server cannot complete request";
        fc_l->errm[-GG_CLI_MAX_ERRS+GG_CLI_ERR_UNK] = "Unknown record type";
        fc_l->errm[-GG_CLI_MAX_ERRS+GG_CLI_ERR_OUT_MEM] = "Out of memory";
        fc_l->errm[-GG_CLI_MAX_ERRS+GG_CLI_ERR_RESOLVE_ADDR] = "Cannot resolve host address";
        fc_l->errm[-GG_CLI_MAX_ERRS+GG_CLI_ERR_PATH_TOO_LONG] = "Unix socket path too long";
        fc_l->errm[-GG_CLI_MAX_ERRS+GG_CLI_ERR_CONNECT] = "Cannot connect to server";
        fc_l->errm[-GG_CLI_MAX_ERRS+GG_CLI_ERR_TIMEOUT] = "Request timed out";
        fc_l->errm[-GG_CLI_MAX_ERRS+GG_CLI_ERR_SOCK_WRITE] = "Error writing to server";
        fc_l->errm[-GG_CLI_MAX_ERRS+GG_CLI_ERR_INTERNAL] = "Internal error";
        fc_l->errm[-GG_CLI_MAX_ERRS+GG_CLI_ERR_ENV_TOO_LONG] = "Environment variables too long";
        fc_l->errm[-GG_CLI_MAX_ERRS+GG_CLI_ERR_BAD_TIMEOUT] = "Invalid timeout value";
        fc_l->errm[-GG_CLI_MAX_ERRS+GG_CLI_ERR_ENV_ODD] = "Name or value missing in environment variables";
    }

    if (start) 
    {
        // This is initialization
        //
        // In order to restore a signal handler properly, do NOT place any returns in between here and
        // the line stating ENDINIT
        //
        // initialize reading stats, in case we return prior to reading so they have default values
        fc_l->fc->internal.read_status = GG_OKAY; // by default, read succeeds, we set error if it doesn't
        fc_l->fc->internal.data = calloc(1,GG_ADD_FOR_GOLF+1); // add GG_ADD_FOR_GOLF so it works with Golf
        if (fc_l->fc->internal.data == NULL) { return GG_CLI_ERR_OUT_MEM; }
        fc_l->fc->data_len = 0;
        fc_l->fc->internal.error = calloc(1,GG_ADD_FOR_GOLF+1); //add GG_ADD_FOR_GOLF so it works with Golf
        if (fc_l->fc->internal.error == NULL) { return GG_CLI_ERR_OUT_MEM; }
        fc_l->fc->error_len = 0;
        fc_l->fc->other = calloc(1,1); // other is not used for now like data or error
        if (fc_l->fc->other == NULL) { return GG_CLI_ERR_OUT_MEM; }
        fc_l->fc->other_len = 0;
        fc_l->read_done = false;
        fc_l->env = NULL;
        fc_l->env_nlen = NULL;
        fc_l->env_vlen = NULL;
        fc_l->sock = -1;  // socket for sending to server
        fc_l->req_id = 1;  // id of request
        fc_l->done_header = false;  // was header read (true) or not (false)
        // set timeout to "no timeout" by default
        fc_l->tout.tv_sec = 0;
        fc_l->tout.tv_usec = 0;
        fc_l->interrupted = false;
        fc_l->fc->done = false;
        fc_l->fc->return_code = GG_OKAY; // set on return by GG_CLI_RET
        if (timeout != 0)
        {
            // check if timeout value in range, don't do anythig if not
            if (timeout < 1 || timeout > 86400) return GG_CLI_ERR_BAD_TIMEOUT;
            fc_l->tout.tv_sec = timeout;
            fc_l->tout.tv_usec = 0;
            gg_check_timeout(fc_l, true); // arm timer
        }
    }
    else
    {
        shut_sock(fc_l); // close socket if not already closed
        if (fc_l->env != NULL) free(fc_l->env);
        if (fc_l->env_nlen != NULL) free(fc_l->env_nlen);
        if (fc_l->env_vlen != NULL) free(fc_l->env_vlen);
        if (fc_l->fc->other != NULL) free (fc_l->fc->other);
        if (fc_l->just_query_path != NULL) { free (fc_l->query_string); free (fc_l->path_info); };
        fc_l->fc->done = true;
    }
    return GG_OKAY;
}


//
// The purpose of this function is to split the URL payload (url_params) into path segment
// and query string. While the two are combined in Golf's url payload, in http they are split
// with path segment being part of PATH_INFO (with request name prepended), and query string being just QUERY_STRING
// On input, *query_string_l is 0 and *path_info_l is the length of ->path_info as input (i.e. ->req). This way
// if query_string is empty, those values are ready to send to the server.
// On input also, ->query_string is referencing ->url_params from the end_user and ->path_info referencing 
// end-user's ->req at input; 
// On output, ->query_string is QUERY_STRING ->path_info is PATH_INFO, ready for the server
// On output also, query_string_l is length of QUERY_STRING and path_info_l is length of PATH_INFO.
// This will search input query string for / backwards from the end, and then for ? forwards. If no /
// is found, then nothing is done and query string remains what it is and so does path info (which is just /req).
// If / is found, but no ?, then the whole of query string is appended to path info. If / is found, and then ? is
// found, then all up to ? is copied to path info, and after ? to query string. 
// This way, both path info and query string are always up to http standard.
// Returns GG_OKAY if okay, otherwise error
//
int parse_payload (fc_local *fc_l, int *query_string_l, int *path_info_l)
{
    // if url_params is empty, do nothing, in which case ->query_string remains pointing to
    // ->url_params from end-user, and ->path_info remains pointing to ->req from end-user
    //
    if (fc_l->query_string != NULL && fc_l->query_string[0] !=0) 
    {
        // ->query_string is referencing end-user's ->url_params which we cannot change
        *query_string_l = strlen(fc_l->query_string);
        // Search from the end backwards until / found
        int l_payload = *query_string_l;
        while (l_payload > 0) 
        {
            // it's either x=y&z=w ....
            // or /x/y/z/w
            // or /x/y?z=w ...
            if (fc_l->query_string[l_payload-1] == '/') 
            {
                // Now search for ? after last / forward, that is the start of query string
                // or until end of string
                int p_question = l_payload;
                while (fc_l->query_string[p_question] && fc_l->query_string[p_question] != '?') p_question++;
                // if reached the end, then the whole string is path
                // otherwise it is where & is
                l_payload = p_question;
                break;
            }
            l_payload--; // go backwards until / found or beginning of string
        }
        if (l_payload > 0)  // means / found, if not, leave path_info as it is (i.e. req) and query_string as it is (i.e. query string)
        {
            // in this case there is a / and l_payload is either ? after that or null-char if none found
            // use old payload to set query path
            fc_l->just_query_path = fc_l->fc->url_params; // now it's non-NULL, meaning query_string and path_info allocated
            // create new payload which is pure query string, if no ?query_string, then just empty
            if (fc_l->query_string[l_payload])
            {
                fc_l->query_string = strdup (fc_l->query_string + l_payload + 1);
                *query_string_l -= l_payload + 1;
            }
            else 
            {
                fc_l->query_string = strdup("");
                *query_string_l = 0;
            }
            if (fc_l->query_string == NULL) { shut_sock(fc_l); return GG_CLI_ERR_OUT_MEM; }
            // since query_string starts at l_payload+1 (see above) in old query_string (after path segments), deduct that from its length
            // since l_payload is where ? is or end of string, so l_payload is the length of path segments either way
            // thus the end of fc_l->just_query_path is in l_payload, this is the length of path segments, since ->just_query_path is 
            // original end-user's url_params, so we will use that to copy the path segments to new path_name.
            // At this point we have pointers and lengths of query path and query string
            // Query path needs to be appended to request name to become path info
            int old_path_info_l = *path_info_l;
            *path_info_l += l_payload; // add path segments length to request name (which is current *path_info_l)
            char *preq = malloc(*path_info_l + 1); // allocate for new path_name
            if (preq == NULL) { shut_sock(fc_l); return GG_CLI_ERR_OUT_MEM; }
            memcpy (preq, fc_l->path_info , old_path_info_l); // copy /req_name first
            memcpy (preq + old_path_info_l, fc_l->just_query_path, l_payload); // then append path segments
            preq[*path_info_l] = 0; // finish with null char
            fc_l->path_info = preq; // now this is request name / query path, i.e. full query path with *path_info_l its length
        }
    }
    return GG_OKAY;
}

// Returning from fc_request must close socket, and account for any timeout.
// This will close socket, and return either the exit code or
// the value for being interrupted. Also set error message;
// fc_l is fcgi thread context
#define GG_CLI_RET(rc) {fc_edge (fc_l, fc_l->fc->timeout, false); int _rc = (fc_l->interrupted?GG_CLI_ERR_TIMEOUT:(rc)); fc_l->fc->return_code = _rc; if (_rc != GG_OKAY) {fc_l->fc->errm = fc_l->errm[-GG_CLI_MAX_ERRS+_rc];} else {fc_l->fc->errm = ""; if (fc_l->fc->done_hook != NULL) (*(fc_l->fc->done_hook))(GG_MADJ(fc_l->fc->internal.data), fc_l->fc->data_len, GG_MADJ(fc_l->fc->internal.error), fc_l->fc->error_len, fc_l->fc); } return _rc;}

//
// Make a request to SERVICE server from a client. 
// fc_l->fc->server is 1. the file path to local server or 2. IP:port of the SERVICE server
// fc_l->fc->req_method is request method (GET, POST..), fc_l->fc->app_path is PATH_INFO (application path),
// fc_l->fc->req is SCRIPT_NAME (/request_name), fc_l->fc->content_len is the length of request body,
// fc_l->fc->url_params is the query path+query string or just query path, and it can be NULL or empty,
// fc_l->fc->content_type is CONTENT_TYPE (application/json...), fc_l->fc->req_body is the request body,
// fc_l->fc->env is the environment to pass (name,value,name,value..) that ends with NULL, 
// fc_l->fc->timeout is timeout in seconds (1..86400)
// Returns  connect errors (GG_CLI_ERR_RESOLVE_ADDR if IP cannot be resolved, GG_CLI_ERR_PATH_TOO_LONG if sock path too long, 
// GG_CLI_ERR_CONNECT if cannot connect), 
// GG_CLI_ERR_SOCK_WRITE error in writing to server, GG_CLI_ERR_ENV_TOO_LONG if fc_l->fc->env too long.
// GG_CLI_ERR_BAD_TIMEOUT invalid timeout.
// GG_CLI_ERR_SOCK_READ if cannot read from server,
// GG_CLI_ERR_PROT_ERR if protocol error,
// GG_CLI_ERR_BAD_VER if fcgi versions don't match
// GG_CLI_ERR_UNK if server doesn't recognize client record types,
// GG_CLI_ERR_OUT_MEM if out of memory,
// GG_CLI_ERR_ENV_TOO_LONG if environment too long,
// GG_CLI_ERR_BAD_TIMEOUT if timeout value lesser than 0 or greater than 86400
// GG_CLI_ERR_SRV if server cannot complete request
// If timed out, returns GG_CLI_ERR_TIMEOUT
// fc_l->fc->content_type can be NULL, and fc_l->fc->content_len 0. If either is like that, then fc_l->fc->req_body should be NULL, as it won't be accounted for.
// fc_l->fc->env can be NULL. If like that, that no environment is used.
// 
//
int gg_cli_request (gg_cli *fc_in)
{
    // Thread-local internal data
    fc_local _fc_l = {0};
    fc_local *fc_l = &_fc_l;

    gettimeofday(&(fc_l->begin), NULL); // record begin of request so timeout can work properly

    // Input/output request data, must be separate for each thread of course
    fc_l->fc = fc_in;

    // Initialize, this must happen before anything else
    int init = fc_edge(fc_l, fc_l->fc->timeout, true);
    if (init != GG_OKAY) return init;

    struct struct_rec {
        fc_header start_rec_header;
        fc_begin_req_body start_rec_body;
    } srec; // put in structure so we can do a single write instead of 2

    int cres =  gg_connect_socket(fc_l, fc_l->fc->server); // connect to Unix or TCP socket
    if (cres != GG_OKAY)  GG_CLI_RET(cres);

    // Begin request
    srec.start_rec_header = build_hdr(fc_l, sizeof(srec.start_rec_body), GG_CLI_BEG_REQ);
    begin_request(&(srec.start_rec_body));
    // Send beginning of request to server
    if (gg_write_socket(fc_l, (char *)&srec, sizeof(srec)) < 0) { shut_sock(fc_l); GG_CLI_RET(GG_CLI_ERR_SOCK_WRITE);}

    // sanity check for content
    // must have all of these to have content
    bool body = true;
    char *ibody = fc_l->fc->req_body; // use local pointer to avoid changing input data
    if (ibody == NULL || fc_l->fc->content_len == 0 || fc_l->fc->content_type == NULL || fc_l->fc->content_type[0] == 0)
    {
        ibody = NULL;
        fc_l->fc->content_len = 0;
        fc_l->fc->content_type = NULL;
        body = false; // there is no req body
    }

    // Send environment variables to the server, this isn't real sending, just queing it up
    char clen[30];
    // do not calculate lengths more than once
    int clen_len = 0;
    int cont_type_l = 0;
    if (body)
    {
        clen_len = snprintf(clen, sizeof(clen), "%d", fc_l->fc->content_len);
        cont_type_l = strlen(fc_l->fc->content_type);
    }
    int req_method_l = strlen(fc_l->fc->req_method);
    int query_string_l = 0;
    fc_l->just_query_path = NULL; // this is query path part of url_params
    fc_l->query_string = fc_l->fc->url_params; // copy, maybe it will stay the same (if no path in it)
                                               // otherwise will be just query string
    fc_l->path_info = fc_l->fc->req; // copy, maybe it will stay the same (if no path in url_params from user)
                               // otherwise will be req/path/string
    int path_info_l = strlen(fc_l->path_info);
    int rp = parse_payload (fc_l, &query_string_l, &path_info_l);
    if (rp != GG_OKAY) GG_CLI_RET(rp); // return error from parsing if any found (such as out of memory)
    //
    // End of splitting url_params into PATH_INFO and QUERY_STRING
    //
    int app_path_l = strlen(fc_l->fc->app_path);
    // calculate length of environment
    fc_l->param_len = 0; // init size before calling gg_size_env's
    gg_size_env(fc_l, strlen("REQUEST_METHOD"), req_method_l);
    gg_size_env(fc_l, strlen("SCRIPT_NAME"), app_path_l);
    gg_size_env(fc_l, strlen("PATH_INFO"), path_info_l);
    if (fc_l->query_string != NULL && fc_l->query_string[0] !=0) gg_size_env(fc_l, strlen("QUERY_STRING"), query_string_l);
    if (body)
    {
        gg_size_env(fc_l, strlen("CONTENT_LENGTH"), clen_len);
        gg_size_env(fc_l, strlen("CONTENT_TYPE"), cont_type_l);
    } 
    int envn = 0;
    if (fc_l->fc->env != NULL)
    {
        while (fc_l->fc->env[envn] != NULL) envn++;
        if (envn % 2 != 0) GG_CLI_RET(GG_CLI_ERR_ENV_ODD);
        fc_l->env_nlen = calloc (envn/2, sizeof(int));
        fc_l->env_vlen = calloc (envn/2, sizeof(int));
        if (fc_l->env_nlen == NULL || fc_l->env_vlen == NULL) { shut_sock(fc_l); GG_CLI_RET (GG_CLI_ERR_OUT_MEM); }
        int i;
        for (i = 0; i < envn; i+=2) gg_size_env(fc_l, fc_l->env_nlen[i/2]=strlen(fc_l->fc->env[i]), fc_l->env_vlen[i/2]=strlen(fc_l->fc->env[i+1]));
    }
    if (fc_l->param_len >= (int)(GG_CLI_MAX_LEN - sizeof(fc_header))) { shut_sock(fc_l); GG_CLI_RET(GG_CLI_ERR_ENV_TOO_LONG);}
    // Parameters for the request
    fc_header header = build_hdr(fc_l, fc_l->param_len, GG_CLI_PARAM);
    if (gg_write_socket(fc_l, (char *)&header, sizeof(header)) < 0) { shut_sock(fc_l); GG_CLI_RET(GG_CLI_ERR_SOCK_WRITE);}
    // send environment, total limit just under 64K (query string limited to 32K in Golf)
    // Build environment string to send
    fc_l->env = malloc (fc_l->param_len);
    if (fc_l->env == NULL) { shut_sock(fc_l); GG_CLI_RET (GG_CLI_ERR_OUT_MEM); }
    fc_l->env_p = 0;
    gg_send_env(fc_l, "REQUEST_METHOD", strlen("REQUEST_METHOD"), fc_l->fc->req_method, req_method_l);
    gg_send_env(fc_l, "SCRIPT_NAME", strlen("SCRIPT_NAME"), fc_l->fc->app_path, app_path_l);
    gg_send_env(fc_l, "PATH_INFO", strlen("PATH_INFO"), fc_l->path_info, path_info_l);
    if (fc_l->query_string != NULL && fc_l->query_string[0] !=0) gg_send_env(fc_l, "QUERY_STRING", strlen("QUERY_STRING"), fc_l->query_string, query_string_l);
    if (body)
    {
        gg_send_env(fc_l, "CONTENT_LENGTH", strlen("CONTENT_LENGTH"), clen, clen_len);
        gg_send_env(fc_l, "CONTENT_TYPE", strlen("CONTENT_TYPE"), fc_l->fc->content_type, cont_type_l);
    }
    if (fc_l->fc->env != NULL)
    {
        int i;
        for (i = 0; i < envn; i+=2) gg_send_env(fc_l, fc_l->fc->env[i], fc_l->env_nlen[i/2], fc_l->fc->env[i+1], fc_l->env_vlen[i/2]);
    }
    if (gg_write_socket(fc_l, (char*)(fc_l->env), fc_l->env_p) < 0) { shut_sock(fc_l); GG_CLI_RET(GG_CLI_ERR_SOCK_WRITE);}
    // End param stream
    header = build_hdr(fc_l, 0, GG_CLI_PARAM);
    if (gg_write_socket(fc_l, (char *)&header, sizeof(header)) < 0) { shut_sock(fc_l); GG_CLI_RET(GG_CLI_ERR_SOCK_WRITE);}


    if (body)
    {
        int req_body_len = fc_l->fc->content_len; // keep track of how many bytes sent
        // limit of 65535 bytes per packet, so it may have to be split, accounting for SERVICE header size
        int curr_packet_len;
        while (req_body_len>0) 
        {
            // first (and possibly last if less than 64K-ish) packet length
            if (req_body_len > GG_CLI_MAX_LEN - (int)sizeof(fc_header)) curr_packet_len = GG_CLI_MAX_LEN - sizeof(fc_header);
            else curr_packet_len = req_body_len;
            // make header for the packet
            header = build_hdr(fc_l, curr_packet_len, GG_CLI_STDIN);
            int res;
            res = gg_write_socket(fc_l, (char*)&header, sizeof(header));
            if (res < 0) GG_CLI_RET(GG_CLI_ERR_SOCK_WRITE);
            res = gg_write_socket(fc_l, ibody, curr_packet_len);
            if (res < 0) GG_CLI_RET(GG_CLI_ERR_SOCK_WRITE);
            req_body_len -= curr_packet_len; // now less to send
            ibody += curr_packet_len;
        }
        // Send an empty record
        header = build_hdr(fc_l, 0, GG_CLI_STDIN);
        int res;
        res = gg_write_socket(fc_l, (char*)&header, sizeof(header));
        if (res < 0) GG_CLI_RET(GG_CLI_ERR_SOCK_WRITE);
    }

    fc_server_read (fc_l);
    
    // We use connect, request, close method. This is much easier on resources than keeping a connection open (especially
    // on the server which may have to juggle many). For Unix sockets, or local 127.0.0.1, there is really no penalty to speak
    // of, and it eliminates programming issues of keeping and multiplexing connections, which probably exerts more of a penalty
    // to begin with. For fast secure local networks (i.e. no encryption needed, as we don't provide any), there is a  
    // penalty with connect/close but then server has to manage many open connections and may actually slow things down because while
    // some clients may have connected to server process A, server process B may be open and idling. A strategy to overcome this may
    // be costly. The end result can go either way, but for now single connection seems to deliver better results. Perhaps
    // in the future we will support connection reuse on both client and server, but that remains to be seen.
    GG_CLI_RET(fc_l->fc->internal.read_status);
}
// Note: valgrind testing with (for tf.c source test):
// valgrind --log-file=$(pwd)/tf.log -s --trace-children=yes --track-origins=yes --verbose  --leak-check=full --show-leak-kinds=all --  ./tf

//
// Free a fast cgi request callin, including any data/errors returned.
//
void gg_cli_delete (gg_cli *callin)
{
#if GG_GOLFSRV==1
    GG_TRACE("");
    if (callin->internal.server_alloc) gg_free (callin->server);
    if (callin->internal.path_alloc) { gg_free (callin->app_path); gg_free (callin->url_params); }
    gg_free (GG_MADJ(callin->internal.data));
    gg_free (GG_MADJ(callin->internal.error));
    gg_free (callin);
#else
    // for Client API, check if NULL
    if (callin->internal.data != GG_EMPTY_STRING) free (GG_MADJ(callin->internal.data));
    if (callin->internal.data != GG_EMPTY_STRING) free (GG_MADJ(callin->internal.error));
    // do not free like for Golf (where it's always allocated pointer), here it may not be, as it's any C above this call
#endif
}

//
// Return data response from server
//
char *gg_cli_data (gg_cli *callin)
{
#if GG_GOLFSRV==1
    GG_TRACE("");
    // convert memory to Golf if okay, so it goes under garbage collection
    gg_num mm = gg_add_mem (callin->internal.data);
    gg_vmset(callin->internal.data,mm);
    gg_num id = gg_mem_get_id (GG_MADJ(callin->internal.data));
    gg_mem_set_len (id, callin->data_len+1); 
#endif
    return GG_MADJ(callin->internal.data);
}

//
// Return error response from server
//
char *gg_cli_error (gg_cli *callin)
{
#if GG_GOLFSRV==1
    GG_TRACE("");
    // convert memory to Golf if okay, so it goes under garbage collection
    gg_num mm = gg_add_mem (callin->internal.error);
    gg_vmset(callin->internal.error,mm);
    gg_num id = gg_mem_get_id (GG_MADJ(callin->internal.error));
    gg_mem_set_len (id, callin->error_len+1); 
#endif
    return GG_MADJ(callin->internal.error);
}


#if GG_GOLFSRV==1
//
// Create SERVICE object for call-fcgi. call is the object, server is the server (or Unix socket location)
// req_method is GET, POST (default GET) etc. app_path is application path, req is request path, url_params is URL payload (i.e. the resot of URL)
// ctype is content type, body is body content, clen is content length, timeout is timeout for fcgi call, env is a list of environment
// vars=vals to be passed to fcgi server.
// server is mandatory. If body is specified, then clen and ctype are considered, otherwise not.
// if simple_server is true, server is just a name of location application.
// if simple_url is true, then url_params is /app_path/req/url_params and is mandatory, otherwise all app_path,req,url_params are mandatory
//
void gg_set_fcgi (gg_cli **callin, char *server, char *req_method, char *app_path, char *req, char *url_params, char *ctype, char *body, int clen, int timeout, char **env, bool simple_server, bool simple_url)
{
    GG_TRACE("");
    *callin = gg_calloc (1, sizeof(gg_cli)); // always allocated
    gg_cli *call = *callin;
    if (env != NULL) call->env = env;

    if (simple_server)
    {
        char *sockloc = gg_malloc (GG_MAX_SOCK_LEN+1);
        gg_num bw = snprintf (sockloc, GG_MAX_SOCK_LEN, GG_ROOT "/var/lib/gg/%s/sock/sock", server);
        gg_mem_set_len (gg_mem_get_id(sockloc), bw+1);
        call->server = sockloc;
        call->internal.server_alloc = true;
    }
    else 
    {
        call->server = server;  // IP:port (TCP) or Unix socket path string
        call->internal.server_alloc = false;
    }

    call->req_method = req_method;

    if (!simple_url)
    {
        call->app_path = app_path;
        call->req = req;
        if (url_params != NULL) call->url_params = url_params;
    }
    else
    {
        call->req = req; // just empty string here
        // url_params is the entire URL
        gg_num ulen = gg_mem_get_len (gg_mem_get_id(url_params));
        char *qs = memchr (url_params, '?', ulen); // look for query string
        if (qs == NULL)
        {
            call->app_path = url_params;
            call->url_params = GG_EMPTY_STRING;
            call->internal.path_alloc = false;
        }
        else
        {
            *qs = 0;
            call->app_path = gg_strdupl (url_params, 0, qs-url_params);
            call->url_params = gg_strdupl (qs + 1, 0, ulen - (qs-url_params+1));
            *qs = '?';
            call->internal.path_alloc = true;
        }
    }

    if (body != NULL) call->content_type = ctype;
    if (body != NULL) call->req_body = body;
    if (body != NULL) 
    {
        gg_num id = gg_mem_get_id(body);
        if (clen == 0) clen = (int)gg_mem_get_len(id);
        else if (clen > gg_mem_get_len(id)) 
        {
            gg_report_error ("Memory used for request body is of length [%d] but only [%ld] allocated", clen, gg_mem_get_len(id));
            exit (1);
        }
        call->content_len = clen;
    }
    if (timeout >0) call->timeout = timeout;
}


//
// Execute single fcgi call. inp is request (gg_cli*), returns its status as void*
// This is for multithreaded execution
//
void *gg_cli_one (void *inp)
{
    GG_TRACE("");
    gg_cli *req = (gg_cli*) inp;

    int res = gg_cli_request (req);
     
    return (void*)(off_t)res;
}



//
// Call-fcgi implementation. req is an array of fcgi requests, threads is how many
// in this array. Returns GG_OKAY if all threads ran and finished okay, or GG_ERR_TOO_MANY 
// if threads is less than 0 or more than a million, GG_ERR_FAILED if not all started or
// not all finished okay (see further, GG_OKAY return). finokay is the number of them that finished
// with GG_OKAY (just finished, nothing about any app errors). started is the number of them
// that actually started. 
//
gg_num gg_call_fcgi (gg_cli **req, gg_num threads, gg_num *finokay, gg_num *started)
{
    GG_TRACE("");
    if (threads == 1) 
    {
        // special case: just one thread, so no need for thread library
        if (started != NULL) *started = 1; 
        int res = gg_cli_request (req[0]);
        if (finokay != NULL)
        {
            if (res == GG_OKAY) *finokay = 1; else *finokay = 0;
        }
        return (res == GG_OKAY ? GG_OKAY:GG_ERR_FAILED);
    }

    if (threads < 0 || threads > 1000000) { return GG_ERR_TOO_MANY; }
    pthread_t *thread_id = calloc (threads, sizeof(pthread_t)); // threads to execute
    if (thread_id == NULL) return GG_ERR_MEMORY;
    // Run threads
    gg_num i = 0;
    gg_num totrun = 0;
    for (i = 0; i < threads; i++){ // execute in parallel
        // If user tries to set hooks manually, disable them; this would be disastrous
        // if user code runs from many threads - Golf is made for single-threaded execution
        // aside from new-fcgi. That would not work; and even if Golf was MT, user could 
        // do exit-service or report-error, which would make execution go passed new-fcgi
        // and there would be multiple threads accepting on the same socket; bad all around.
        req[i]->out_hook = NULL;
        req[i]->err_hook = NULL; 
        req[i]->done_hook = NULL;
        // If thread creation fails, we will notify user how many succeeded
        // Since those that succeeded are running, we cannot back out, it must finish to join
        if (pthread_create(&(thread_id[i]), NULL, gg_cli_one, req[i]))
        {
            req[i]->internal.invalid_thread = 1; // mark threads that didn't start
            GG_TRACE("Thread creation failed with [%d] [%s]", errno, strerror(errno));
        } else totrun++;
    }
    if (started != NULL) *started = totrun; 
    void *thread_res;
    gg_num totok =0;
    // wait for all to complete, count those successful; wait only for however many started
    for (i = 0; i < threads; i++){
        if (req[i]->internal.invalid_thread != 1) //if couldn't start a thread, do not try to join
        {
            if (pthread_join(thread_id[i], &thread_res) != 0)
            {
                // This should not happen: no deadlock (fcgi lib does no join), it's not the self-join;
                // created threads are joinable. Likely thread crashed and is dead anyway.
                GG_TRACE("Thread join failed with [%d] [%s]", errno, strerror(errno));
                // allocate empty freeable memory for unable to join
                req[i]->internal.data = GG_EMPTY_STRING;
                req[i]->internal.error = GG_EMPTY_STRING;
            }
            else
            {
                int r = (int)(off_t)(thread_res);
                if (r == GG_OKAY) totok++; // these are the ones that surely finished
            }
        } 
        else
        {
            // allocate empty freeable memory for unable to start
            req[i]->internal.data = GG_EMPTY_STRING;
            req[i]->internal.error = GG_EMPTY_STRING;
        }
    }
    free (thread_id); // free threads
    if (finokay != NULL) *finokay = totok;
    return (totrun == threads && totok == threads) ? GG_OKAY:GG_ERR_FAILED;
}
#endif