Bug Summary

File:bin/tools/weighttp.c
Warning:line 1416, column 9
Value stored to 'uri' is never read

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name weighttp.c -analyzer-store=region -analyzer-opt-analyze-nested-blocks -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -pic-is-pie -mframe-pointer=all -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -fno-plt -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/home/buildbot/lwan-worker/clang-analyze/build/src/bin/tools -resource-dir /usr/lib/clang/14.0.6 -include /home/buildbot/lwan-worker/clang-analyze/build/lwan-build-config.h -D _FILE_OFFSET_BITS=64 -D _TIME_BITS=64 -I /home/buildbot/lwan-worker/clang-analyze/build/src/lib/missing -I /usr/include/python3.10 -I /usr/include/luajit-2.1 -I /usr/include/valgrind -I /home/buildbot/lwan-worker/clang-analyze/build/src/lib -internal-isystem /usr/lib/clang/14.0.6/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/12.1.0/../../../../x86_64-pc-linux-gnu/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -Wno-unused-parameter -Wno-free-nonheap-object -std=gnu99 -fdebug-compilation-dir=/home/buildbot/lwan-worker/clang-analyze/build/src/bin/tools -ferror-limit 19 -stack-protector 2 -fgnuc-version=4.2.1 -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/buildbot/lwan-worker/clang-analyze/CLANG/2022-07-10-191504-1656555-1 -x c /home/buildbot/lwan-worker/clang-analyze/build/src/bin/tools/weighttp.c
1/*
2 * weighttp - a lightweight and simple webserver benchmarking tool
3 *
4 * Copyright (c) 2016, Glue Logic LLC. All rights reserved. code()gluelogic.com
5 *
6 * This rewrite is based on weighttp by Thomas Porzelt
7 * Copyright (c) 2009-2011 Thomas Porzelt
8 * git://git.lighttpd.net/weighttp
9 * https://github.com/lighttpd/weighttp/
10 *
11 * License:
12 * MIT, see COPYING.weighttp file
13 */
14
15#ifndef _GNU_SOURCE1
16#define _GNU_SOURCE1 1
17#endif
18
19#pragma GCC diagnostic ignored "-Wsign-conversion"
20#pragma GCC diagnostic ignored "-Wconversion"
21#pragma GCC diagnostic ignored "-Wvla"
22
23#include <sys/types.h>
24#include <sys/socket.h>/* socket() connect() SOCK_NONBLOCK sockaddr_storage */
25#include <sys/stat.h> /* fstat() */
26#include <sys/time.h> /* gettimeofday() */
27#include <errno(*__errno_location ()).h> /* errno EINTR EAGAIN EWOULDBLOCK EINPROGRESS EALREADY */
28#include <fcntl.h> /* open() fcntl() pipe2() F_SETFL (O_* flags) */
29#include <inttypes.h> /* PRIu64 PRId64 */
30#include <limits.h> /* USHRT_MAX */
31#include <locale.h> /* setlocale() */
32#include <netdb.h> /* getaddrinfo() freeaddrinfo() */
33#include <poll.h> /* poll() POLLIN POLLOUT POLLERR POLLHUP */
34#include <pthread.h> /* pthread_create() pthread_join() */
35#include <stdarg.h> /* va_start() va_end() vfprintf() */
36#include <stdio.h>
37#include <stdlib.h> /* calloc() free() exit() strtoul() strtoull() */
38#include <stdint.h> /* UINT32_MAX */
39#include <signal.h> /* signal() */
40#include <string.h>
41#include <strings.h> /* strcasecmp() strncasecmp() */
42#include <unistd.h> /* read() write() close() getopt() optarg optind optopt*/
43
44#include <arpa/inet.h>
45#include <netinet/tcp.h>
46#include <netinet/in.h>
47#include <sys/un.h>
48
49#ifndef MSG_DONTWAITMSG_DONTWAIT
50#define MSG_DONTWAITMSG_DONTWAIT 0
51#endif
52#ifndef MSG_NOSIGNALMSG_NOSIGNAL
53#define MSG_NOSIGNALMSG_NOSIGNAL 0
54#endif
55#ifndef SOCK_NONBLOCKSOCK_NONBLOCK
56#define SOCK_NONBLOCKSOCK_NONBLOCK 0
57#endif
58
59#ifndef PACKAGE_VERSION""
60#define PACKAGE_VERSION"" ""
61#endif
62
63
64/*(oversimplified; these attributes are supported by some other compilers)*/
65#if defined(__GNUC__4) || defined(__clang__1)
66#ifndef __attribute_cold____attribute__((__cold__))
67#define __attribute_cold____attribute__((__cold__)) __attribute__((__cold__))
68#endif
69#ifndef __attribute_hot____attribute__((__hot__))
70#define __attribute_hot____attribute__((__hot__)) __attribute__((__hot__))
71#endif
72#ifndef __attribute_noinline____attribute__ ((__noinline__))
73#define __attribute_noinline____attribute__ ((__noinline__)) __attribute__((__noinline__))
74#endif
75#ifndef __attribute_noreturn____attribute__((__noreturn__))
76#define __attribute_noreturn____attribute__((__noreturn__)) __attribute__((__noreturn__))
77#endif
78#ifndef __attribute_pure____attribute__ ((__pure__))
79#define __attribute_pure____attribute__ ((__pure__)) __attribute__((__pure__))
80#endif
81#ifndef __attribute_format__
82#define __attribute_format__(x)__attribute__((__format__ x)) __attribute__((__format__ x))
83#endif
84#else
85#ifndef __builtin_expect
86#define __builtin_expect(x, y) (x)
87#endif
88#ifndef __attribute_cold____attribute__((__cold__))
89#define __attribute_cold____attribute__((__cold__))
90#endif
91#ifndef __attribute_hot____attribute__((__hot__))
92#define __attribute_hot____attribute__((__hot__))
93#endif
94#ifndef __attribute_noinline____attribute__ ((__noinline__))
95#define __attribute_noinline____attribute__ ((__noinline__))
96#endif
97#ifndef __attribute_noreturn____attribute__((__noreturn__))
98#define __attribute_noreturn____attribute__((__noreturn__))
99#endif
100#ifndef __attribute_pure____attribute__ ((__pure__))
101#define __attribute_pure____attribute__ ((__pure__))
102#endif
103#ifndef __attribute_format__
104#define __attribute_format__(x)__attribute__((__format__ x))
105#endif
106#endif
107
108
109__attribute_cold____attribute__((__cold__))
110__attribute_noinline____attribute__ ((__noinline__))
111static void
112show_version (void)
113{
114 puts("\nweighttp " PACKAGE_VERSION""
115 " - a lightweight and simple webserver benchmarking tool\n");
116}
117
118
119__attribute_cold____attribute__((__cold__))
120__attribute_noinline____attribute__ ((__noinline__))
121static void
122show_help (void)
123{
124 puts(
125 "weighttp <options> <URI>\n"
126 " -n num number of requests (mandatory)\n"
127 " -t num thread count (default: 1)\n"
128 " -c num concurrent clients (default: 1)\n"
129 " -k keep alive (default: no)\n"
130 " -K num num pipelined requests (default: 1)\n"
131 " -6 use ipv6 (default: no)\n"
132 " -i use HTTP HEAD method (default: GET)\n"
133 " -m method use custom HTTP method (default: GET)\n"
134 " -H str add header to request (\"label: value\"); repeatable\n"
135 " -b size socket buffer sizes (SO_SNDBUF, SO_RCVBUF)\n"
136 " -B addr local address to bind to when making outgoing connections\n"
137 " -C cookie add cookie to request (\"cookie-name=value\"); repeatable\n"
138 " -F use TCP Fast Open (RFC 7413)\n"
139 " -T type Content-Type header to use for POST/PUT data,\n"
140 " e.g. application/x-www-form-urlencoded\n"
141 " (default: text/plain)\n"
142 " -A string add Basic WWW Authorization (str is username:password)\n"
143 " -P string add Basic Proxy-Authorization (str is username:password)\n"
144 " -X proxy proxy:port or unix domain socket path beginning w/ '/'\n"
145 " -p file make HTTP POST request using file contents for body\n"
146 " -u file make HTTP PUT request using file contents for body\n"
147 " -d (ignored; compatibility with Apache Bench (ab))\n"
148 " -l (ignored; compatibility with Apache Bench (ab))\n"
149 " -r (ignored; compatibility with Apache Bench (ab))\n"
150 " -q quiet: do not show version header or progress\n"
151 " -h show help and exit\n"
152 " -V show version and exit\n\n"
153 "example: \n"
154 " weighttpd -n 500000 -c 100 -t 2 -K 64 http://localhost/index.html\n");
155}
156
157/* Notes regarding pipelining
158 * Enabling pipelining (-p x where x > 1) results in extra requests being sent
159 * beyond the precise number requested on the command line. Subsequently,
160 * extra bytes might be read and reported in stats at the end of the test run.
161 * Additionally, the extra requests are dropped once the req_todo amount is
162 * reached, and so the target web server(s) might report errors that client
163 * dropped connection (client disconnect) for those final requests.
164 *
165 * The benefits of pipelining include reduced latency between request/response,
166 * as well as potentially fewer socket read()s for data if multiple requests or
167 * multiple responses are available to be read by server or client, respectively
168 */
169
170#define CLIENT_BUFFER_SIZE32 * 1024 32 * 1024
171
172
173struct Stats;
174typedef struct Stats Stats;
175struct Client;
176typedef struct Client Client;
177struct Worker;
178typedef struct Worker Worker;
179struct Config;
180typedef struct Config Config;
181struct Worker_Config;
182typedef struct Worker_Config Worker_Config;
183
184
185struct Stats {
186 uint64_t req_todo; /* total num of requests to do */
187 uint64_t req_started; /* total num of requests started */
188 uint64_t req_done; /* total num of requests done */
189 uint64_t req_success; /* total num of successful requests */
190 uint64_t req_failed; /* total num of failed requests */
191 uint64_t req_error; /* total num of errored requests */
192 uint64_t bytes_total; /* total num of bytes received (headers+body) */
193 uint64_t bytes_headers; /* total num of bytes received (headers) */
194 uint64_t req_2xx;
195 uint64_t req_3xx;
196 uint64_t req_4xx;
197 uint64_t req_5xx;
198};
199
200struct Client {
201 int revents;
202 enum {
203 PARSER_CONNECT,
204 PARSER_START,
205 PARSER_HEADER,
206 PARSER_BODY
207 } parser_state;
208
209 uint32_t buffer_offset; /* pos in buffer (size of data in buffer) */
210 uint32_t parser_offset; /* pos in parsing (behind buffer_offset) */
211 uint32_t request_offset; /* pos in sending request */
212 int chunked;
213 int64_t content_length;
214 int64_t chunk_size;
215 int64_t chunk_received;
216 int http_status_success;
217 int config_keepalive;
218 int keepalive;
219 int keptalive;
220 int pipelined;
221 int pipeline_max;
222 int tcp_fastopen;
223 int http_head;
224 int so_bufsz;
225
226 uint32_t request_size;
227 const char *request;
228 struct pollfd *pfd;
229 Stats *stats;
230 const struct addrinfo *raddr;
231 const struct addrinfo *laddr;
232 char buffer[CLIENT_BUFFER_SIZE32 * 1024];
233};
234
235struct Worker {
236 struct pollfd *pfds;
237 Client *clients;
238 Stats stats;
239 struct addrinfo raddr;
240 struct addrinfo laddr;
241 struct sockaddr_storage raddr_storage;
242 struct sockaddr_storage laddr_storage;
243};
244
245struct Worker_Config {
246 const Config *config;
247 int id;
248 int num_clients;
249 uint64_t num_requests;
250 Stats stats;
251 /* pad struct Worker_Config for cache line separation between threads.
252 * Round up to 256 to avoid chance of false sharing between threads.
253 * Alternatively, could memalign the allocation of struct Worker_Config
254 * list to cache line size (e.g. 128 bytes) */
255 uint64_t padding[(256 - (1*sizeof(void *))
256 - (2*sizeof(int))
257 - (1*sizeof(uint64_t))
258 - sizeof(Stats))
259 / sizeof(uint64_t)];
260};
261
262struct Config {
263 Worker_Config *wconfs;
264 char *proxy;
265 struct timeval ts_start;
266 struct timeval ts_end;
267
268 uint64_t req_count;
269 int thread_count;
270 int keep_alive;
271 int concur_count;
272 int pipeline_max;
273 int tcp_fastopen;
274 int http_head;
275 int so_bufsz;
276
277 int quiet;
278 uint32_t request_size;
279 char *request;
280 char buf[16384]; /*(used for simple 8k memaligned request buffer on stack)*/
281 struct addrinfo raddr;
282 struct addrinfo laddr;
283 struct sockaddr_storage raddr_storage;
284 struct sockaddr_storage laddr_storage;
285 struct laddrs {
286 struct addrinfo **addrs;
287 int num;
288 } laddrs;
289};
290
291
292__attribute_cold____attribute__((__cold__))
293static void
294client_init (Worker * const restrict worker,
295 const Config * const restrict config,
296 const int i)
297{
298 Client * const restrict client = worker->clients+i;
299 client->pfd = worker->pfds+i;
300 client->pfd->fd = -1;
301 client->parser_state = PARSER_CONNECT;
302
303 client->stats = &worker->stats;
304 client->raddr = &worker->raddr;
305 client->laddr = config->laddrs.num > 0
306 ? config->laddrs.addrs[(i % config->laddrs.num)]
307 : (0 != worker->laddr.ai_addrlen) ? &worker->laddr : NULL((void*)0);
308 client->config_keepalive = config->keep_alive;
309 client->pipeline_max = config->pipeline_max;
310 client->tcp_fastopen = config->tcp_fastopen;
311 client->http_head = config->http_head;
312 client->so_bufsz = config->so_bufsz;
313 client->request_size = config->request_size;
314 client->request = config->request;
315 /* future: might copy config->request to new allocation in Worker
316 * so that all memory accesses during benchmark execution are to
317 * independent, per-thread allocations */
318}
319
320
321__attribute_cold____attribute__((__cold__))
322static void
323client_delete (const Client * const restrict client)
324{
325 if (-1 != client->pfd->fd)
326 close(client->pfd->fd);
327}
328
329
330__attribute_cold____attribute__((__cold__))
331__attribute_noinline____attribute__ ((__noinline__))
332static void
333worker_init (Worker * const restrict worker,
334 Worker_Config * const restrict wconf)
335{
336 const Config * const restrict config = wconf->config;
337 memset(worker, 0, sizeof(Worker));
338 memcpy(&worker->laddr, &config->laddr, sizeof(config->laddr));
339 memcpy(&worker->raddr, &config->raddr, sizeof(config->raddr));
340 if (config->laddr.ai_addrlen)
341 worker->laddr.ai_addr = (struct sockaddr *)
342 memcpy(&worker->laddr_storage,
343 &config->laddr_storage, config->laddr.ai_addrlen);
344 worker->raddr.ai_addr = (struct sockaddr *)
345 memcpy(&worker->raddr_storage,
346 &config->raddr_storage, config->raddr.ai_addrlen);
347 const int num_clients = wconf->num_clients;
348 worker->stats.req_todo = wconf->num_requests;
349 worker->pfds = (struct pollfd *)calloc(num_clients, sizeof(struct pollfd));
350 worker->clients = (Client *)calloc(num_clients, sizeof(Client));
351 for (int i = 0; i < num_clients; ++i)
352 client_init(worker, wconf->config, i);
353}
354
355
356__attribute_cold____attribute__((__cold__))
357__attribute_noinline____attribute__ ((__noinline__))
358static void
359worker_delete (Worker * const restrict worker,
360 Worker_Config * const restrict wconf)
361{
362 int i;
363 const int num_clients = wconf->num_clients;
364
365 /* adjust bytes_total to discard count of excess responses
366 * (> worker->stats.req_todo) */
367 if (worker->clients[0].pipeline_max > 1) {
368 for (i = 0; i < num_clients; ++i) {
369 worker->stats.bytes_total -= ( worker->clients[i].buffer_offset
370 - worker->clients[i].parser_offset );
371 }
372 }
373
374 memcpy(&wconf->stats, &worker->stats, sizeof(Stats));
375 for (i = 0; i < num_clients; ++i)
376 client_delete(worker->clients+i);
377 free(worker->clients);
378 free(worker->pfds);
379}
380
381
382__attribute_cold____attribute__((__cold__))
383__attribute_noinline____attribute__ ((__noinline__))
384static void
385wconfs_init (Config * const restrict config)
386{
387 /* create Worker_Config data structures for each (future) thread */
388 Worker_Config * const restrict wconfs =
389 (Worker_Config *)calloc(config->thread_count, sizeof(Worker_Config));
390
391 uint32_t rest_concur = config->concur_count % config->thread_count;
392 uint32_t rest_req = config->req_count % config->thread_count;
393
394 for (int i = 0; i < config->thread_count; ++i) {
395 uint64_t reqs = config->req_count / config->thread_count;
396 int concur = config->concur_count / config->thread_count;
397
398 if (rest_concur) {
399 concur += 1;
400 rest_concur -= 1;
401 }
402
403 if (rest_req) {
404 reqs += 1;
405 rest_req -= 1;
406 }
407
408 if (!config->quiet)
409 printf("spawning thread #%d: %d concurrent requests, "
410 "%"PRIu64"l" "u"" total requests\n", i+1, concur, reqs);
411
412 wconfs[i].config = config;
413 wconfs[i].id = i;
414 wconfs[i].num_clients = concur;
415 wconfs[i].num_requests = reqs;
416 }
417
418 config->wconfs = wconfs;
419}
420
421
422__attribute_cold____attribute__((__cold__))
423__attribute_noinline____attribute__ ((__noinline__))
424static void
425wconfs_delete (const Config * const restrict config)
426{
427 free(config->wconfs);
428 if (config->request < config->buf
429 || config->buf+sizeof(config->buf) <= config->request)
430 free(config->request);
431
432 if (config->laddrs.num > 0) {
433 for (int i = 0; i < config->laddrs.num; ++i)
434 freeaddrinfo(config->laddrs.addrs[i]);
435 free(config->laddrs.addrs);
436 }
437}
438
439
440__attribute_hot____attribute__((__hot__))
441static void
442client_reset (Client * const restrict client, const int success)
443{
444 /* update worker stats */
445 Stats * const restrict stats = client->stats;
446
447 ++stats->req_done;
448 if (__builtin_expect( (0 != success), 1))
449 ++stats->req_success;
450 else
451 ++stats->req_failed;
452
453 client->revents = (stats->req_started < stats->req_todo) ? POLLOUT0x004 : 0;
454 if (client->revents && client->keepalive) {
455 /*(assumes writable; will find out soon if not and register interest)*/
456 ++stats->req_started;
457 client->parser_state = PARSER_START;
458 client->keptalive = 1;
459 if (client->parser_offset == client->buffer_offset) {
460 client->parser_offset = 0;
461 client->buffer_offset = 0;
462 }
463 #if 0
464 else if (client->parser_offset > (CLIENT_BUFFER_SIZE32 * 1024/2)) {
465 memmove(client->buffer, client->buffer+client->parser_offset,
466 client->buffer_offset - client->parser_offset + 1);
467 client->buffer_offset -= client->parser_offset;
468 client->parser_offset = 0;
469 }
470 /* future: if we tracked size of headers for first successful response,
471 * we might use that size to determine whether or not to memmove()
472 * any remaining contents in client->buffer to the beginning of buffer,
473 * e.g. if parser_offset + expected_response_len exceeds buffer size
474 * On the size, if we expect to already have completed response fully
475 * received in buffer, then skip the memmove(). */
476 #endif
477 if (--client->pipelined && client->buffer_offset)
478 client->revents |= POLLIN0x001;
479 }
480 else {
481 close(client->pfd->fd);
482 client->pfd->fd = -1;
483 client->pfd->events = 0;
484 /*client->pfd->revents = 0;*/
485 client->parser_state = PARSER_CONNECT;
486 }
487}
488
489
490__attribute_cold____attribute__((__cold__))
491__attribute_noinline____attribute__ ((__noinline__))
492static void
493client_error (Client * const restrict client)
494{
495 ++client->stats->req_error;
496 if (client->parser_state != PARSER_BODY) {
497 /*(might include subsequent responses to pipelined requests, but
498 * some sort of invalid response received if client_error() called)*/
499 client->stats->bytes_headers +=
500 (client->buffer_offset - client->parser_offset);
501 client->buffer_offset = 0;
502 client->parser_offset = 0;
503 }
504 client->keepalive = 0;
505 client_reset(client, 0);
506}
507
508
509__attribute_cold____attribute__((__cold__))
510__attribute_noinline____attribute__ ((__noinline__))
511static void
512client_perror (Client * const restrict client, const char * const restrict tag)
513{
514 const int errnum = errno(*__errno_location ());
515 client->buffer[0] = '\0';
516 #if defined(_GNU_SOURCE1) && defined(__GLIBC__2)
517 const char * const errstr =
518 strerror_r(errnum, client->buffer, sizeof(client->buffer));
519 #else /* XSI-compliant strerror_r() */
520 const char * const errstr = client->buffer;
521 strerror_r(errnum, client->buffer, sizeof(client->buffer));
522 #endif
523 fprintf(stderrstderr, "error: %s failed: (%d) %s\n", tag, errnum, errstr);
524 client_error(client);
525}
526
527
528
529static void
530client_connected (Client * const restrict client)
531{
532 client->request_offset = 0;
533 client->buffer_offset = 0;
534 client->parser_offset = 0;
535 client->parser_state = PARSER_START;
536 client->pipelined = 0;
537 client->keepalive = client->config_keepalive;
538 client->keptalive = 0;
539 /*client->success = 0;*/
540}
541
542
543__attribute_noinline____attribute__ ((__noinline__))
544static int
545client_connect (Client * const restrict client)
546{
547 const struct addrinfo * const restrict raddr = client->raddr;
548 int fd = client->pfd->fd;
549 int opt;
550
551 if (-1 == fd) {
552 ++client->stats->req_started;
553
554 do {
555 fd = socket(raddr->ai_family,raddr->ai_socktype,raddr->ai_protocol);
556 } while (__builtin_expect( (-1 == fd), 0) && errno(*__errno_location ()) == EINTR4);
557
558 if (fd >= 0) {
559 #if !SOCK_NONBLOCKSOCK_NONBLOCK
560 fcntl(fd, F_SETFL4, O_NONBLOCK04000 | O_RDWR02); /* set non-blocking */
561 #endif
562 client->pfd->fd = fd;
563 }
564 else {
565 client_perror(client, "socket()");
566 return 0;
567 }
568
569 if (1 == client->pipeline_max && raddr->ai_family != AF_UNIX1) {
570 /* disable Nagle if not pipelining requests and not AF_UNIX
571 * (pipelining enables keepalive, but if not pipelining but
572 * keepalive enabled, still want to disable Nagle to reduce latency
573 * when sending next keepalive request after receiving response) */
574 opt = 1;
575 setsockopt(fd, IPPROTO_TCPIPPROTO_TCP, TCP_NODELAY1, &opt, sizeof(opt));
576 }
577
578 if (0 != client->so_bufsz) {
579 opt = client->so_bufsz;
580 if (0 != setsockopt(fd, SOL_SOCKET1, SO_SNDBUF7, &opt, sizeof(opt)))
581 client_perror(client, "setsockopt() SO_SNDBUF");
582 if (0 != setsockopt(fd, SOL_SOCKET1, SO_RCVBUF8, &opt, sizeof(opt)))
583 client_perror(client, "setsockopt() SO_RCVBUF");
584 }
585
586 if (raddr->ai_family != AF_UNIX1) {
587 /*(might not be correct for real clients, but ok for load test)*/
588 struct linger l = { .l_onoff = 1, .l_linger = 0 };
589 if (0 != setsockopt(fd, SOL_SOCKET1, SO_LINGER13, &l, sizeof(l)))
590 client_perror(client, "setsockopt() SO_LINGER");
591 }
592
593 if (NULL((void*)0) != client->laddr) {
594 if (0 != bind(fd,client->laddr->ai_addr,client->laddr->ai_addrlen)){
595 client_perror(client, "bind() (local addr)");
596 return 0;
597 }
598 }
599
600 int rc;
601 #ifdef TCP_FASTOPEN23
602 ssize_t wr = 0;
603 if (client->tcp_fastopen) {/*(disabled if config->proxy is AF_UNIX)*/
604 wr = sendto(fd, client->request, client->request_size,
605 MSG_FASTOPENMSG_FASTOPEN | MSG_DONTWAITMSG_DONTWAIT | MSG_NOSIGNALMSG_NOSIGNAL,
606 raddr->ai_addr, raddr->ai_addrlen);
607 if (wr > 0) {
608 client_connected(client);
609 if (client->request_size == (uint32_t)wr) {
610 client->pfd->events |= POLLIN0x001;
611 if (++client->pipelined == client->pipeline_max) {
612 client->revents &= ~POLLOUT0x004;
613 client->pfd->events &= ~POLLOUT0x004;
614 }
615 }
616 else
617 client->request_offset = (uint32_t)wr;
618 return 1;
619 }
620 else if (-1 == wr && errno(*__errno_location ()) == EOPNOTSUPP95)
621 wr = 0;
622 else {
623 /*(0 == wr with sendto() should not happen
624 * with MSG_FASTOPEN and non-zero request_size)*/
625 wr = -1;
626 rc = -1;
627 }
628 }
629 if (0 == wr)
630 #endif
631 do {
632 rc = connect(fd, raddr->ai_addr, raddr->ai_addrlen);
633 } while (__builtin_expect( (-1 == rc), 0) && errno(*__errno_location ()) == EINTR4);
634
635 if (0 != rc) {
636 switch (errno(*__errno_location ())) {
637 case EINPROGRESS115:
638 case EALREADY114:
639 /* async connect now in progress */
640 client->revents &= ~POLLOUT0x004;
641 client->pfd->events |= POLLOUT0x004;
642 return 0;
643 default:
644 client_perror(client, "connect()");
645 return 0;
646 }
647 }
648 }
649 else {
650 opt = 0;
651 socklen_t optlen = sizeof(opt);
652 if (0 != getsockopt(fd,SOL_SOCKET1,SO_ERROR4,&opt,&optlen) || 0 != opt) {
653 if (0 != opt) errno(*__errno_location ()) = opt;
654 client_perror(client, "connect() getsockopt()");
655 return 0; /* error connecting */
656 }
657 }
658
659 /* successfully connected */
660 client_connected(client);
661 return 1;
662}
663
664
665
666static int
667client_parse_chunks (Client * const restrict client)
668{
669 do {
670 char *str = client->buffer+client->parser_offset;
671
672 if (-1 == client->chunk_size) {
673 /* read chunk size */
674 /*char *end = strchr(str, '\n');*/
675 char *end =
676 memchr(str, '\n', client->buffer_offset - client->parser_offset);
677 if (!end) /* partial line */
678 return 1;
679 ++end;
680
681 /* assume server sends valid chunked header
682 * (not validating; (invalid) chunked header without any
683 * hex digits is treated as 0-chunk, ending input) */
684 client->chunk_size = 0;
685 do {
686 int c = *str;
687 client->chunk_size <<= 4;
688 if (c >= '0' && c <= '9')
689 client->chunk_size |= (c - '0');
690 else if ((c |= 0x20) >= 'a' && c <= 'f')
691 client->chunk_size |= (c - 'a' + 10);
692 else {
693 if (c=='\r' || c=='\n' || c==' ' || c=='\t' || c==';')
694 break;
695 client_error(client);
696 return 0;
697 }
698 } while (*++str != '\r' && *str != '\n');
699
700 if (0 == client->chunk_size) {
701 /* chunk of size 0 marks end of content body
702 * check for final "\r\n" ending response
703 * (not handling trailers if user supplied -H "TE: trailers") */
704 if (end + 2 > client->buffer + client->buffer_offset) {
705 client->chunk_size = -1;
706 return 1; /* final "\r\n" not yet received */
707 }
708 if (end[0] == '\r' && end[1] == '\n')
709 client->stats->bytes_headers += 2;
710 else
711 client->keepalive = 0; /*(just close con if trailers)*/
712 client->parser_offset = end - client->buffer + 2;
713 client_reset(client, client->http_status_success);
714 return 0; /*(trigger loop continue in caller)*/
715 }
716
717 client->parser_offset = end - client->buffer;
718 client->chunk_received = 0;
719 client->chunk_size += 2; /*(for chunk "\r\n" end)*/
720 }
721
722 /* consume chunk until chunk_size is reached */
723 const int rd = client->buffer_offset - client->parser_offset;
724 int chunk_remain = client->chunk_size - client->chunk_received;
725 if (rd >= chunk_remain) {
726 client->chunk_received += chunk_remain;
727 client->parser_offset += chunk_remain;
728
729 if (client->buffer[client->parser_offset-1] != '\n') {
730 client_error(client);
731 return 0;
732 }
733
734 /* got whole chunk, next! */
735 client->chunk_size = -1;
736 client->chunk_received = 0;
737 }
738 else {
739 client->chunk_received += rd;
740 client->parser_offset += rd;
741 }
742
743 } while (client->parser_offset != client->buffer_offset);/* more to parse */
744
745 client->parser_offset = 0;
746 client->buffer_offset = 0;
747 return 1;
748}
749
750
751__attribute_hot____attribute__((__hot__))
752__attribute_pure____attribute__ ((__pure__))
753static uint64_t
754client_parse_uint64 (const char * const restrict str)
755{
756 /* quick-n-dirty conversion of numerical string to integral number
757 * Note: not validating field and not checking for valid number
758 * (weighttp not intended for use with requests > 2 GB, as transfer
759 * of body would take the majority of the time in that case)*/
760 uint64_t x = 0;
761 for (int i = 0; (unsigned int)(str[i] - '0') < 10u; ++i) {
762 x *= 10;
763 x += (unsigned int)(str[i] - '0');
764 }
765 return x;
766}
767
768
769__attribute_hot____attribute__((__hot__))
770__attribute_noinline____attribute__ ((__noinline__))
771static int
772client_parse (Client * const restrict client)
773{
774 char *end;
775 uint32_t len;
776
777 /* future: might combine PARSER_START and PARSER_HEADER states by
778 * collecting entire set of headers (reading until "\r\n\r\n")
779 * prior to parsing */
780
781 switch (client->parser_state) {
782
783 case PARSER_START:
784 /* look for HTTP/1.1 200 OK (though also accept HTTP/1.0 200)
785 * Note: does not support 1xx intermediate messages */
786 /* Note: not validating response line; assume valid */
787 /*end = strchr(client->buffer+client->parser_offset, '\n');*/
788 end = memchr(client->buffer+client->parser_offset, '\n',
789 client->buffer_offset - client->parser_offset);
790 if (NULL((void*)0) != end) {
791 len = (uint32_t)(end - client->buffer - client->parser_offset + 1);
792 if (len < sizeof("HTTP/1.1 200\r\n")-1) {
793 client_error(client);
794 return 0;
795 }
796 }
797 else /*(partial response line; incomplete)*/
798 return 1;
799
800 client->content_length = -1;
801 client->chunked = 0;
802 client->http_status_success = 1;
803 switch (client->buffer[client->parser_offset + sizeof("HTTP/1.1 ")-1]
804 - '0') {
805 case 2:
806 ++client->stats->req_2xx;
807 break;
808 case 3:
809 ++client->stats->req_3xx;
810 break;
811 case 4:
812 client->http_status_success = 0;
813 ++client->stats->req_4xx;
814 break;
815 case 5:
816 client->http_status_success = 0;
817 ++client->stats->req_5xx;
818 break;
819 default:
820 /* invalid status code */
821 client_error(client);
822 return 0;
823 }
824 client->stats->bytes_headers += len;
825 client->parser_offset += len;
826 client->parser_state = PARSER_HEADER;
827 /* fall through */
828
829 case PARSER_HEADER:
830 /* minimally peek at Content-Length, Connection, Transfer-Encoding */
831 do {
832 const char *str = client->buffer+client->parser_offset;
833 /*end = strchr(str, '\n');*/
834 end =
835 memchr(str, '\n', client->buffer_offset - client->parser_offset);
836 if (NULL((void*)0) == end)
837 return 1;
838 len = (uint32_t)(end - str + 1);
839 client->stats->bytes_headers += len;
840 client->parser_offset += len;
841
842 /* minimum lengths for us to check for ':' in the following:
843 * "Content-Length:0\r\n"
844 * "Connection:close\r\n"
845 * "Transfer-Encoding:chunked\r\n"*/
846 if (end - str < 17)
847 continue;
848
849 if (str[14] == ':'
850 && (0 == memcmp(str, "Content-Length",
851 sizeof("Content-Length")-1)
852 || 0 == strncasecmp(str, "Content-Length",
853 sizeof("Content-Length")-1))) {
854 str += sizeof("Content-Length:")-1;
855 if (__builtin_expect( (*str == ' '), 1))
856 ++str;
857 while (__builtin_expect( (*str == ' '), 0)
858 || __builtin_expect( (*str == '\t'), 0))
859 ++str;
860 client->content_length = client_parse_uint64(str);
861 }
862 else if (str[10] == ':'
863 && (0 == memcmp(str, "Connection",
864 sizeof("Connection")-1)
865 || 0 == strncasecmp(str, "Connection",
866 sizeof("Connection")-1))) {
867 str += sizeof("Connection:")-1;
868 if (__builtin_expect( (*str == ' '), 1))
869 ++str;
870 while (__builtin_expect( (*str == ' '), 0)
871 || __builtin_expect( (*str == '\t'), 0))
872 ++str;
873 if ((*str | 0x20) == 'c') /*(assume "close")*/
874 client->keepalive = 0;
875 }
876 else if (str[17] == ':'
877 && (0 == memcmp(str, "Transfer-Encoding",
878 sizeof("Transfer-Encoding")-1)
879 || 0 == strncasecmp(str, "Transfer-Encoding",
880 sizeof("Transfer-Encoding")-1))) {
881 client->chunked = 1; /*(assume "chunked")*/
882 client->chunk_size = -1;
883 client->chunk_received = 0;
884 }
885
886 } while (end[1] != '\r' || end[2] != '\n');
887
888 /* body reached */
889 client->stats->bytes_headers += 2;
890 client->parser_offset += 2;
891 client->parser_state = PARSER_BODY;
892 if (client->http_head)
893 client->content_length = 0;
894 else if (!client->chunked && -1 == client->content_length)
895 client->keepalive = 0;
896 /* fall through */
897
898 case PARSER_BODY:
899 /* consume and discard response body */
900
901 if (client->chunked)
902 return client_parse_chunks(client);
903 else {
904 /* consume all data until content-length reached (or EOF) */
905 if (-1 != client->content_length) {
906 uint32_t rd = client->buffer_offset - client->parser_offset;
907 if (client->content_length > rd)
908 client->content_length -= rd;
909 else { /* full response received */
910 client->parser_offset += client->content_length;
911 client_reset(client, client->http_status_success);
912 return 0; /*(trigger loop continue in caller)*/
913 }
914 }
915
916 client->buffer_offset = 0;
917 client->parser_offset = 0;
918 return 1;
919 }
920
921 case PARSER_CONNECT: /*(should not happen here)*/
922 break;
923 }
924
925 return 1;
926}
927
928
929
930static void
931client_revents (Client * const restrict client)
932{
933 while (client->revents & POLLIN0x001) {
934 /* parse pipelined responses */
935 if (client->buffer_offset && !client_parse(client))
936 continue;
937
938 ssize_t r;
939 do {
940 r = recv(client->pfd->fd, client->buffer+client->buffer_offset,
941 sizeof(client->buffer) - client->buffer_offset - 1,
942 MSG_DONTWAITMSG_DONTWAIT);
943 } while (__builtin_expect( (-1 == r), 0) && errno(*__errno_location ()) == EINTR4);
944 if (__builtin_expect( (r > 0), 1)) {
945 if (r < (ssize_t)(sizeof(client->buffer)-client->buffer_offset-1))
946 client->revents &= ~POLLIN0x001;
947 client->buffer[(client->buffer_offset += (uint32_t)r)] = '\0';
948 client->stats->bytes_total += r;
949
950 if (!client_parse(client))
951 continue;
952
953 /* PARSER_BODY handling consumes data, so buffer full might happen
954 * only when parsing response header line or chunked header line.
955 * If buffer is full, then line is *way* too long. However, if
956 * client->parser_offset is non-zero, then move data to beginning
957 * of buffer and attempt to read() more */
958 if (__builtin_expect(
959 (client->buffer_offset == sizeof(client->buffer)-1), 0)) {
960 if (0 == client->parser_offset) {
961 client_error(client); /* response header too big */
962 break;
963 }
964 else {
965 memmove(client->buffer,client->buffer+client->parser_offset,
966 client->buffer_offset - client->parser_offset + 1);
967 client->buffer_offset -= client->parser_offset;
968 client->parser_offset = 0;
969 }
970 }
971 }
972 else {
973 if (-1 == r) { /* error */
974 if (errno(*__errno_location ()) == EAGAIN11
975 #if EAGAIN11 != EWOULDBLOCK11
976 || errno(*__errno_location ()) == EWOULDBLOCK11
977 #endif
978 ) {
979 client->revents &= ~POLLIN0x001;
980 client->pfd->events |= POLLIN0x001;
981 break;
982 }
983 else
984 client_perror(client, "read()");
985 }
986 else { /* disconnect; evaluate if end-of-response or error */
987 if (client->http_status_success
988 && client->parser_state == PARSER_BODY
989 && !client->chunked && -1 == client->content_length) {
990 client->keepalive = 0;
991 client_reset(client, 1);
992 }
993 else {
994 if (client->keptalive
995 && client->parser_state == PARSER_START
996 && 0 == client->buffer_offset) {
997 /* (server might still read and discard request,
998 * but has initiated connection close)
999 * (decrement counters to redo request, including
1000 * decrementing counters that will be incremented
1001 * by call to client_error() directly below) */
1002 --client->stats->req_started;
1003 --client->stats->req_failed;
1004 --client->stats->req_error;
1005 --client->stats->req_done;
1006 }
1007 client_error(client);
1008 }
1009 }
1010 }
1011 }
1012
1013 if (__builtin_expect( (client->revents & (POLLERR0x008|POLLHUP0x010)), 0)) {
1014 client->keepalive = 0;
1015 client_reset(client, 0);
1016 }
1017
1018 while (client->revents & POLLOUT0x004) {
1019 ssize_t r;
1020 if (client->parser_state == PARSER_CONNECT && !client_connect(client))
1021 continue;
1022
1023 do {
1024 r = send(client->pfd->fd,
1025 client->request+client->request_offset,
1026 client->request_size - client->request_offset,
1027 MSG_DONTWAITMSG_DONTWAIT | MSG_NOSIGNALMSG_NOSIGNAL);
1028 } while (__builtin_expect( (-1 == r), 0) && errno(*__errno_location ()) == EINTR4);
1029 if (__builtin_expect( (r > 0), 1)) {
1030 if (client->request_size == (uint32_t)r
1031 || client->request_size==(client->request_offset+=(uint32_t)r)){
1032 /* request sent; register read interest for response */
1033 client->request_offset = 0;
1034 client->pfd->events |= POLLIN0x001;
1035 if (++client->pipelined < client->pipeline_max)
1036 continue;
1037 else {
1038 client->revents &= ~POLLOUT0x004; /*(trigger write() loop exit)*/
1039 client->pfd->events &= ~POLLOUT0x004;
1040 }
1041 }
1042 else {
1043 client->revents &= ~POLLOUT0x004; /*(trigger write() loop exit)*/
1044 client->pfd->events |= POLLOUT0x004;
1045 }
1046 }
1047 else {
1048 if (-1 == r) { /* error */
1049 if (errno(*__errno_location ()) == EAGAIN11
1050 #if EAGAIN11 != EWOULDBLOCK11
1051 || errno(*__errno_location ()) == EWOULDBLOCK11
1052 #endif
1053 ) {
1054 client->revents &= ~POLLOUT0x004;
1055 client->pfd->events |= POLLOUT0x004;
1056 break;
1057 }
1058 else
1059 client_perror(client, "write()");
1060 }
1061 else { /* (0 == r); not expected; not attempting to write 0 bytes */
1062 client->keepalive = 0;
1063 client_reset(client, 0);
1064 }
1065 }
1066 }
1067}
1068
1069
1070
1071static void *
1072worker_thread (void * const arg)
1073{
1074 Worker worker;
1075 int i, nready;
1076 Worker_Config * const restrict wconf = (Worker_Config *)arg;
1077 worker_init(&worker, wconf);
1078
1079 const int num_clients = wconf->num_clients;
1080 const int progress =
1081 (0==wconf->id && !wconf->config->quiet); /* report only in first thread */
1082 const uint64_t progress_interval = /* print every 10% done */
1083 (worker.stats.req_todo > 10) ? worker.stats.req_todo / 10 : 1;
1084 uint64_t progress_next = progress_interval;
1085
1086 /* start all clients */
1087 for (i = 0; i < num_clients; ++i) {
1088 if (worker.stats.req_started < worker.stats.req_todo) {
1089 worker.clients[i].revents = POLLOUT0x004;
1090 client_revents(worker.clients+i);
1091 }
1092 }
1093
1094 while (worker.stats.req_done < worker.stats.req_todo) {
1095 do { /*(infinite wait)*/
1096 nready = poll(worker.pfds, (nfds_t)num_clients, -1);
1097 } while (__builtin_expect( (-1 == nready), 0) && errno(*__errno_location ()) == EINTR4);
1098 if (__builtin_expect( (-1 == nready), 0)) {
1099 /*(repurpose client_perror(); use client buffer for strerror_r())*/
1100 client_perror(worker.clients+0, "poll()"); /* fatal; ENOMEM */
1101 return NULL((void*)0);
1102 }
1103
1104 i = 0;
1105 do {
1106 while (0 == worker.pfds[i].revents)
1107 ++i;
1108 worker.clients[i].revents |= worker.pfds[i].revents;
1109 worker.pfds[i].revents = 0;
1110 client_revents(worker.clients+i);
1111 } while (--nready);
1112
1113 if (progress) {
1114 /*(assume progress of one thread approximates that of all threads)*/
1115 /*(RFE: main thread could poll and report progress of all workers)*/
1116 while (__builtin_expect( worker.stats.req_done >= progress_next,0)){
1117 printf("progress: %3d%% done\n", (int)
1118 (worker.stats.req_done * 100 / worker.stats.req_todo));
1119 if (progress_next == worker.stats.req_todo)
1120 break;
1121 progress_next += progress_interval;
1122 if (__builtin_expect( progress_next > worker.stats.req_todo, 0))
1123 progress_next = worker.stats.req_todo;
1124 }
1125 }
1126 }
1127
1128 worker_delete(&worker, wconf);
1129 return NULL((void*)0);
1130}
1131
1132
1133__attribute_cold____attribute__((__cold__))
1134__attribute_noinline____attribute__ ((__noinline__))
1135static void
1136config_error_diagnostic (const char * const restrict errfmt,
1137 const int perr, va_list ap)
1138{
1139 const int errnum = errno(*__errno_location ());
1140 show_version();
1141 show_help();
1142 fflush(stdoutstdout);
1143
1144 fprintf(stderrstderr, "\nerror: ");
1145 vfprintf(stderrstderr, errfmt, ap);
1146
1147 if (!perr)
1148 fprintf(stderrstderr, "\n\n");
1149 else {
1150 char buf[1024];
1151 buf[0] = '\0';
1152 #if defined(_GNU_SOURCE1) && defined(__GLIBC__2)
1153 const char * const errstr = strerror_r(errnum, buf, sizeof(buf));
1154 #else /* XSI-compliant strerror_r() */
1155 const char * const errstr = buf;
1156 strerror_r(errnum, buf, sizeof(buf));
1157 #endif
1158
1159 fprintf(stderrstderr, ": (%d) %s\n\n", errnum, errstr);
1160 }
1161}
1162
1163
1164__attribute_cold____attribute__((__cold__))
1165__attribute_format__((__printf__, 1, 2))__attribute__((__format__ (__printf__, 1, 2)))
1166__attribute_noinline____attribute__ ((__noinline__))
1167__attribute_noreturn____attribute__((__noreturn__))
1168static void
1169config_error (const char * const restrict errfmt, ...)
1170{
1171 va_list ap;
1172 va_start(ap, errfmt)__builtin_va_start(ap, errfmt);
1173 config_error_diagnostic(errfmt, 0, ap);
1174 va_end(ap)__builtin_va_end(ap);
1175 exit(1);
1176}
1177
1178
1179__attribute_cold____attribute__((__cold__))
1180__attribute_format__((__printf__, 1, 2))__attribute__((__format__ (__printf__, 1, 2)))
1181__attribute_noinline____attribute__ ((__noinline__))
1182__attribute_noreturn____attribute__((__noreturn__))
1183static void
1184config_perror (const char * const restrict errfmt, ...)
1185{
1186 va_list ap;
1187 va_start(ap, errfmt)__builtin_va_start(ap, errfmt);
1188 config_error_diagnostic(errfmt, 1, ap);
1189 va_end(ap)__builtin_va_end(ap);
1190 exit(1);
1191}
1192
1193
1194typedef struct config_params {
1195 const char *method;
1196 const char *uri;
1197 char *laddrstr;
1198 int use_ipv6;
1199 int headers_num;
1200 int cookies_num;
1201 const char *headers[64];
1202 const char *cookies[64];
1203 const char *body_content_type;
1204 const char *body_filename;
1205 const char *authorization;
1206 const char *proxy_authorization;
1207} config_params;
1208
1209
1210__attribute_cold____attribute__((__cold__))
1211static int
1212config_laddr (Config * const restrict config,
1213 const char * const restrict laddrstr)
1214{
1215 struct addrinfo hints, *res = NULL((void*)0);
1216 memset(&hints, 0, sizeof(hints));
1217 /*hints.ai_flags |= AI_NUMERICHOST;*/
1218 hints.ai_family = config->raddr.ai_family;
1219 hints.ai_socktype = SOCK_STREAMSOCK_STREAM;
1220
1221 if (0 != getaddrinfo(laddrstr, NULL((void*)0), &hints, &res) || NULL((void*)0) == res)
1222 return 0;
1223
1224 config->laddr.ai_family = res->ai_family;
1225 config->laddr.ai_socktype = res->ai_socktype;
1226 config->laddr.ai_protocol = res->ai_protocol;
1227 config->laddr.ai_addrlen = res->ai_addrlen;
1228 config->laddr.ai_addr = (struct sockaddr *)
1229 memcpy(&config->laddr_storage, res->ai_addr, res->ai_addrlen);
1230
1231 freeaddrinfo(res);
1232 return 1;
1233}
1234
1235
1236__attribute_cold____attribute__((__cold__))
1237static int
1238config_laddrs (Config * const restrict config,
1239 char * const restrict laddrstr)
1240{
1241 char *s;
1242 int num = 1;
1243 for (s = laddrstr; NULL((void*)0) != (s = strchr(s, ',')); s = s+1) ++num;
1244 if (1 == num) return config_laddr(config, laddrstr);
1245
1246 struct addrinfo hints, **res;
1247 memset(&hints, 0, sizeof(hints));
1248 /*hints.ai_flags |= AI_NUMERICHOST;*/
1249 hints.ai_family = config->raddr.ai_family;
1250 hints.ai_socktype = SOCK_STREAMSOCK_STREAM;
1251
1252 config->laddrs.num = num;
1253 config->laddrs.addrs = res =
1254 (struct addrinfo **)calloc((size_t)num, sizeof(struct addrinfo *));
1255
1256 s = laddrstr;
1257 for (int i = 0; i < num; ++i, ++res) {
1258 char *e = strchr(s, ',');
1259 if (NULL((void*)0) != e) *e = '\0';
1260
1261 *res = NULL((void*)0);
1262 if (0 != getaddrinfo(s, NULL((void*)0), &hints, res) || NULL((void*)0) == *res)
1263 return 0; /*(leave laddrstr modified so last addr is one w/ error)*/
1264
1265 if (NULL((void*)0) == e) break;
1266 *e = ',';
1267 s = e+1;
1268 }
1269
1270 return 1;
1271}
1272
1273