fastfetch-sys 2.43.0

A neofetch like system information tool
Documentation
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
#include "fastfetch.h"
#include "common/networking/networking.h"
#include "common/time.h"
#include "common/library.h"
#include "util/stringUtils.h"
#include "util/mallocHelper.h"
#include "util/debug.h"

#include <unistd.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h> // For FreeBSD
#include <netinet/tcp.h>
#include <errno.h>
#include <fcntl.h>

static const char* tryNonThreadingFastPath(FFNetworkingState* state)
{
    #if defined(TCP_FASTOPEN) || __APPLE__

        if (!state->tfo)
        {
            #ifdef __linux__
            // Linux doesn't support sendto() on unconnected sockets
            FF_DEBUG("TCP Fast Open disabled, skipping");
            return "TCP Fast Open disabled";
            #endif
        }
        else
        {
            FF_DEBUG("Attempting to use TCP Fast Open to connect");

            #ifndef __APPLE__ // On macOS, TCP_FASTOPEN doesn't seem to be needed
            // Set TCP Fast Open
            #ifdef __linux__
            int flag = 5; // the queue length of pending packets
            #else
            int flag = 1; // enable TCP Fast Open
            #endif
            if (setsockopt(state->sockfd, IPPROTO_TCP,
                #ifdef __APPLE__
                // https://github.com/rust-lang/libc/pull/3135
                0x218 // TCP_FASTOPEN_FORCE_ENABLE
                #else
                TCP_FASTOPEN
                #endif
                , &flag, sizeof(flag)) != 0) {
                FF_DEBUG("Failed to set TCP_FASTOPEN option: %s", strerror(errno));
                return "setsockopt(TCP_FASTOPEN) failed";
            } else {
                #ifdef __linux__
                FF_DEBUG("Successfully set TCP_FASTOPEN option, queue length: %d", flag);
                #elif defined(__APPLE__)
                FF_DEBUG("Successfully set TCP_FASTOPEN_FORCE_ENABLE option");
                #else
                FF_DEBUG("Successfully set TCP_FASTOPEN option");
                #endif
            }
            #endif
        }

        #ifndef __APPLE__
        FF_DEBUG("Using sendto() + MSG_DONTWAIT to send %u bytes of data", state->command.length);
        ssize_t sent = sendto(state->sockfd,
                                state->command.chars,
                                state->command.length,
            #ifdef MSG_FASTOPEN
                                MSG_FASTOPEN |
            #endif
            #ifdef MSG_NOSIGNAL
                                MSG_NOSIGNAL |
            #endif
                                MSG_DONTWAIT,
                                state->addr->ai_addr,
                                state->addr->ai_addrlen);
        #else
        if (fcntl(state->sockfd, F_SETFL, O_NONBLOCK) == -1) {
            FF_DEBUG("fcntl(F_SETFL) failed: %s", strerror(errno));
            return "fcntl(F_SETFL) failed";
        }
        FF_DEBUG("Using connectx() to send %u bytes of data", state->command.length);
        // Use connectx to establish connection and send data in one call
        size_t sent;
        if (connectx(state->sockfd,
            &(sa_endpoints_t) {
                .sae_dstaddr = state->addr->ai_addr,
                .sae_dstaddrlen = state->addr->ai_addrlen,
            },
            SAE_ASSOCID_ANY, state->tfo ? CONNECT_DATA_IDEMPOTENT : 0,
            &(struct iovec) {
                .iov_base = state->command.chars,
                .iov_len = state->command.length,
            }, 1, &sent, NULL) != 0) sent = 0;
        if (fcntl(state->sockfd, F_SETFL, 0) == -1) {
            FF_DEBUG("fcntl(F_SETFL) failed: %s", strerror(errno));
            return "fcntl(F_SETFL) failed";
        }
        #endif
        if (sent > 0 || (errno == EAGAIN || errno == EWOULDBLOCK
            #ifdef __APPLE__
            // On macOS EINPROGRESS means the connection cannot be completed immediately
            // On Linux, it means the TFO cookie is not available locally
            || errno == EINPROGRESS
            #endif
        ))
        {
            FF_DEBUG(
                #ifdef __APPLE__
                "connectx()"
                #else
                "sendto()"
                #endif
                " %s (sent=%zd, errno=%d: %s)", errno == 0 ? "succeeded" : "was in progress",
                sent, errno, strerror(errno));
            freeaddrinfo(state->addr);
            state->addr = NULL;
            ffStrbufDestroy(&state->command);
            return NULL;
        }

        FF_DEBUG(
            #ifdef __APPLE__
            "connectx()"
            #else
            "sendto()"
            #endif
            " failed: %s (errno=%d)", strerror(errno), errno);
        #ifdef __APPLE__
        return "connectx() failed";
        #else
        return "sendto() failed";
        #endif
    #else
        FF_UNUSED(state);
        return "TFO support is not available";
    #endif
}

// Traditional connect and send function
static const char* connectAndSend(FFNetworkingState* state)
{
    const char* ret = NULL;
    FF_DEBUG("Using traditional connection method to connect");

    FF_DEBUG("Attempting connect() to server...");
    if(connect(state->sockfd, state->addr->ai_addr, state->addr->ai_addrlen) == -1)
    {
        FF_DEBUG("connect() failed: %s (errno=%d)", strerror(errno), errno);
        ret = "connect() failed";
        goto error;
    }
    FF_DEBUG("connect() succeeded");

    FF_DEBUG("Attempting to send %u bytes of data...", state->command.length);
    if(send(state->sockfd, state->command.chars, state->command.length, 0) < 0)
    {
        FF_DEBUG("send() failed: %s (errno=%d)", strerror(errno), errno);
        ret = "send() failed";
        goto error;
    }
    FF_DEBUG("Data sent successfully");

    goto exit;

error:
    FF_DEBUG("Error occurred, closing socket");
    close(state->sockfd);
    state->sockfd = -1;

exit:
    FF_DEBUG("Releasing address info and other resources");
    freeaddrinfo(state->addr);
    state->addr = NULL;
    ffStrbufDestroy(&state->command);

    return ret;
}

FF_THREAD_ENTRY_DECL_WRAPPER(connectAndSend, FFNetworkingState*);

// Parallel DNS resolution and socket creation
static const char* initNetworkingState(FFNetworkingState* state, const char* host, const char* path, const char* headers)
{
    FF_DEBUG("Initializing network connection state: host=%s, path=%s", host, path);

    // Initialize command and host information
    ffStrbufInitA(&state->command, 64);
    ffStrbufAppendS(&state->command, "GET ");
    ffStrbufAppendS(&state->command, path);
    ffStrbufAppendS(&state->command, " HTTP/1.0\nHost: ");
    ffStrbufAppendS(&state->command, host);
    ffStrbufAppendS(&state->command, "\r\n");

    // Add extra optimized HTTP headers
    ffStrbufAppendS(&state->command, "Connection: close\r\n"); // Explicitly tell the server we don't need to keep the connection

    // If compression needs to be enabled
    if (state->compression) {
        FF_DEBUG("Enabling HTTP content compression");
        ffStrbufAppendS(&state->command, "Accept-Encoding: gzip\r\n");
    }

    ffStrbufAppendS(&state->command, headers);
    ffStrbufAppendS(&state->command, "\r\n");

    #ifdef FF_HAVE_THREADS
    state->thread = 0;
    FF_DEBUG("Thread ID initialized to 0");
    #endif

    const char* ret = NULL;

    struct addrinfo hints = {
        .ai_family = state->ipv6 ? AF_INET6 : AF_INET,
        .ai_socktype = SOCK_STREAM,
        .ai_flags = AI_NUMERICSERV
    };

    FF_DEBUG("Resolving address: %s (%s)", host, state->ipv6 ? "IPv6" : "IPv4");
    // Use AI_NUMERICSERV flag to indicate the service is a numeric port, reducing parsing time

    if(getaddrinfo(host, "80", &hints, &state->addr) != 0)
    {
        FF_DEBUG("getaddrinfo() failed: %s", gai_strerror(errno));
        ret = "getaddrinfo() failed";
        goto error;
    }
    FF_DEBUG("Address resolution successful");

    FF_DEBUG("Creating socket");
    state->sockfd = socket(state->addr->ai_family, state->addr->ai_socktype, state->addr->ai_protocol);
    if(state->sockfd == -1)
    {
        FF_DEBUG("socket() failed: %s (errno=%d)", strerror(errno), errno);
        ret = "socket() failed";
        goto error;
    }
    FF_DEBUG("Socket creation successful: fd=%d", state->sockfd);

    int flag = 1;
    #ifdef TCP_NODELAY
    // Disable Nagle's algorithm to reduce small packet transmission delay
    if (setsockopt(state->sockfd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag)) != 0) {
        FF_DEBUG("Failed to set TCP_NODELAY: %s", strerror(errno));
    } else {
        FF_DEBUG("Successfully disabled Nagle's algorithm");
    }
    #endif

    #ifdef TCP_QUICKACK
    // Set TCP_QUICKACK option to avoid delayed acknowledgments
    if (setsockopt(state->sockfd, IPPROTO_TCP, TCP_QUICKACK, &flag, sizeof(flag)) != 0) {
        FF_DEBUG("Failed to set TCP_QUICKACK: %s", strerror(errno));
    } else {
        FF_DEBUG("Successfully enabled TCP quick acknowledgment");
    }
    #endif

    if (state->timeout > 0)
    {
        FF_DEBUG("Setting connection timeout: %u ms", state->timeout);
        FF_MAYBE_UNUSED uint32_t sec = state->timeout / 1000;
        if (sec == 0) sec = 1;

        #ifdef TCP_CONNECTIONTIMEOUT
        FF_DEBUG("Using TCP_CONNECTIONTIMEOUT: %u seconds", sec);
        setsockopt(state->sockfd, IPPROTO_TCP, TCP_CONNECTIONTIMEOUT, &sec, sizeof(sec));
        #elif defined(TCP_KEEPINIT)
        FF_DEBUG("Using TCP_KEEPINIT: %u seconds", sec);
        setsockopt(state->sockfd, IPPROTO_TCP, TCP_KEEPINIT, &sec, sizeof(sec));
        #elif defined(TCP_USER_TIMEOUT)
        FF_DEBUG("Using TCP_USER_TIMEOUT: %u milliseconds", state->timeout);
        setsockopt(state->sockfd, IPPROTO_TCP, TCP_USER_TIMEOUT, &state->timeout, sizeof(state->timeout));
        #else
        FF_DEBUG("Current platform does not support TCP connection timeout");
        #endif
    }

    return NULL;

error:
    FF_DEBUG("Error occurred during initialization");
    if (state->addr != NULL)
    {
        FF_DEBUG("Releasing address information");
        freeaddrinfo(state->addr);
        state->addr = NULL;
    }

    if (state->sockfd > 0)
    {
        FF_DEBUG("Closing socket: fd=%d", state->sockfd);
        close(state->sockfd);
        state->sockfd = -1;
    }
    return ret;
}

const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, const char* path, const char* headers)
{
    FF_DEBUG("Preparing to send HTTP request: host=%s, path=%s", host, path);

    if (state->compression)
    {
        FF_DEBUG("Compression enabled, checking if zlib is available");

        #ifdef FF_HAVE_ZLIB
        const char* zlibError = ffNetworkingLoadZlibLibrary();
        // Only enable compression if zlib library is successfully loaded
        if (zlibError == NULL)
        {
            FF_DEBUG("Successfully loaded zlib library, compression enabled");
        } else {
            FF_DEBUG("Failed to load zlib library, compression disabled: %s", zlibError);
            state->compression = false;
        }
        #else
        FF_DEBUG("zlib not supported at build time, compression disabled");
        state->compression = false;
        #endif
    }
    else
    {
        FF_DEBUG("Compression disabled");
    }

    const char* initResult = initNetworkingState(state, host, path, headers);
    if (initResult != NULL) {
        FF_DEBUG("Initialization failed: %s", initResult);
        return initResult;
    }
    FF_DEBUG("Network state initialization successful");

    const char* tfoResult = tryNonThreadingFastPath(state);
    if (tfoResult == NULL) {
        FF_DEBUG("TryNonThreadingFastPath() succeeded or in progress");
        return NULL;
    }
    FF_DEBUG("TryNonThreadingFastPath() failed: %s, trying traditional connection", tfoResult);

    #ifdef FF_HAVE_THREADS
    if (instance.config.general.multithreading)
    {
        FF_DEBUG("Multithreading mode enabled, creating connection thread");
        state->thread = ffThreadCreate(connectAndSendThreadMain, state);
        if (state->thread) {
            FF_DEBUG("Thread creation successful: thread=%p", (void*)state->thread);
            return NULL;
        }
        FF_DEBUG("Thread creation failed");
    } else {
        FF_DEBUG("Multithreading mode disabled, connecting in main thread");
    }
    #endif

    return connectAndSend(state);
}

const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buffer)
{
    FF_DEBUG("Preparing to receive HTTP response");
    uint32_t timeout = state->timeout;

    #ifdef FF_HAVE_THREADS
    if (state->thread)
    {
        FF_DEBUG("Connection thread is running, waiting for it to complete (timeout=%u ms)", timeout);
        if (!ffThreadJoin(state->thread, timeout)) {
            FF_DEBUG("Thread join failed or timed out");
            return "ffThreadJoin() failed or timeout";
        }
        FF_DEBUG("Thread completed successfully");
        state->thread = 0;
    }
    #endif

    if(state->sockfd == -1)
    {
        FF_DEBUG("Invalid socket, HTTP request might have failed");
        return "ffNetworkingSendHttpRequest() failed";
    }

    // Set larger initial receive buffer instead of small repeated receives
    int rcvbuf = 65536; // 64KB
    setsockopt(state->sockfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));

    #ifdef __APPLE__
    // poll for the socket to be readable.
    // Because of the non-blocking connectx() call, the connection might not be established yet
    FF_DEBUG("Using poll() to check if socket is readable");
    if (poll(&(struct pollfd) {
        .fd = state->sockfd,
        .events = POLLIN
    }, 1, timeout > 0 ? (int) timeout : -1) == -1)
    {
        FF_DEBUG("poll() failed: %s (errno=%d)", strerror(errno), errno);
        close(state->sockfd);
        state->sockfd = -1;
        return "poll() failed";
    }
    FF_DEBUG("Socket is readable, proceeding to receive data");
    #else
    if(timeout > 0)
    {
        FF_DEBUG("Setting receive timeout: %u ms", timeout);
        struct timeval timev;
        timev.tv_sec = timeout / 1000;
        timev.tv_usec = (__typeof__(timev.tv_usec)) ((timeout % 1000) * 1000); //milliseconds to microseconds
        setsockopt(state->sockfd, SOL_SOCKET, SO_RCVTIMEO, &timev, sizeof(timev));
    }
    #endif

    FF_DEBUG("Starting data reception");
    FF_MAYBE_UNUSED int recvCount = 0;
    uint32_t contentLength = 0;
    char* headerEnd = NULL;

    do {
        FF_DEBUG("Data reception loop #%d, current buffer size: %u, available space: %u",
                 ++recvCount, buffer->length, ffStrbufGetFree(buffer));

        ssize_t received = recv(state->sockfd, buffer->chars + buffer->length, ffStrbufGetFree(buffer), MSG_WAITALL);

        if (received <= 0) {
            if (received == 0) {
                FF_DEBUG("Connection closed (received=0)");
            } else {
                FF_DEBUG("Reception failed: %s (errno=%d)", strerror(errno), errno);
            }
            break;
        }

        buffer->length += (uint32_t) received;
        buffer->chars[buffer->length] = '\0';

        FF_DEBUG("Successfully received %zd bytes of data, total: %u bytes", received, buffer->length);

        // Check if HTTP header end marker is found
        if (headerEnd == NULL) {
            headerEnd = memmem(buffer->chars, buffer->length, "\r\n\r\n", 4);
            if (headerEnd != NULL) {
                FF_DEBUG("Found HTTP header end marker, position: %ld", (long)(headerEnd - buffer->chars));

                // Check for Content-Length header to pre-allocate enough memory
                const char* clHeader = strcasestr(buffer->chars, "Content-Length:");
                if (clHeader) {
                    contentLength = (uint32_t) strtoul(clHeader + 16, NULL, 10);
                    if (contentLength > 0) {
                        FF_DEBUG("Detected Content-Length: %u, pre-allocating buffer", contentLength);
                        // Ensure buffer is large enough, adding header size and some margin
                        ffStrbufEnsureFree(buffer, contentLength + 16);
                        FF_DEBUG("Extended receive buffer to %u bytes", buffer->length);
                    }
                }
            }
        }
    } while (ffStrbufGetFree(buffer) > 0);

    FF_DEBUG("Closing socket: fd=%d", state->sockfd);
    close(state->sockfd);
    state->sockfd = -1;

    if (buffer->length == 0) {
        FF_DEBUG("Server response is empty");
        return "Empty server response received";
    }

    if (headerEnd == NULL) {
        FF_DEBUG("No HTTP header end marker found");
        return "No HTTP header end found";
    }
    if (contentLength > 0 && buffer->length != contentLength + (uint32_t)(headerEnd - buffer->chars) + 4) {
        FF_DEBUG("Received content length mismatches: %u != %u", buffer->length, contentLength + (uint32_t)(headerEnd - buffer->chars) + 4);
        return "Content length mismatch";
    }

    if (ffStrbufStartsWithS(buffer, "HTTP/1.0 200 OK\r\n")) {
        FF_DEBUG("Received valid HTTP 200 response, content %u bytes, total %u bytes", contentLength, buffer->length);
    } else {
        FF_DEBUG("Invalid response: %.40s...", buffer->chars);
        return "Invalid response";
    }

    // If compression was used, try to decompress
    #ifdef FF_HAVE_ZLIB
    if (state->compression) {
        FF_DEBUG("Content received, checking if compressed");
        if (!ffNetworkingDecompressGzip(buffer, headerEnd)) {
            FF_DEBUG("Decompression failed or invalid compression format");
            return "Failed to decompress or invalid format";
        } else {
            FF_DEBUG("Decompression successful or no decompression needed, total length after decompression: %u bytes", buffer->length);
        }
    }
    #endif

    return NULL;
}