BitLab 0.1.0
BitLab: A Browser for the Bitcoin P2P Network and Blockchain
Loading...
Searching...
No Matches
cli.c
Go to the documentation of this file.
1#include "cli.h"
2
3#include <readline/readline.h>
4#include <readline/history.h>
5#include <errno.h>
6#include <pthread.h>
7#include <sys/stat.h>
8
9#include "peer_queue.h"
10#include "peer_connection.h"
11#include "state.h"
12#include "utils.h"
13#include "ip.h"
14
15// History directory initialized in CLI thread
16static const char* cli_history_dir = NULL;
17
18// CLI mutex protecting started command-line operation to avoid mixing outputs
19pthread_mutex_t cli_mutex = PTHREAD_MUTEX_INITIALIZER;
20
21// BitLab command array
23{
24 {
26 .cli_command_name = "clear",
27 .cli_command_brief_desc = "Clears CLI screen.",
28 .cli_command_detailed_desc =
29 " * clear - Clears the command line interface screen for better readability.",
30 .cli_command_usage = "clear"
31 },
32 {
33 .cli_command = &cli_history,
34 .cli_command_name = "history",
35 .cli_command_brief_desc = "Prints command history.",
36 .cli_command_detailed_desc =
37 " * history - Displays the history of all entered commands for reference.",
38 .cli_command_usage = "history"
39 },
40 {
41 .cli_command = &cli_info,
42 .cli_command_name = "info",
43 .cli_command_brief_desc = "Prints program information.",
44 .cli_command_detailed_desc =
45 " * info - Displays information about BitLab program and the host machine.",
46 .cli_command_usage = "info"
47 },
48 {
49 .cli_command = &cli_echo,
50 .cli_command_name = "echo",
51 .cli_command_brief_desc = "Echoes the input.",
52 .cli_command_detailed_desc =
53 " * echo - Outputs the text or arguments provided as input.",
54 .cli_command_usage = "echo [Text to be echoed]"
55 },
56 {
57 .cli_command = &cli_exit,
58 .cli_command_name = "exit",
59 .cli_command_brief_desc = "Stops the server.",
60 .cli_command_detailed_desc =
61 " * exit - Stops the server gracefully and terminates the session.",
62 .cli_command_usage = "exit [-f | --force]"
63 },
64 {
65 .cli_command = &cli_get_ip,
66 .cli_command_name = "getip",
67 .cli_command_brief_desc = "Obtains IP address of given URL.",
68 .cli_command_detailed_desc =
69 " * getip - Retrieves and displays the remote IP address of a specified URL or host if not specified.",
70 .cli_command_usage = "getip [URL 1] [URL 2] ..."
71 },
72 {
73 .cli_command = &cli_help,
74 .cli_command_name = "help",
75 .cli_command_brief_desc = "Prints command descriptions.",
76 .cli_command_detailed_desc =
77 " * help - Lists all available commands with their descriptions.",
78 .cli_command_usage = "help [command]"
79 },
80 {
81 .cli_command = &cli_peer_discovery,
82 .cli_command_name = "peerdiscovery",
83 .cli_command_brief_desc = "Starts peer discovery.",
84 .cli_command_detailed_desc =
85 " * peerdiscovery - Initiates the peer discovery proces. Use daemon argument to detach and run in the background. Run again to connect and wait for results. Use without arguments to run default DNS lookup. Add domain after -d or --dns to use custom DNS lookup. Use -h or --hardcoded to use hardcoded seeds of Bitcoin network IPs. Running without arguments will wait for results and running with other arguments before previous are generated will wait for the previous results.",
86 .cli_command_usage =
87 "peerdiscovery [-d | --daemon] [-h | --hardcoded] [-l | --dns-lookup]"
88 },
89 {
90 .cli_command = &cli_connect,
91 .cli_command_name = "connect",
92 .cli_command_brief_desc = "Connects to the specified IP address.",
93 .cli_command_detailed_desc =
94 " * connect - Connects to the specified IP address to establish a peer-to-peer connection.",
95 .cli_command_usage = "connect [IP address]"
96 },
97 {
98 .cli_command = &cli_ping,
99 .cli_command_name = "ping",
100 .cli_command_brief_desc = "Pings the specified IP address.",
101 .cli_command_detailed_desc =
102 " * ping - Pings the specified IP address to check for connectivity.",
103 .cli_command_usage = "ping [-c | --count]"
104 },
105 {
106 .cli_command = &cli_whoami,
107 .cli_command_name = "whoami",
108 .cli_command_brief_desc = "Prints basic information about user.",
109 .cli_command_detailed_desc =
110 " * whoami - Displays username or full user information, including username, local IP, and public IP address when --full argument provided.",
111 .cli_command_usage = "whoami [-f | --full]",
112 },
113 {
114 .cli_command = &cli_list,
115 .cli_command_name = "list",
116 .cli_command_brief_desc = "Lists connected nodes.",
117 .cli_command_detailed_desc = " * list - Lists nodes connected with 'connect' command. Shows IP address, port, socket FD, thread ID, connection status, operation status, compact blocks, and fee rate.",
118 .cli_command_usage = "list"
119 },
120 {
121 .cli_command = &cli_getaddr,
122 .cli_command_name = "getaddr",
123 .cli_command_brief_desc = "Gets addresses from the specified node.",
124 .cli_command_detailed_desc = " * getaddr - Sends 'getaddr' command to peer and wait for response. Use with node index to specify the node. Prints IP addresses of returned nodes.",
125 .cli_command_usage = "getaddr [idx of node]"
126 },
127 {
128 .cli_command = &cli_disconnect,
129 .cli_command_name = "disconnect",
130 .cli_command_brief_desc = "Disconnect from specified node.",
131 .cli_command_detailed_desc = " * disconnect - Disconnects from node specified by the given node ID. Closes the socket, terminates the thread, and logs the disconnection.",
132 .cli_command_usage = "disconnect [idx of node]"
133 },
134 {
135 .cli_command = &cli_getheaders,
136 .cli_command_name = "getheaders",
137 .cli_command_brief_desc = "Gets blockchain headers from the specified node.",
138 .cli_command_detailed_desc = " * getheaders - Sends 'getheaders' and prints decoded 'headers' as a chain of block headers.",
139 .cli_command_usage = "getheaders [idx of node]"
140 },
141 {
142 .cli_command = &cli_getblocks,
143 .cli_command_name = "getblocks",
144 .cli_command_brief_desc = "Gets block headers from given range",
145 .cli_command_detailed_desc = " * getblocks - Sends 'getblocks' message with fixed (dummy) interval to ask for 'inv' message. Obtains block hashes.",
146 .cli_command_usage = "getblocks [idx of node]"
147 },
148 {
149 .cli_command = &cli_inv,
150 .cli_command_name = "inv",
151 .cli_command_brief_desc = "Let's answer 'getblocks' manually or verbosely send advertisement about known blocks.",
152 .cli_command_detailed_desc = " * inv - Sends an 'inv' message to the specified peer using the existing blocks.dat file to advertise known blocks.",
153 .cli_command_usage = "inv [idx of node]"
154 },
155 {
156 .cli_command = &cli_getdata,
157 .cli_command_name = "getdata",
158 .cli_command_brief_desc = "Obtains transactions from given block.",
159 .cli_command_detailed_desc = " * getdata - Sends 'getdata' to a idx-th node with given block hashes. Retrieves transaction data from returned 'blocks' message.",
160 .cli_command_usage = "getdata [idx of node] [block hash 1] ..."
161 },
162 {
163 .cli_command = &cli_tx,
164 .cli_command_name = "tx",
165 .cli_command_brief_desc = "Sends a transaction to a specified node.",
166 .cli_command_detailed_desc = " * tx - Sends a 'tx' message to the specified node with the provided transaction data.",
167 .cli_command_usage = "tx [idx of node] [transaction data in hex]"
168 },
169}; // do not add NULLs at the end
170
172{
173 int longest_command_length = 0;
174 int longest_parameters_length = 0;
175 for (int i = 0; i < CLI_COMMANDS_NUM; ++i)
176 {
177 int cmd_length = 0;
178 int param_length = 0;
179
180 char* space_pos = strchr(cli_commands[i].cli_command_usage, ' ');
181 if (space_pos)
182 {
183 cmd_length = space_pos - cli_commands[i].cli_command_usage;
184 // only command length
185 param_length = strlen(space_pos + 1); // parameters length
186 }
187 else
188 {
189 cmd_length = strlen(cli_commands[i].cli_command_usage);
190 }
191
192 if (cmd_length > longest_command_length)
193 longest_command_length = cmd_length;
194 if (param_length > longest_parameters_length)
195 longest_parameters_length = param_length;
196 }
197
198 int longest_description_length = 0;
199 for (int i = 0; i < CLI_COMMANDS_NUM; ++i)
200 {
201 int length = strlen(cli_commands[i].cli_command_brief_desc);
202 if (length > longest_description_length)
203 longest_description_length = length;
204 }
205
206 char* dashes_command = (char*)malloc(longest_command_length + 1);
207 memset(dashes_command, '-', longest_command_length);
208 dashes_command[longest_command_length] = '\0';
209
210 char* dashes_params = (char*)malloc(longest_parameters_length + 1);
211 memset(dashes_params, '-', longest_parameters_length);
212 dashes_params[longest_parameters_length] = '\0';
213
214 char* dashes_description = (char*)malloc(longest_description_length + 1);
215 memset(dashes_description, '-', longest_description_length);
216 dashes_description[longest_description_length] = '\0';
217
218 guarded_print_line("\033[1m%-*s | %-*s | %s\033[0m",
219 longest_command_length, "Command",
220 longest_parameters_length, "Parameters",
221 "Description");
222 guarded_print_line("%s-+-%s-+-%s",
223 dashes_command, dashes_params, dashes_description);
224
225 for (int i = 0; i < CLI_COMMANDS_NUM; ++i)
226 {
227 const char* usage = cli_commands[i].cli_command_usage;
228 const char* description = cli_commands[i].cli_command_brief_desc;
229
230 // split the command and its parameters
231 char command[longest_command_length + 1];
232 char parameters[longest_parameters_length + 1];
233 char* params = strchr(usage, ' ');
234
235 if (params)
236 {
237 int cmd_length = params - usage;
238 strncpy(command, usage, cmd_length);
239 command[cmd_length] = '\0';
240 strncpy(parameters, params + 1, longest_parameters_length);
241 parameters[longest_parameters_length] = '\0';
242 }
243 else
244 {
245 strncpy(command, usage, longest_command_length);
246 command[longest_command_length] = '\0';
247 parameters[0] = '\0';
248 }
249
250 // print the row
251 guarded_print_line("%-*s | %-*s | %s",
252 longest_command_length, command,
253 longest_parameters_length, parameters,
254 description);
255 }
256
257 free(dashes_command);
258 free(dashes_params);
259 free(dashes_description);
260}
261
262void print_usage(const char* command_name)
263{
264 for (int i = 0; i < CLI_COMMANDS_NUM; ++i)
265 {
266 if (strcmp(command_name, cli_commands[i].cli_command_name) == 0)
267 {
268 guarded_print_line("Usage: %s", cli_commands[i].cli_command_usage);
269 return;
270 }
271 }
272 guarded_print_line("Unknown command: %s", command_name);
273}
274
276{
277 for (int i = 0; i < CLI_COMMANDS_NUM; ++i)
278 guarded_print_line("%s", cli_commands[i].cli_command_name);
279}
280
281int cli_exit(char** args)
282{
283 pthread_mutex_lock(&cli_mutex);
284 if (args[0] != NULL)
285 {
286 if (strcmp(args[0], "-f") == 0 || strcmp(args[0], "--force") == 0)
287 {
288 if (args[1] != NULL)
289 {
291 "Unknown argument for exit command: \"%s\"", args[1]);
292 print_usage("exit");
293 pthread_mutex_unlock(&cli_mutex);
294 return 1;
295 }
296 log_message(LOG_WARN, BITLAB_LOG, __FILE__, "Force exiting BitLab");
297 pthread_mutex_unlock(&cli_mutex);
298 exit(0);
299 }
301 "Unknown argument for exit command: \"%s\"", args[0]);
302 print_usage("exit");
303 pthread_mutex_unlock(&cli_mutex);
304 return 1;
305 }
306 log_message(LOG_INFO, BITLAB_LOG, __FILE__, "Server shutdown requested");
307 set_exit_flag(1);
308 pthread_mutex_unlock(&cli_mutex);
309 return 0;
310}
311
312int cli_history(char** args)
313{
314 pthread_mutex_lock(&cli_mutex);
315 if (args[0] != NULL)
316 {
318 "Unknown argument for history command: \"%s\"", args[0]);
319 print_usage("history");
320 pthread_mutex_unlock(&cli_mutex);
321 return 1;
322 }
323 HIST_ENTRY** history_entries = history_list();
324 if (history_entries)
325 {
326 for (int i = 0; history_entries[i] != NULL; ++i)
327 guarded_print_line("%d: %s", i + 1, history_entries[i]->line);
328 }
329 pthread_mutex_unlock(&cli_mutex);
330 return 0;
331}
332
333int cli_help(char** args)
334{
335 pthread_mutex_lock(&cli_mutex);
336 if (args[0] != NULL)
337 {
338 if (args[1] != NULL && args[2] == NULL)
339 {
341 "Too many arguments for help command");
342 print_usage("help");
343 pthread_mutex_unlock(&cli_mutex);
344 return 1;
345 }
346 for (long unsigned i = 0; i < sizeof(cli_commands) / sizeof(cli_command); ++i)
347 {
348 if (strcmp(args[0], cli_commands[i].cli_command_name) == 0)
349 {
350 if (cli_commands[i].cli_command_detailed_desc)
351 guarded_print_line(cli_commands[i].cli_command_detailed_desc);
352 else
353 guarded_print_line(" * %s - Detailed information not included.",
354 args[0]);
355 pthread_mutex_unlock(&cli_mutex);
356 return 0;
357 }
358 }
359 guarded_print_line(" * %s - Unknown command.", args[0]);
360 }
361 else
362 print_help();
363 pthread_mutex_unlock(&cli_mutex);
364 return 0;
365}
366
367int cli_echo(char** args)
368{
369 pthread_mutex_lock(&cli_mutex);
370 if (args[0] == NULL)
371 {
373 "No arguments provided for echo command");
374 print_usage("echo");
375 pthread_mutex_unlock(&cli_mutex);
376 return 1;
377 }
378 for (int i = 0; args[i] != NULL; ++i)
379 guarded_print("%s ", args[i]);
380 guarded_print("\n");
381 pthread_mutex_unlock(&cli_mutex);
382 return 0;
383}
384
385int cli_whoami(char** args)
386{
387 pthread_mutex_lock(&cli_mutex);
388 bool full_information = false;
389 if (args[0] != NULL)
390 {
391 if (strcmp(args[0], "-f") == 0 || strcmp(args[0], "--full") == 0)
392 {
393 if (args[1] != NULL)
394 {
396 "Unknown argument for whoami command: \"%s\"", args[1]);
397 print_usage("whoami");
398 pthread_mutex_unlock(&cli_mutex);
399 return 1;
400 }
401 full_information = true;
402 }
403 else
404 {
406 "Unknown argument for whoami command: \"%s\"", args[0]);
407 print_usage("whoami");
408 pthread_mutex_unlock(&cli_mutex);
409 return 1;
410 }
411 }
412
413 if (!full_information)
414 guarded_print_line("%s", getenv("USER"));
415 else
416 {
417 char ip_address[BUFFER_SIZE];
418 get_local_ip_address(ip_address);
419
420 if (getenv("USER") == NULL)
421 guarded_print_line("Unknown user");
422 else if (strcmp(getenv("USER"), "root") == 0)
423 guarded_print_line("You are \033[1;31m%s\033[0m", getenv("USER"));
424 else
425 guarded_print_line("You are \033[1;34m%s\033[0m", getenv("USER"));
426
427 guarded_print_line("Local IP address: %s", ip_address);
428
429 get_remote_ip_address(ip_address);
430 guarded_print_line("Public IP address: %s", ip_address);
431 }
432
433 pthread_mutex_unlock(&cli_mutex);
434 return 0;
435}
436
437int cli_get_ip(char** args)
438{
439 pthread_mutex_lock(&cli_mutex);
440 char ip_address[BUFFER_SIZE];
441 if (args[0] == NULL)
442 {
443 get_remote_ip_address(ip_address);
444 guarded_print_line("Public IP address: %s", ip_address);
445 }
446 else
447 {
448 if (args[0] != NULL && args[1] == NULL)
449 {
450 if (is_valid_domain_address(args[0]))
451 {
452 lookup_address(args[0], ip_address);
453 guarded_print_line("IP address of %s: %s", args[0], ip_address);
454 }
455 else
456 guarded_print_line("Invalid domain address: %s", args[0]);
457 }
458 else
459 {
460 int i = 0;
461 while (args[i] != NULL)
462 {
463 ip_address[0] = '\0';
464 if (is_valid_domain_address(args[i]))
465 {
466 lookup_address(args[i], ip_address);
467 guarded_print_line("%d: IP address of %s: %s", i + 1, args[i],
468 ip_address);
469 }
470 else
471 guarded_print_line("%d: Invalid domain address: %s", i + 1,
472 args[i]);
473 i++;
474 }
475 }
476 }
477 pthread_mutex_unlock(&cli_mutex);
478 return 0;
479}
480
481int cli_info(char** args)
482{
483 pthread_mutex_lock(&cli_mutex);
484 if (args[0] != NULL)
485 {
487 "Unknown argument for info command: %s", args[0]);
488 print_usage("info");
489 pthread_mutex_unlock(&cli_mutex);
490 return 1;
491 }
492
493 guarded_print_line("BitLab v%s", BITLAB_VERSION);
494 guarded_print_line("Built on %s %s", __DATE__, __TIME__);
497 {
498 guarded_print_line("Peer discovery: active");
499
501 guarded_print_line("Peer discovery in progress: true");
502 else
503 guarded_print_line("Peer discovery in progress: false");
505 guarded_print_line("Peer discovery succeeded: true");
506 else
507 guarded_print_line("Peer discovery succeeded: false");
509 guarded_print_line("Peer discovery daemon: true");
510 else
511 guarded_print_line("Peer discovery daemon: false");
513 guarded_print_line("Peer discovery hardcoded seeds: true");
514 else
515 guarded_print_line("Peer discovery hardcoded seeds: false");
517 guarded_print_line("Peer discovery DNS lookup: true");
518 else
519 guarded_print_line("Peer discovery DNS lookup: false");
520 }
521 else
522 guarded_print_line("Peer discovery: inactive");
523
524 pthread_mutex_unlock(&cli_mutex);
525 return 0;
526}
527
528int cli_peer_discovery(char** args)
529{
530 pthread_mutex_lock(&cli_mutex);
531
532 // defaults
533 bool daemon = PEER_DISCOVERY_DEFAULT_DAEMON;
534 bool hardcoded_seeds = PEER_DISCOVERY_DEFAULT_HARDCODED_SEEDS;
535 bool dns_lookup = PEER_DISCOVERY_DEFAULT_DNS_LOOKUP;
536
537 bool daemon_set = false;
538 bool hardcoded_seeds_set = false;
539 bool dns_lookup_set = false;
540
541 // before checking program state, ensure command is only used with valid arguments to avoid confusion
542 if (args[0] != NULL)
543 {
544 for (int i = 0; args[i] != NULL; ++i)
545 {
546 if (strcmp(args[i], "-d") == 0 || strcmp(args[i], "--daemon") == 0)
547 {
548 if (daemon_set)
549 {
551 "Daemon flag already set for peerdiscovery command");
552 print_usage("peerdiscovery");
553 pthread_mutex_unlock(&cli_mutex);
554 return 1;
555 }
556 daemon = true;
557 daemon_set = true;
558 }
559 else if (strcmp(args[i], "-h") == 0 || strcmp(args[i], "--hardcoded") == 0)
560 {
561 if (hardcoded_seeds_set || dns_lookup_set)
562 {
564 "Hardcoded seeds or DNS lookup flag already set for peerdiscovery command");
565 print_usage("peerdiscovery");
566 pthread_mutex_unlock(&cli_mutex);
567 return 1;
568 }
569 hardcoded_seeds = true;
570 dns_lookup = false;
571
572 hardcoded_seeds_set = true;
573 }
574 else if (strcmp(args[i], "-l") == 0 || strcmp(args[i], "--dns-lookup") == 0)
575 {
576 if (dns_lookup_set || hardcoded_seeds_set)
577 {
579 "DNS lookup or hardcoded seeds flag already set for peerdiscovery command");
580 print_usage("peerdiscovery");
581 pthread_mutex_unlock(&cli_mutex);
582 return 1;
583 }
584
585 if (args[i + 1] == NULL)
587 "Missing domain argument for DNS lookup flag for peerdiscovery command, default will be used");
588 if (args[i + 2] != NULL)
589 {
591 "Too many arguments for DNS lookup flag for peerdiscovery command");
592 print_usage("peerdiscovery");
593 pthread_mutex_unlock(&cli_mutex);
594 return 1;
595 }
596
597 dns_lookup = true;
598 hardcoded_seeds = false;
599
600 dns_lookup_set = true;
601 }
602 else
603 {
604 if (dns_lookup_set)
605 {
608 "Set DNS domain for peerdiscovery command: %s",
609 args[i]);
610 else
611 {
613 "Failed to set DNS domain for peerdiscovery command");
614 pthread_mutex_unlock(&cli_mutex);
615 return 1;
616 }
617 }
618 else
619 {
621 "Unknown argument for peerdiscovery command: %s",
622 args[i]);
623 print_usage("peerdiscovery");
624 pthread_mutex_unlock(&cli_mutex);
625 return 1;
626 }
627 }
628 }
629 }
630
631 // process current state and provided arguments if state allows
633 {
634 if (daemon)
635 {
637 "Peer discovery already in progress");
639 "Peer discovery already in progress. Use \"peerdiscovery\" to check results later or \"info\" to see the status. Any additional arguments will be ignored until the results are cleared.");
640 pthread_mutex_unlock(&cli_mutex);
641 return 1;
642 }
643 else // wait for peer discovery to finish
644 {
646 "Connected to peer discovery daemon. Arguments ignored if provided. Waiting for results...");
648 usleep(100000); // 100 ms
649 usleep(1000000); // 1s
650 }
651 }
652 else
653 {
654 // check if previous peer discovery attempt failed
655 bool results_successful = get_peer_discovery_succeeded();
656 if (get_peer_discovery() && !results_successful)
658 "Peer discovery previous attempt failed. Allowing new attempt...");
659
660 if (!results_successful) // results does not exist or failed and are not cleared
661 {
662 // [ ] Add clearing results
663 // set states
664 set_peer_discovery(true);
666 set_peer_discovery_hardcoded_seeds(hardcoded_seeds);
668
669 if (daemon)
670 {
672 "Peer discovery in background");
674 "Peer discovery started as daemon. Use \"peerdiscovery\" to check results or \"info\" to see the status.");
675 pthread_mutex_unlock(&cli_mutex);
676 return 0;
677 }
678 else // wait for peer discovery to finish
679 {
680 guarded_print_line("Peer discovery started. Waiting for results...");
682 usleep(100000); // 100 ms
683 usleep(1000000); // 1s
684 }
685 }
686 }
687
689
690 pthread_mutex_unlock(&cli_mutex);
691 return 0;
692}
693
694int cli_ping(char** args)
695{
696 pthread_mutex_lock(&cli_mutex);
697 int count = 4;
698 int sleep_time = 1;
699 char ip_address[BUFFER_SIZE] = { 0 };
700
701 int i = 0;
702 while (args[i] != NULL)
703 {
704 if (strcmp(args[i], "-c") == 0 || strcmp(args[i], "--count") == 0)
705 {
706 if (args[i + 1] == NULL)
707 {
709 "Missing count value for ping command.");
710 print_usage("ping");
711 pthread_mutex_unlock(&cli_mutex);
712 return 1;
713 }
714
715 count = strtol(args[i + 1], NULL, 10);
716 if (count <= 0)
717 {
718 log_message(LOG_WARN, BITLAB_LOG, __FILE__, "Invalid count value: %s",
719 args[i + 1]);
720 print_usage("ping");
721 pthread_mutex_unlock(&cli_mutex);
722 return 1;
723 }
724 i += 2;
725 }
726 else if (ip_address[0] == '\0')
727 {
728 if (strlen(args[i]) >= BUFFER_SIZE)
729 {
731 "Invalid IP address for ping command.");
732 print_usage("ping");
733 pthread_mutex_unlock(&cli_mutex);
734 return 1;
735 }
736 strncpy(ip_address, args[i], BUFFER_SIZE - 1);
737 i++;
738 }
739 else
740 {
742 "Too many arguments for ping command: %s", args[i]);
743 print_usage("ping");
744 pthread_mutex_unlock(&cli_mutex);
745 return 1;
746 }
747 }
748
749 if (ip_address[0] == '\0')
750 {
752 "Missing IP address for ping command.");
753 print_usage("ping");
754 pthread_mutex_unlock(&cli_mutex);
755 return 1;
756 }
757
758 guarded_print_line("Pinging %s with count %d", ip_address, count);
759 char command[BUFFER_SIZE];
760 snprintf(command, sizeof(command), "ping -c %d -i %d %s", count, sleep_time,
761 ip_address);
762 system(command);
763
764 pthread_mutex_unlock(&cli_mutex);
765 return 0;
766}
767
768int cli_connect(char** args)
769{
770 pthread_mutex_lock(&cli_mutex);
771 if (args[0] == NULL)
772 {
774 "No arguments provided for connect command");
775 print_usage("connect");
776 pthread_mutex_unlock(&cli_mutex);
777 return 1;
778 }
779 if (args[1] != NULL)
780 {
782 "Too many arguments for connect command");
783 print_usage("connect");
784 pthread_mutex_unlock(&cli_mutex);
785 return 1;
786 }
787 char ip_address[BUFFER_SIZE];
788 if (is_numeric_address(args[0]))
789 {
790 strncpy(ip_address, args[0], BUFFER_SIZE - 1);
791 ip_address[BUFFER_SIZE - 1] = '\0';
792 guarded_print_line("Connecting to %s", ip_address);
793 connect_to_peer(ip_address);
794 }
795 else
797 "Connect command uses numeric address for peer connection. Supplied argument: %s",
798 args[0]);
799 pthread_mutex_unlock(&cli_mutex);
800 return 0;
801}
802
803
804int cli_getaddr(char** args)
805{
806 pthread_mutex_lock(&cli_mutex);
807 if (args[0] == NULL)
808 {
810 "No arguments provided for getaddr command");
811 print_usage("getaddr");
812 pthread_mutex_unlock(&cli_mutex);
813 return 1;
814 }
815 if (args[1] != NULL)
816 {
818 "Too many arguments for getaddr command");
819 print_usage("getaddr");
820 pthread_mutex_unlock(&cli_mutex);
821 return 1;
822 }
823 int idx = atoi(args[0]);
824 guarded_print_line("Sending getaddr to %d", idx);
826 pthread_mutex_unlock(&cli_mutex);
827 return 0;
828}
829
830int cli_disconnect(char** args)
831{
832 pthread_mutex_lock(&cli_mutex);
833 if (args[0] == NULL)
834 {
836 "No arguments provided for disconnect command");
837 print_usage("disconnect");
838 pthread_mutex_unlock(&cli_mutex);
839 return 1;
840 }
841 if (args[1] != NULL)
842 {
844 "Too many arguments for disconnect command");
845 print_usage("disconnect");
846 pthread_mutex_unlock(&cli_mutex);
847 return 1;
848 }
849 int idx = atoi(args[0]);
850 guarded_print_line("Disconnecting from node %d", idx);
851 disconnect(idx);
852 pthread_mutex_unlock(&cli_mutex);
853 return 0;
854}
855
856int cli_getheaders(char** args)
857{
858 pthread_mutex_lock(&cli_mutex);
859 if (args[0] == NULL)
860 {
862 "No arguments provided for getheaders command");
863 print_usage("getheaders");
864 pthread_mutex_unlock(&cli_mutex);
865 return 1;
866 }
867 if (args[1] != NULL)
868 {
870 "Too many arguments for getheaders command");
871 print_usage("getheaders");
872 pthread_mutex_unlock(&cli_mutex);
873 return 1;
874 }
875 int idx = atoi(args[0]);
876 guarded_print_line("Sending getheaders to %d", idx);
878 pthread_mutex_unlock(&cli_mutex);
879 return 0;
880}
881
882int cli_getblocks(char** args)
883{
884 pthread_mutex_lock(&cli_mutex);
885 if (args[0] == NULL)
886 {
888 "No arguments provided for getblocks command");
889 print_usage("getblocks");
890 pthread_mutex_unlock(&cli_mutex);
891 return 1;
892 }
893 if (args[1] != NULL)
894 {
896 "Too many arguments for getblocks command");
897 print_usage("getblocks");
898 pthread_mutex_unlock(&cli_mutex);
899 return 1;
900 }
901 int idx = atoi(args[0]);
902 guarded_print_line("Sending getblocks to %d", idx);
904 pthread_mutex_unlock(&cli_mutex);
905 return 0;
906}
907
908int cli_getdata(char** args)
909{
910 pthread_mutex_lock(&cli_mutex);
911 if (args[0] == NULL)
912 {
914 "No arguments provided for getdata command");
915 print_usage("getdata");
916 pthread_mutex_unlock(&cli_mutex);
917 return 1;
918 }
919
920 int idx = atoi(args[0]);
921 if (idx < 0 || idx >= MAX_NODES)
922 {
924 "Invalid node index for getdata command");
925 print_usage("getdata");
926 pthread_mutex_unlock(&cli_mutex);
927 return 1;
928 }
929
930 // Count the number of hashes provided
931 size_t hash_count = 0;
932 for (int i = 1; args[i] != NULL; i++)
933 {
934 hash_count++;
935 }
936
937 if (hash_count == 0)
938 {
940 "No hashes provided for getdata command");
941 print_usage("getdata");
942 pthread_mutex_unlock(&cli_mutex);
943 return 1;
944 }
945
946 if (hash_count > 50000)
947 {
949 "Too many hashes provided for getdata command");
950 print_usage("getdata");
951 pthread_mutex_unlock(&cli_mutex);
952 return 1;
953 }
954
955 // Allocate memory for the hashes
956 unsigned char* hashes = malloc(hash_count * 32);
957 if (hashes == NULL)
958 {
960 "Failed to allocate memory for hashes");
961 pthread_mutex_unlock(&cli_mutex);
962 return 1;
963 }
964
965 // Parse the hashes from the arguments
966 for (size_t i = 0; i < hash_count; i++)
967 {
968 if (strlen(args[i + 1]) != 64)
969 {
971 "Invalid hash length for getdata command");
972 print_usage("getdata");
973 free(hashes);
974 pthread_mutex_unlock(&cli_mutex);
975 return 1;
976 }
977
978 for (size_t j = 0; j < 32; j++)
979 {
980 sscanf(&args[i + 1][j * 2], "%2hhx", &hashes[i * 32 + j]);
981 }
982 }
983
984 guarded_print_line("Sending getdata to %d with %zu hashes", idx, hash_count);
985 send_getdata_and_wait(idx, hashes, hash_count);
986
987 free(hashes);
988 pthread_mutex_unlock(&cli_mutex);
989 return 0;
990}
991
992int cli_inv(char** args)
993{
994 pthread_mutex_lock(&cli_mutex);
995 if (args[0] == NULL)
996 {
998 "Insufficient arguments provided for inv command");
999 print_usage("inv");
1000 pthread_mutex_unlock(&cli_mutex);
1001 return 1;
1002 }
1003 if (args[1] != NULL)
1004 {
1005 log_message(LOG_WARN, BITLAB_LOG, __FILE__,
1006 "Too many arguments for inv command");
1007 print_usage("inv");
1008 pthread_mutex_unlock(&cli_mutex);
1009 return 1;
1010 }
1011
1012 int idx = atoi(args[0]);
1013
1014 // Load inventory data from blocks.dat
1015 size_t inv_len;
1016 unsigned char* inv_data = load_blocks_from_file("blocks.dat", &inv_len);
1017 if (!inv_data)
1018 {
1019 log_message(LOG_ERROR, BITLAB_LOG, __FILE__,
1020 "Failed to load inventory data from blocks.dat");
1021 pthread_mutex_unlock(&cli_mutex);
1022 return 1;
1023 }
1024
1025 size_t inv_count = inv_len / 36;
1026 printf("inv_len: %zu, inv_count: %zu\n", inv_len, inv_count);
1027
1028 // Send the 'inv' message
1029 send_inv_and_wait(idx, inv_data, inv_count);
1030
1031 free(inv_data);
1032 pthread_mutex_unlock(&cli_mutex);
1033 return 0;
1034}
1035
1036int cli_tx(char** args)
1037{
1038 pthread_mutex_lock(&cli_mutex);
1039 if (args[0] == NULL || args[1] == NULL)
1040 {
1041 log_message(LOG_WARN, BITLAB_LOG, __FILE__,
1042 "No arguments provided for tx command");
1043 print_usage("tx");
1044 pthread_mutex_unlock(&cli_mutex);
1045 return 1;
1046 }
1047
1048 int idx = atoi(args[0]);
1049 if (idx < 0 || idx >= MAX_NODES)
1050 {
1051 log_message(LOG_WARN, BITLAB_LOG, __FILE__,
1052 "Invalid node index for tx command");
1053 print_usage("tx");
1054 pthread_mutex_unlock(&cli_mutex);
1055 return 1;
1056 }
1057
1058 // Parse the transaction data from the arguments
1059 size_t tx_size = strlen(args[1]) / 2;
1060 unsigned char* tx_data = malloc(tx_size);
1061 if (tx_data == NULL)
1062 {
1063 log_message(LOG_ERROR, BITLAB_LOG, __FILE__,
1064 "Failed to allocate memory for transaction data");
1065 pthread_mutex_unlock(&cli_mutex);
1066 return 1;
1067 }
1068
1069 for (size_t i = 0; i < tx_size; i++)
1070 {
1071 sscanf(&args[1][i * 2], "%2hhx", &tx_data[i]);
1072 }
1073
1074 guarded_print_line("Sending transaction to %d with size %zu", idx, tx_size);
1075 send_tx(idx, tx_data, tx_size);
1076
1077 free(tx_data);
1078 pthread_mutex_unlock(&cli_mutex);
1079 return 0;
1080}
1081
1082int cli_list(char** args)
1083{
1084 pthread_mutex_lock(&cli_mutex);
1085 if (args[0] != NULL)
1086 {
1087 log_message(LOG_WARN, BITLAB_LOG, __FILE__,
1088 "Too many arguments for getaddr command");
1089 print_usage("getaddr");
1090 pthread_mutex_unlock(&cli_mutex);
1091 return 1;
1092 }
1094 pthread_mutex_unlock(&cli_mutex);
1095 return 0;
1096}
1097
1098int cli_clear(char** args)
1099{
1100 pthread_mutex_lock(&cli_mutex);
1101 if (args[0] != NULL)
1102 {
1103 log_message(LOG_WARN, BITLAB_LOG, __FILE__,
1104 "Unknown argument for clear command: %s", args[0]);
1105 print_usage("clear");
1106 pthread_mutex_unlock(&cli_mutex);
1107 return 1;
1108 }
1109 clear_cli();
1110 pthread_mutex_unlock(&cli_mutex);
1111 return 0;
1112}
1113
1114int cli_get_line(char** lineptr, size_t* n, FILE* stream)
1115{
1116 static char line[MAX_LINE_LEN];
1117 char* ptr;
1118 unsigned int len;
1119
1120 if (lineptr == NULL || n == NULL)
1121 {
1122 errno = EINVAL;
1123 return -1;
1124 }
1125
1126 if (ferror(stream))
1127 return -1;
1128
1129 if (feof(stream))
1130 return -1;
1131
1132 (void)fgets(line, MAX_LINE_LEN, stream);
1133
1134 ptr = strchr(line, '\n');
1135 if (ptr)
1136 *ptr = '\0';
1137
1138 len = strlen(line);
1139
1140 if ((len + 1) < MAX_LINE_LEN)
1141 {
1142 ptr = realloc(*lineptr, MAX_LINE_LEN);
1143 if (ptr == NULL)
1144 return (-1);
1145 *lineptr = ptr;
1146 *n = MAX_LINE_LEN;
1147 }
1148
1149 strcpy(*lineptr, line);
1150 return (len);
1151}
1152
1153char* cli_read_line(void)
1154{
1155 pthread_mutex_lock(&cli_mutex);
1156 char* line = readline(CLI_PREFIX);
1157 if (line && *line)
1158 add_history(line);
1159 pthread_mutex_unlock(&cli_mutex);
1160 return line;
1161}
1162
1163int cli_exec_line(char* line)
1164{
1165 int i = 0;
1166 int buffer_size = CLI_BUFSIZE;
1167 char** tokens = malloc(buffer_size * sizeof(char*));
1168 char* token;
1169
1170 char* command;
1171 char** args;
1172
1173 if (!tokens)
1174 {
1175 log_message(LOG_FATAL, BITLAB_LOG, __FILE__, "cli_exec_line: malloc error");
1176 exit(EXIT_FAILURE);
1177 }
1178
1179 token = strtok(line, CLI_DELIM);
1180 while (token != NULL)
1181 {
1182 tokens[i] = token;
1183 i++;
1184
1185 if (i >= buffer_size)
1186 {
1187 buffer_size += CLI_BUFSIZE;
1188 tokens = realloc(tokens, buffer_size * sizeof(char*));
1189 if (!tokens)
1190 {
1191 log_message(LOG_FATAL, BITLAB_LOG, __FILE__,
1192 "cli_exec_line: malloc error");
1193 exit(EXIT_FAILURE);
1194 }
1195 }
1196
1197 token = strtok(NULL, CLI_DELIM);
1198 }
1199 tokens[i] = NULL;
1200
1201 command = tokens[0];
1202 args = tokens + 1;
1203
1204 if (command == NULL)
1205 {
1206 free(tokens);
1207 return 1;
1208 }
1209
1210 for (i = 0; i < CLI_COMMANDS_NUM; ++i)
1211 {
1212 if (!strcmp(command, cli_commands[i].cli_command_name))
1213 {
1214 int result = (cli_commands[i].cli_command)(args);
1215 free(tokens);
1216 return result;
1217 }
1218 }
1219 guarded_print_line("Command not found! Type \"help\" to see available commands.");
1220 log_message(LOG_INFO, BITLAB_LOG, __FILE__, "Command not found: %s", command);
1221 free(tokens);
1222 return 1;
1223}
1224
1225char** cli_completion(const char* text, int start, int end)
1226{
1227 rl_attempted_completion_over = 1;
1228 start = start; // unused
1229 end = end; // unused
1230 char* line = rl_line_buffer;
1231 int spaces = 0;
1232
1233 // print help for empty input
1234 if (strlen(line) == 0)
1235 {
1236 guarded_print("\n");
1237 print_help();
1239 return NULL;
1240 }
1241
1242 // count spaces
1243 for (long unsigned i = 0; i < strlen(line); ++i)
1244 if (line[i] == ' ')
1245 spaces++;
1246
1247 // check if the input starts with "help" and allow for one space
1248 if (((strncmp(line, "help", 4) == 0) && spaces <= 1))
1249 return rl_completion_matches(text, cli_command_generator);
1250
1251 // complete first word
1252 if (spaces == 0)
1253 return rl_completion_matches(text, cli_command_generator);
1254
1255 return NULL;
1256}
1257
1258char* cli_command_generator(const char* text, int state)
1259{
1260 static int list_index, len;
1261 const char* name;
1262 if (!state)
1263 {
1264 list_index = 0;
1265 len = strlen(text);
1266 }
1267 while (list_index < CLI_COMMANDS_NUM)
1268 {
1269 name = cli_commands[list_index].cli_command_name;
1270 list_index++;
1271
1272 if (strncmp(name, text, len) == 0)
1273 return strdup(name);
1274 }
1275 return NULL;
1276}
1277
1278void* handle_cli(void* arg)
1279{
1280 char* line = NULL;
1282 struct stat st = { 0 };
1283
1284 if (stat(cli_history_dir, &st) == -1 && mkdir(cli_history_dir, 0700) != 0)
1285 log_message(LOG_WARN, BITLAB_LOG, __FILE__,
1286 "Failed to create history directory");
1287
1288 char full_path[BUFFER_SIZE];
1289 snprintf(full_path, sizeof(full_path), "%s/%s", cli_history_dir, CLI_HISTORY_FILE);
1290 read_history(full_path);
1291 usleep(50000); // 50 ms
1292
1293 rl_basic_word_break_characters = " \t\n\"\\'`@$><=;|&{(";
1294 rl_completer_word_break_characters = " \t\n\"\\'`@$><=;|&{(";
1295 rl_completer_quote_characters = "\"\\'`";
1296 rl_completion_append_character = '\0';
1297 rl_attempted_completion_over = 1;
1298 rl_attempted_completion_function = cli_completion;
1299 rl_getc_function = getc;
1300 rl_catch_signals = 0;
1301
1302 while (!get_exit_flag())
1303 {
1304 line = cli_read_line();
1305 if (line && *line)
1306 {
1307 cli_exec_line(line);
1308 write_history(full_path);
1309 }
1310 if (line)
1311 {
1312 free(line);
1313 line = NULL;
1314 }
1315 }
1316 if (arg)
1317 {
1318 } // dummy code to avoid unused parameter warning
1319 if (cli_history_dir != NULL)
1320 {
1321 free((void*)cli_history_dir);
1322 cli_history_dir = NULL;
1323 }
1324 log_message(LOG_INFO, BITLAB_LOG, __FILE__, "Exiting CLI thread");
1325 pthread_exit(NULL);
1326}
1327
1329{
1330 const char* home = getenv("HOME");
1331 if (home == NULL)
1332 return NULL;
1333 const char* suffix = "/.bitlab/history";
1334 char* logs_dir = malloc(strlen(home) + strlen(suffix) + 1);
1335 if (logs_dir == NULL)
1336 return NULL;
1337 strcpy(logs_dir, home);
1338 strcat(logs_dir, suffix);
1339 return logs_dir;
1340}
char * cli_command_generator(const char *text, int state)
CLI command generator.
Definition cli.c:1258
void print_usage(const char *command_name)
Prints CLI command usage.
Definition cli.c:262
void print_commands()
Prints CLI commands.
Definition cli.c:275
int cli_history(char **args)
Prints CLI command history.
Definition cli.c:312
const char * create_history_dir()
Create history directory.
Definition cli.c:1328
int cli_clear(char **args)
Clears CLI window.
Definition cli.c:1098
int cli_help(char **args)
Prints CLI command help.
Definition cli.c:333
static cli_command cli_commands[]
Definition cli.c:22
int cli_whoami(char **args)
Prints the user name.
Definition cli.c:385
char ** cli_completion(const char *text, int start, int end)
CLI completion function.
Definition cli.c:1225
int cli_peer_discovery(char **args)
Discovers Bitcoin peers.
Definition cli.c:528
int cli_getdata(char **args)
Sends a 'getdata' message to specified peer.
Definition cli.c:908
int cli_tx(char **args)
Sends a 'tx' message to specified peer.
Definition cli.c:1036
static const char * cli_history_dir
Definition cli.c:16
int cli_connect(char **args)
Connects to the specified IP address.
Definition cli.c:768
char * cli_read_line(void)
Reads input from user.
Definition cli.c:1153
int cli_disconnect(char **args)
Disconnects from the specified node.
Definition cli.c:830
void * handle_cli(void *arg)
CLI handler thread.
Definition cli.c:1278
void print_help()
Prints CLI command help.
Definition cli.c:171
int cli_get_line(char **lineptr, size_t *n, FILE *stream)
Gets the line from the file stream.
Definition cli.c:1114
int cli_getblocks(char **args)
Sends a 'getblocks' message to specified peer.
Definition cli.c:882
int cli_info(char **args)
Prints program information.
Definition cli.c:481
int cli_get_ip(char **args)
Gets remote IP address on an URL or host machine if no URL is provided.
Definition cli.c:437
int cli_getheaders(char **args)
Sends a 'getheaders' message to specified peer.
Definition cli.c:856
int cli_getaddr(char **args)
Sends a 'getaddr' message to the specified peer.
Definition cli.c:804
int cli_inv(char **args)
Sends a 'inv' message to specified peer.
Definition cli.c:992
int cli_exit(char **args)
Exits BitLab CLI command.
Definition cli.c:281
int cli_exec_line(char *line)
Execute command.
Definition cli.c:1163
int cli_echo(char **args)
Echoes the input.
Definition cli.c:367
int cli_ping(char **args)
Pings the specified IP address.
Definition cli.c:694
pthread_mutex_t cli_mutex
Definition cli.c:19
int cli_list(char **args)
Lists all connected nodes.
Definition cli.c:1082
#define MAX_LINE_LEN
Definition cli.h:7
#define CLI_BUFSIZE
Definition cli.h:8
#define CLI_COMMANDS_NUM
Definition cli.h:10
#define CLI_DELIM
Definition cli.h:9
#define CLI_PREFIX
Definition cli.h:12
#define CLI_HISTORY_FILE
Definition cli.h:11
void get_local_ip_address(char *ip_addr)
Get the local IP address of the machine (e.g.
Definition ip.c:11
int lookup_address(const char *lookup_addr, char *ip_addr)
Perform lookup of given IP address by the domain address (e.g.
Definition ip.c:55
int is_numeric_address(const char *ip_addr)
Check if the IP address is a numeric address (e.g.
Definition ip.c:119
int is_valid_domain_address(const char *domain_addr)
Check if the IP address is a valid domain address (e.g.
Definition ip.c:136
void get_remote_ip_address(char *ip_addr)
Get the remote IP address of the machine (e.g.
Definition ip.c:33
static const char * logs_dir
Definition log.c:18
void log_message(log_level level, const char *filename, const char *source_file, const char *format,...)
Log a message used to log a message to the console or a file.
Definition log.c:89
#define BITLAB_LOG
Definition log.h:11
@ LOG_ERROR
Definition log.h:32
@ LOG_INFO
Definition log.h:30
@ LOG_FATAL
Definition log.h:33
@ LOG_WARN
Definition log.h:31
void list_connected_nodes()
Lists all connected nodes and their details.
void send_inv_and_wait(int idx, const unsigned char *inv_data, size_t inv_count)
Sends an 'inv' message to the peer and waits for a response.
void send_getheaders_and_wait(int idx)
Sends a 'getheaders' message to the peer and waits for a response.
void send_getblocks_and_wait(int idx)
Sends a 'getblocks' message to the peer and waits for a response.
unsigned char * load_blocks_from_file(const char *filename, size_t *payload_len)
void send_tx(int idx, const unsigned char *tx_data, size_t tx_size)
Sends a 'tx' message to the specified node.
void send_getaddr_and_wait(int idx)
Sends a 'getaddr' message to the peer and waits for a response.
#define MAX_NODES
int connect_to_peer(const char *ip_addr)
Connects to a peer using the specified IP address.
void disconnect(int node_id)
Disconnects from the node specified by the node ID.
void send_getdata_and_wait(int idx, const unsigned char *hashes, size_t hash_count)
Sends a 'getdata' message to the peer and waits for a response.
void print_peer_queue()
Print the peer queue.
Definition peer_queue.c:103
bool set_peer_discovery_daemon(bool value)
Set the peer discovery daemon state.
Definition state.c:174
bool get_peer_discovery()
Get the peer discovery operation.
Definition state.c:150
bool set_peer_discovery_hardcoded_seeds(bool value)
Set the peer discovery hardcoded seeds state.
Definition state.c:182
bool get_peer_discovery_in_progress()
Get the peer discovery in progress state.
Definition state.c:158
bool set_peer_discovery_dns_lookup(bool value)
Set the peer discovery DNS lookup state.
Definition state.c:190
bool set_peer_discovery_dns_domain(const char *domain)
Set the peer discovery DNS domain.
Definition state.c:222
void print_program_state()
Print the program state.
Definition state.c:63
program_state state
The program state used to store the state of the program.
Definition state.c:19
void set_exit_flag(volatile sig_atomic_t flag)
Set the exit flag.
Definition state.c:73
bool get_peer_discovery_succeeded()
Get the peer discovery succeeded state.
Definition state.c:166
#define PEER_DISCOVERY_DEFAULT_DNS_LOOKUP
Definition state.h:8
#define BITLAB_VERSION
Definition state.h:4
program_operation operation
The program operation used to store all current operations of the program.
Definition state.c:40
bool set_peer_discovery(bool value)
Set the peer discovery operation.
Definition state.c:125
sig_atomic_t get_exit_flag()
Get the exit flag.
Definition state.c:80
#define PEER_DISCOVERY_DEFAULT_DAEMON
Definition state.h:6
#define PEER_DISCOVERY_DEFAULT_HARDCODED_SEEDS
Definition state.h:7
CLI command structure.
Definition cli.h:24
const char * cli_command_brief_desc
Definition cli.h:27
int(* cli_command)(char **args)
Definition cli.h:25
const char * cli_command_usage
Definition cli.h:29
const char * cli_command_name
Definition cli.h:26
bool peer_discovery_succeeded
Definition state.h:51
bool peer_discovery_in_progress
Definition state.h:50
bool peer_discovery_daemon
Definition state.h:52
bool peer_discovery_dns_lookup
Definition state.h:54
bool peer_discovery_hardcoded_seeds
Definition state.h:53
bool peer_discovery
Definition state.h:49
char * strdup(const char *str1)
#define BUFFER_SIZE
Definition utils.h:12
void clear_cli()
Clear the CLI window.
Definition utils.c:33
void guarded_print_line(const char *format,...)
Guarded print line function.
Definition utils.c:65
void usleep(unsigned int usec)
void guarded_print(const char *format,...)
Guarded print function.
Definition utils.c:55
char * strtok(char *str, const char *delimiters)