Add files via upload

pull/1724/head
snokvist 2025-02-19 21:06:39 +01:00 committed by GitHub
parent 82e3ff9c82
commit 41caa7017b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 1498 additions and 0 deletions

View File

@ -0,0 +1,658 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <stdarg.h>
#include <sys/wait.h>
#include <stdint.h>
#define DEFAULT_SERVER_IP "10.5.99.2"
#define DEFAULT_SERVER_PORT 5555
#define BUFFER_SIZE 8192
#define DEFAULT_LISTEN_DURATION 60 // seconds
// Define directories and file paths for BIND and FLASH commands.
#define BIND_DIR "/tmp/bind"
#define BIND_FILE "/tmp/bind/bind.tar.gz"
#define FLASH_DIR "/tmp/flash"
#define FLASH_FILE "/tmp/flash/flash.tar.gz"
// Exit code definitions.
#define EXIT_ERR 1
#define EXIT_BIND 2
#define EXIT_UNBIND 3
#define EXIT_FLASH 4
#define EXIT_BACKUP 5 // For BACKUP command
// Global flag for debug output.
static int debug_enabled = 0;
// Global bandwidth limit in bits/second (default: 1,000,000 bits/s = 1 Mbit/s).
static double g_bw_limit_bits = 1000000.0;
/*--------------------------------------------------
* Helper Functions
*--------------------------------------------------*/
// Print debug messages if debug is enabled.
void debug_print(const char *fmt, ...) {
if (!debug_enabled)
return;
va_list args;
va_start(args, fmt);
fprintf(stderr, "DEBUG: ");
vfprintf(stderr, fmt, args);
va_end(args);
}
// Print usage help.
void print_help() {
fprintf(stderr, "Usage: wfb_bind_rcv [OPTIONS]\n");
fprintf(stderr, "Options:\n");
fprintf(stderr, " --ip <address> Set server IP address (default: %s)\n", DEFAULT_SERVER_IP);
fprintf(stderr, " --port <number> Set server port (default: %d)\n", DEFAULT_SERVER_PORT);
fprintf(stderr, " --listen-duration <sec> Set duration to listen (default: %d seconds)\n", DEFAULT_LISTEN_DURATION);
fprintf(stderr, " --force-listen Continue listening even after a terminating command\n");
fprintf(stderr, " --bw-limit <bits/s> Bandwidth limit in bits/sec (default: 1000000)\n");
fprintf(stderr, " --debug Enable debug output\n");
fprintf(stderr, " --help Show this help message\n");
}
// Ensure that a directory exists.
void ensure_directory(const char *dir) {
struct stat st = {0};
if (stat(dir, &st) == -1) {
if (mkdir(dir, 0777) != 0) {
fprintf(stderr, "ERR\tFailed to create directory: %s\n", dir);
exit(EXIT_ERR);
}
}
}
// Read entire file into a dynamically allocated buffer (including newlines).
char *read_file(const char *filename) {
FILE *fp = fopen(filename, "r");
if (!fp) {
return strdup("Failed to read file");
}
fseek(fp, 0, SEEK_END);
long fsize = ftell(fp);
fseek(fp, 0, SEEK_SET);
char *buffer = malloc(fsize + 1);
if (!buffer) {
fclose(fp);
return strdup("Memory allocation error");
}
size_t nread = fread(buffer, 1, fsize, fp);
fclose(fp);
buffer[nread] = '\0';
return buffer;
}
// Base64 decode the input string and write the decoded data to a file.
int base64_decode_and_save_to(const char *input, size_t input_length, const char *dir, const char *file) {
ensure_directory(dir);
FILE *output_file = fopen(file, "wb");
if (!output_file) {
fprintf(stderr, "ERR\tFailed to open output file: %s\n", file);
return 1;
}
unsigned char decode_buffer[BUFFER_SIZE];
int val = 0, valb = -8;
size_t out_len = 0;
for (size_t i = 0; i < input_length; i++) {
char c = input[i];
if (c == '=' || c == '\n' || c == '\r')
continue;
char *pos = strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", c);
if (pos == NULL)
continue;
val = (val << 6) + (pos - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
valb += 6;
if (valb >= 0) {
decode_buffer[out_len++] = (val >> valb) & 0xFF;
valb -= 8;
}
if (out_len >= BUFFER_SIZE) {
fwrite(decode_buffer, 1, out_len, output_file);
out_len = 0;
}
}
if (out_len > 0) {
fwrite(decode_buffer, 1, out_len, output_file);
}
fclose(output_file);
return 0;
}
// Return elapsed time (in seconds) since 'start', using monotonic clock.
static double elapsed_time_sec(const struct timespec *start) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
double seconds = (double)(now.tv_sec - start->tv_sec);
double nsecs = (double)(now.tv_nsec - start->tv_nsec) / 1e9;
return seconds + nsecs;
}
// Bandwidth-limited write: writes data in chunks, sleeping if we exceed bits/sec.
static void throttle_write(FILE *fp, const char *data, size_t data_len, double bits_per_sec) {
const size_t chunk_size = 4096;
struct timespec start;
clock_gettime(CLOCK_MONOTONIC, &start);
double bytes_per_sec = bits_per_sec / 8.0;
size_t total_sent = 0;
while (total_sent < data_len) {
size_t remain = data_len - total_sent;
size_t send_now = (remain < chunk_size) ? remain : chunk_size;
size_t n = fwrite(data + total_sent, 1, send_now, fp);
if (n < send_now) {
// If we failed to write the full chunk, likely connection closed or error
total_sent += n;
break;
}
total_sent += n;
fflush(fp); // ensure timely sending
// Calculate how much we've sent vs. how much we *should* have sent
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
double elapsed = (double)(now.tv_sec - start.tv_sec) +
(double)(now.tv_nsec - start.tv_nsec) / 1e9;
double allowed_bytes = bytes_per_sec * elapsed;
if (total_sent > allowed_bytes) {
double excess_bytes = total_sent - allowed_bytes;
double wait_time = excess_bytes / bytes_per_sec; // in seconds
if (wait_time > 0) {
usleep((useconds_t)(wait_time * 1e6));
}
}
}
}
// Execute a system command and capture its output in a dynamically allocated string.
char *execute_command(const char *cmd) {
FILE *fp = popen(cmd, "r");
if (!fp)
return NULL;
size_t size = 4096;
char *output = malloc(size);
if (!output) {
pclose(fp);
return NULL;
}
output[0] = '\0';
size_t len = 0;
char buffer[1024];
while (fgets(buffer, sizeof(buffer), fp)) {
size_t buffer_len = strlen(buffer);
if (len + buffer_len + 1 > size) {
size = (len + buffer_len + 1) * 2;
char *temp = realloc(output, size);
if (!temp) {
free(output);
pclose(fp);
return NULL;
}
output = temp;
}
strcpy(output + len, buffer);
len += buffer_len;
}
pclose(fp);
return output;
}
// Base64 encode a given binary buffer.
char *base64_encode(const unsigned char *data, size_t input_length) {
static const char encoding_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const int mod_table[] = {0, 2, 1};
size_t output_length = 4 * ((input_length + 2) / 3);
char *encoded_data = malloc(output_length + 1);
if (!encoded_data)
return NULL;
size_t i, j;
for (i = 0, j = 0; i < input_length;) {
uint32_t octet_a = i < input_length ? data[i++] : 0;
uint32_t octet_b = i < input_length ? data[i++] : 0;
uint32_t octet_c = i < input_length ? data[i++] : 0;
uint32_t triple = (octet_a << 16) | (octet_b << 8) | (octet_c);
encoded_data[j++] = encoding_table[(triple >> 18) & 0x3F];
encoded_data[j++] = encoding_table[(triple >> 12) & 0x3F];
encoded_data[j++] = encoding_table[(triple >> 6) & 0x3F];
encoded_data[j++] = encoding_table[triple & 0x3F];
}
// Pad as needed
for (i = 0; i < (size_t)mod_table[input_length % 3]; i++) {
encoded_data[output_length - 1 - i] = '=';
}
encoded_data[output_length] = '\0';
return encoded_data;
}
/*--------------------------------------------------
* Command Handler Declarations
*--------------------------------------------------*/
typedef int (*command_handler)(const char *arg, FILE *client_file, int force_listen);
/*
* Each command handler sends a reply to the connected peer.
* If the command should cause the program to terminate (and force_listen is false),
* the handler returns the exit code to use (nonzero). Otherwise, it returns 0.
*/
// VERSION: reply with version info.
int cmd_version(const char *arg, FILE *client_file, int force_listen) {
(void)arg;
(void)force_listen;
fprintf(client_file, "OK\tOpenIPC bind v0.1\n");
fflush(client_file);
return 0;
}
// BIND: decode base64 input and save to BIND_FILE.
int cmd_bind(const char *arg, FILE *client_file, int force_listen) {
if (arg == NULL || strlen(arg) == 0) {
fprintf(client_file, "ERR\tMissing argument for BIND command\n");
fflush(client_file);
return 0;
}
debug_print("Received BIND command with base64 length: %zu\n", strlen(arg));
if (base64_decode_and_save_to(arg, strlen(arg), BIND_DIR, BIND_FILE) == 0) {
fprintf(client_file, "OK\n");
fflush(client_file);
if (!force_listen)
return EXIT_BIND;
} else {
fprintf(client_file, "ERR\tFailed to process data for BIND\n");
fflush(client_file);
}
return 0;
}
// FLASH: decode base64 input and save to FLASH_FILE.
int cmd_flash(const char *arg, FILE *client_file, int force_listen) {
if (arg == NULL || strlen(arg) == 0) {
fprintf(client_file, "ERR\tMissing argument for FLASH command\n");
fflush(client_file);
return 0;
}
debug_print("Received FLASH command with base64 length: %zu\n", strlen(arg));
if (base64_decode_and_save_to(arg, strlen(arg), FLASH_DIR, FLASH_FILE) == 0) {
fprintf(client_file, "OK\n");
fflush(client_file);
if (!force_listen)
return EXIT_FLASH;
} else {
fprintf(client_file, "ERR\tFailed to process data for FLASH\n");
fflush(client_file);
}
return 0;
}
// UNBIND: execute the system command "firstboot".
int cmd_unbind(const char *arg, FILE *client_file, int force_listen) {
(void)arg;
debug_print("Received UNBIND command\n");
int ret = system("firstboot");
if (ret == -1) {
fprintf(client_file, "ERR\tFailed to execute UNBIND command\n");
} else if (WIFEXITED(ret) && WEXITSTATUS(ret) == 0) {
fprintf(client_file, "OK\tUNBIND executed successfully\n");
fflush(client_file);
if (!force_listen)
return EXIT_UNBIND;
} else {
fprintf(client_file, "ERR\tUNBIND command returned error code %d\n", WEXITSTATUS(ret));
}
fflush(client_file);
return 0;
}
/*
* INFO: now we only read the file /etc/vtx_info.yaml.
* We preserve newlines as they are read, base64-encode them directly,
* and then send them with bandwidth throttling if needed.
*/
int cmd_info(const char *arg, FILE *client_file, int force_listen) {
(void)arg;
(void)force_listen;
debug_print("Received INFO command\n");
// Read content from /etc/vtx_info.yaml directly
char *info_content = read_file("/etc/vtx_info.yaml");
if (!info_content) {
info_content = strdup("Failed to read /etc/vtx_info.yaml");
}
// Base64-encode the raw data (including any newlines)
char *encoded_response = base64_encode((unsigned char*)info_content, strlen(info_content));
if (encoded_response) {
size_t enc_len = strlen(encoded_response);
fprintf(client_file, "OK\t");
fflush(client_file);
// Throttle the base64 data
throttle_write(client_file, encoded_response, enc_len, g_bw_limit_bits);
fprintf(client_file, "\n");
fflush(client_file);
free(encoded_response);
} else {
fprintf(client_file, "ERR\tFailed to encode response\n");
fflush(client_file);
}
free(info_content);
return 0;
}
// BACKUP command - unchanged from previous, with throttled output if large
int cmd_backup(const char *arg, FILE *client_file, int force_listen) {
(void)arg; // BACKUP has no base64 input
debug_print("Received BACKUP command\n");
// 1. Run generate_backup.sh
int ret = system("generate_backup.sh");
if (ret == -1) {
fprintf(client_file, "ERR\tFailed to execute generate_backup.sh\n");
fflush(client_file);
return 0; // continue listening
}
if (!WIFEXITED(ret) || WEXITSTATUS(ret) != 0) {
fprintf(client_file, "ERR\tgenerate_backup.sh returned error code %d\n", WEXITSTATUS(ret));
fflush(client_file);
return 0;
}
// 2. Check if /tmp/backup/backup.tar.gz exists
struct stat st;
if (stat("/tmp/backup/backup.tar.gz", &st) != 0) {
fprintf(client_file, "ERR\tBackup file not found\n");
fflush(client_file);
return 0;
}
// 3. Read the file into memory (binary)
FILE *fp = fopen("/tmp/backup/backup.tar.gz", "rb");
if (!fp) {
fprintf(client_file, "ERR\tFailed to open /tmp/backup/backup.tar.gz\n");
fflush(client_file);
return 0;
}
unsigned char *buffer = malloc(st.st_size);
if (!buffer) {
fclose(fp);
fprintf(client_file, "ERR\tMemory allocation error\n");
fflush(client_file);
return 0;
}
size_t read_count = fread(buffer, 1, st.st_size, fp);
fclose(fp);
if (read_count != (size_t)st.st_size) {
free(buffer);
fprintf(client_file, "ERR\tFailed to read backup file\n");
fflush(client_file);
return 0;
}
// 4. Base64-encode the file contents
char *encoded_data = base64_encode(buffer, read_count);
free(buffer);
if (!encoded_data) {
fprintf(client_file, "ERR\tBase64 encoding failed\n");
fflush(client_file);
return 0;
}
// 5. Respond with "OK\t" then throttled base64 data, then newline
fprintf(client_file, "OK\t");
fflush(client_file);
throttle_write(client_file, encoded_data, strlen(encoded_data), g_bw_limit_bits);
fprintf(client_file, "\n");
fflush(client_file);
free(encoded_data);
// 6. If not force_listen, terminate with EXIT_BACKUP
if (!force_listen) {
return EXIT_BACKUP; // = 5
}
return 0;
}
/*--------------------------------------------------
* Command Dispatch
*--------------------------------------------------*/
typedef struct {
const char *name;
command_handler handler;
} command_entry;
command_entry commands[] = {
{ "VERSION", cmd_version },
{ "BIND", cmd_bind },
{ "FLASH", cmd_flash },
{ "UNBIND", cmd_unbind },
{ "INFO", cmd_info }, // updated to read /etc/vtx_info.yaml
{ "BACKUP", cmd_backup },
{ NULL, NULL } // sentinel
};
/*
* Dispatch a command based on the command lookup table.
* Returns a nonzero exit code if the command requests termination; otherwise returns 0.
*/
int handle_command(const char *cmd, const char *arg, FILE *client_file, int force_listen) {
for (int i = 0; commands[i].name != NULL; i++) {
if (strcmp(cmd, commands[i].name) == 0) {
return commands[i].handler(arg, client_file, force_listen);
}
}
fprintf(client_file, "ERR\tUnknown command\n");
fflush(client_file);
return 0;
}
/*--------------------------------------------------
* Main
*--------------------------------------------------*/
int main(int argc, char *argv[]) {
int server_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int listen_duration = DEFAULT_LISTEN_DURATION;
char server_ip[INET_ADDRSTRLEN] = DEFAULT_SERVER_IP;
int server_port = DEFAULT_SERVER_PORT;
int force_listen = 0; // Default: terminate on a successful terminating command.
int exit_code = 0;
int command_terminated = 0;
// Parse command-line arguments.
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--help") == 0) {
print_help();
return 0;
} else if (strcmp(argv[i], "--ip") == 0 && i + 1 < argc) {
strncpy(server_ip, argv[i + 1], INET_ADDRSTRLEN);
i++;
} else if (strcmp(argv[i], "--port") == 0 && i + 1 < argc) {
server_port = atoi(argv[i + 1]);
i++;
} else if (strcmp(argv[i], "--listen-duration") == 0 && i + 1 < argc) {
listen_duration = atoi(argv[i + 1]);
if (listen_duration <= 0) {
fprintf(stderr, "ERR\tInvalid listen duration\n");
exit(EXIT_ERR);
}
i++;
} else if (strcmp(argv[i], "--force-listen") == 0) {
force_listen = 1;
} else if (strcmp(argv[i], "--debug") == 0) {
debug_enabled = 1;
} else if (strcmp(argv[i], "--bw-limit") == 0 && i + 1 < argc) {
// parse bandwidth limit in bits/s
g_bw_limit_bits = atof(argv[i + 1]);
if (g_bw_limit_bits < 1.0) {
// enforce minimal limit if invalid
g_bw_limit_bits = 1.0;
}
i++;
} else {
fprintf(stderr, "ERR\tInvalid argument: %s\n", argv[i]);
exit(EXIT_ERR);
}
}
fprintf(stderr, "INFO\tStarting server on %s:%d for %d seconds\n",
server_ip, server_port, listen_duration);
fprintf(stderr, "INFO\tBandwidth limit: %.0f bits/s\n", g_bw_limit_bits);
// Ensure directories for BIND and FLASH exist (created unconditionally).
ensure_directory(BIND_DIR);
ensure_directory(FLASH_DIR);
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("Socket creation failed");
exit(EXIT_ERR);
}
int opt = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("setsockopt(SO_REUSEADDR) failed");
close(server_fd);
exit(EXIT_ERR);
}
int flags = fcntl(server_fd, F_GETFL, 0);
if (flags == -1) {
perror("fcntl(F_GETFL) failed");
close(server_fd);
exit(EXIT_ERR);
}
if (fcntl(server_fd, F_SETFL, flags | O_NONBLOCK) == -1) {
perror("fcntl(F_SETFL, O_NONBLOCK) failed");
close(server_fd);
exit(EXIT_ERR);
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(server_ip);
server_addr.sin_port = htons(server_port);
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("Binding failed");
close(server_fd);
exit(EXIT_ERR);
}
if (listen(server_fd, 5) == -1) {
perror("Listening failed");
close(server_fd);
exit(EXIT_ERR);
}
struct timespec start_time;
clock_gettime(CLOCK_MONOTONIC, &start_time);
while (1) {
double diff = elapsed_time_sec(&start_time);
if (diff >= listen_duration) {
fprintf(stderr, "INFO\tListen duration expired\n");
break;
}
if (command_terminated) {
fprintf(stderr, "INFO\tA command requested termination\n");
break;
}
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_addr_len);
if (client_fd == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
usleep(100000);
continue;
} else {
perror("Accept failed");
usleep(100000);
continue;
}
}
fprintf(stderr, "INFO\tClient connected\n");
{
int client_flags = fcntl(client_fd, F_GETFL, 0);
if (client_flags != -1) {
client_flags &= ~O_NONBLOCK;
fcntl(client_fd, F_SETFL, client_flags);
}
}
FILE *client_file = fdopen(client_fd, "r+");
if (!client_file) {
perror("fdopen failed");
close(client_fd);
continue;
}
char *line = NULL;
size_t linecap = 0;
while (getline(&line, &linecap, client_file) != -1) {
size_t len = strlen(line);
/* Remove any trailing newline and carriage return characters */
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) {
line[len - 1] = '\0';
len--;
}
char *cmd = line;
char *arg = NULL;
char *sep = strpbrk(line, " \t");
if (sep != NULL) {
*sep = '\0';
arg = sep + 1;
while (*arg == ' ' || *arg == '\t')
arg++;
if (*arg == '\0')
arg = NULL;
}
int ret = handle_command(cmd, arg, client_file, force_listen);
if (ret != 0) {
exit_code = ret;
command_terminated = 1;
break;
}
}
free(line);
fclose(client_file);
fprintf(stderr, "INFO\tClient disconnected\n");
if (command_terminated)
break;
}
close(server_fd);
exit(exit_code);
}

View File

@ -0,0 +1,221 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char **argv)
{
if (argc < 4) {
fprintf(stderr, "Usage:\n");
fprintf(stderr, " TCP mode: %s <port> <address> <command> [args...]\n", argv[0]);
fprintf(stderr, " UDP mode: %s --udp <port> <command> [args...]\n", argv[0]);
exit(EXIT_FAILURE);
}
// Check for UDP mode
if (strcmp(argv[1], "--udp") == 0) {
// UDP mode: argv[2] is the port, argv[3] is the command
int port = atoi(argv[2]);
// Create UDP socket
int udp_sock;
if ((udp_sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("UDP socket creation failed");
exit(EXIT_FAILURE);
}
int opt = 1;
if (setsockopt(udp_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("setsockopt SO_REUSEADDR failed");
close(udp_sock);
exit(EXIT_FAILURE);
}
struct sockaddr_in udp_addr;
memset(&udp_addr, 0, sizeof(udp_addr));
udp_addr.sin_family = AF_INET;
udp_addr.sin_addr.s_addr = INADDR_ANY; // Bind to 0.0.0.0
udp_addr.sin_port = htons(port);
if (bind(udp_sock, (struct sockaddr *)&udp_addr, sizeof(udp_addr)) < 0) {
perror("UDP bind failed");
close(udp_sock);
exit(EXIT_FAILURE);
}
fprintf(stderr, "Listening for UDP packets on 0.0.0.0:%d\n", port);
// Create pipes for redirecting child's stdin and capturing child's stdout.
// pipe_in: parent writes UDP data -> child's stdin.
// pipe_out: child writes stdout -> parent reads and prints.
int pipe_in[2], pipe_out[2];
if (pipe(pipe_in) < 0) {
perror("pipe for stdin failed");
close(udp_sock);
exit(EXIT_FAILURE);
}
if (pipe(pipe_out) < 0) {
perror("pipe for stdout failed");
close(udp_sock);
exit(EXIT_FAILURE);
}
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
close(udp_sock);
exit(EXIT_FAILURE);
} else if (pid == 0) {
// Child process
// Close the parent's ends of the pipes.
close(pipe_in[1]);
close(pipe_out[0]);
// Replace child's stdin with pipe_in[0]
if (dup2(pipe_in[0], STDIN_FILENO) < 0) {
perror("dup2 for stdin failed");
exit(EXIT_FAILURE);
}
// Replace child's stdout with pipe_out[1]
if (dup2(pipe_out[1], STDOUT_FILENO) < 0) {
perror("dup2 for stdout failed");
exit(EXIT_FAILURE);
}
// Optionally also send stderr to stdout.
dup2(pipe_out[1], STDERR_FILENO);
// Close the now duplicated descriptors.
close(pipe_in[0]);
close(pipe_out[1]);
// Execute the requested command.
// In UDP mode, the command starts at argv[3].
execvp(argv[3], &argv[3]);
perror("execvp failed");
exit(EXIT_FAILURE);
}
// Parent process: close the child's ends of the pipes.
close(pipe_in[0]);
close(pipe_out[1]);
// Use select() to multiplex between the UDP socket and the child's output.
fd_set readfds;
int maxfd = (udp_sock > pipe_out[0]) ? udp_sock : pipe_out[0];
while (1) {
FD_ZERO(&readfds);
FD_SET(udp_sock, &readfds);
FD_SET(pipe_out[0], &readfds);
int ret = select(maxfd + 1, &readfds, NULL, NULL, NULL);
if (ret < 0) {
if (errno == EINTR)
continue;
perror("select failed");
break;
}
// Check for incoming UDP packets.
if (FD_ISSET(udp_sock, &readfds)) {
char buffer[4096];
struct sockaddr_in sender_addr;
socklen_t sender_len = sizeof(sender_addr);
ssize_t recv_len = recvfrom(udp_sock, buffer, sizeof(buffer), 0,
(struct sockaddr *)&sender_addr, &sender_len);
if (recv_len < 0) {
perror("recvfrom failed");
} else {
// Forward the UDP data to the child's stdin.
ssize_t written = write(pipe_in[1], buffer, recv_len);
if (written < 0) {
perror("write to child's stdin failed");
}
}
}
// Check if there is output from the child.
if (FD_ISSET(pipe_out[0], &readfds)) {
char outbuf[4096];
ssize_t count = read(pipe_out[0], outbuf, sizeof(outbuf));
if (count < 0) {
perror("read from child's stdout failed");
} else if (count == 0) {
// End-of-file: child closed its stdout.
break;
} else {
// Print child's output to stdout.
if (write(STDOUT_FILENO, outbuf, count) < 0) {
perror("write to stdout failed");
}
}
}
}
// Cleanup: close file descriptors and wait for child termination.
close(pipe_in[1]);
close(pipe_out[0]);
close(udp_sock);
wait(NULL);
exit(EXIT_SUCCESS);
} else {
// TCP mode: Expect arguments: <port> <address> <command> [args...]
if (argc < 4) {
fprintf(stderr, "Usage: %s <port> <address> <command> [args...]\n", argv[0]);
exit(EXIT_FAILURE);
}
int port = atoi(argv[1]);
int sock_fd, conn_fd;
struct sockaddr_in server_addr, peer_addr;
socklen_t peer_addr_len = sizeof(peer_addr);
if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
int opt = 1;
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(argv[2]);
server_addr.sin_port = htons(port);
if (bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
close(sock_fd);
exit(EXIT_FAILURE);
}
if (listen(sock_fd, 1) < 0) {
perror("listen failed");
close(sock_fd);
exit(EXIT_FAILURE);
}
fprintf(stderr, "Waiting for connection on %s:%d\n", argv[2], port);
if ((conn_fd = accept(sock_fd, (struct sockaddr *)&peer_addr, &peer_addr_len)) < 0) {
perror("accept failed");
exit(EXIT_FAILURE);
}
fprintf(stderr, "Connection accepted\n");
close(sock_fd);
// Replace stdin and stdout with the accepted connection.
dup2(conn_fd, STDIN_FILENO);
dup2(conn_fd, STDOUT_FILENO);
close(conn_fd);
execvp(argv[3], argv + 3);
perror("execvp failed");
exit(EXIT_FAILURE);
}
}

View File

@ -0,0 +1,619 @@
/*
* YAML Configurator
*
* This program parses a subset of YAML configuration files and supports:
*
* 1. Mappings and nested mappings (indented key/value pairs)
* Example:
* network:
* wifi:
* ssid: MyNetwork
* password: secret
*
* 2. Sequences both dash notation and inline lists
* Dash notation:
* ports:
* - 80
* - 443
*
* Inline list:
* ports_inline: [80,443,8080]
*
* 3. Inline mappings
* Example:
* credentials: {user:admin,password:secret}
*
* 4. Block scalar literals (using the '|' notation)
* Example:
* description: |
* This is a multi-line
* description text.
*
* Command-line arguments:
* -i <file> Specify the YAML file to parse.
* -g <key> Get the value at the dot-separated key path.
* If the node is a container, its entire inline
* representation is printed (e.g. [1,2,3]).
* -s <key> <value> Set value at the dot-separated key path using inline style for lists.
* (e.g. -s list.key "[1,2,3]" saves the list inline)
* -S <key> <value> Set value at the dot-separated key path using dash notation for lists.
* -d <key> Delete the node at the dot-separated key path.
*
* When using -s/-S and -d, changes are saved back to the file.
* Leading dots (e.g. ".fpv.enabled") are allowed.
*
* If no operation (-g, -s, -S or -d) is specified, a sanity check is performed:
* the YAML file is parsed and the complete structure is dumped in a pretty format.
*
* Example usage:
* ./configurator -i config.yaml
* ./configurator -i config.yaml -g network.wifi.ssid
* ./configurator -i config.yaml -s credentials.password newsecret
* ./configurator -i config.yaml -S list.key "[1,2,3,4]" (dash notation for lists)
* ./configurator -i config.yaml -d network.ethernet
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <unistd.h>
typedef enum {
YAML_NODE_SCALAR,
YAML_NODE_MAPPING,
YAML_NODE_SEQUENCE
} YAMLNodeType;
typedef struct YAMLNode {
char *key; /* For mapping nodes; NULL for sequence items */
char *value; /* For scalar values */
YAMLNodeType type;
struct YAMLNode **children;
size_t num_children;
int force_inline; /* For sequences: if nonzero, dump inline as [a,b,c] */
} YAMLNode;
YAMLNode *current_block_literal = NULL;
int block_literal_base_indent = -1;
YAMLNode *create_node(const char *key, const char *value) {
YAMLNode *node = malloc(sizeof(YAMLNode));
if (!node) { perror("malloc"); exit(EXIT_FAILURE); }
node->key = key ? strdup(key) : NULL;
node->value = value ? strdup(value) : NULL;
node->type = (value ? YAML_NODE_SCALAR : YAML_NODE_MAPPING);
node->children = NULL;
node->num_children = 0;
node->force_inline = 0;
return node;
}
void add_child(YAMLNode *parent, YAMLNode *child) {
parent->children = realloc(parent->children, sizeof(YAMLNode*) * (parent->num_children + 1));
if (!parent->children) { perror("realloc"); exit(EXIT_FAILURE); }
parent->children[parent->num_children++] = child;
}
void free_node(YAMLNode *node) {
if (!node) return;
if (node->key) free(node->key);
if (node->value) free(node->value);
for (size_t i = 0; i < node->num_children; i++) {
free_node(node->children[i]);
}
free(node->children);
free(node);
}
/* Parse an inline sequence of the form "[item1,item2,...]". */
YAMLNode *parse_inline_sequence(const char *str) {
YAMLNode *node = create_node(NULL, NULL);
node->type = YAML_NODE_SEQUENCE;
node->force_inline = 1; /* Inline parsed list defaults to inline style */
size_t len = strlen(str);
if (len < 2) return node;
char *inner = strndup(str + 1, len - 2);
char *token, *saveptr;
token = strtok_r(inner, ",", &saveptr);
while (token) {
while (*token && isspace((unsigned char)*token)) token++;
char *end = token + strlen(token) - 1;
while (end > token && isspace((unsigned char)*end)) { *end = '\0'; end--; }
YAMLNode *child = create_node(NULL, token);
child->type = YAML_NODE_SCALAR;
add_child(node, child);
token = strtok_r(NULL, ",", &saveptr);
}
free(inner);
return node;
}
/* Parse an inline mapping of the form "{key1:value1,key2:value2,...}".
This parser avoids splitting on commas that are inside inline sequences. */
YAMLNode *parse_inline_mapping(const char *str) {
YAMLNode *node = create_node(NULL, NULL);
node->type = YAML_NODE_MAPPING;
size_t len = strlen(str);
if (len < 2) return node;
char *inner = strndup(str + 1, len - 2);
int inner_len = strlen(inner);
int token_start = 0, bracket_level = 0;
for (int i = 0; i <= inner_len; i++) {
char c = inner[i];
if (c == '[') { bracket_level++; }
else if (c == ']') { if (bracket_level > 0) bracket_level--; }
if ((c == ',' && bracket_level == 0) || c == '\0') {
int token_len = i - token_start;
if (token_len > 0) {
char *pair = strndup(inner + token_start, token_len);
char *colon = strchr(pair, ':');
if (colon) {
*colon = '\0';
char *k = pair;
char *v = colon + 1;
while (*k && isspace((unsigned char)*k)) k++;
char *kend = k + strlen(k) - 1;
while (kend >= k && isspace((unsigned char)*kend)) { *kend = '\0'; kend--; }
while (*v && isspace((unsigned char)*v)) v++;
char *vend = v + strlen(v) - 1;
while (vend >= v && isspace((unsigned char)*vend)) { *vend = '\0'; vend--; }
YAMLNode *child = NULL;
if (v[0] == '[') {
child = parse_inline_sequence(v);
free(child->key);
child->key = strdup(k);
} else if (v[0] == '{') {
child = parse_inline_mapping(v);
free(child->key);
child->key = strdup(k);
} else {
child = create_node(k, v);
}
child->type = (child->value ? YAML_NODE_SCALAR : YAML_NODE_MAPPING);
add_child(node, child);
}
free(pair);
}
token_start = i + 1;
}
}
free(inner);
return node;
}
/* Print a YAML node inline to the specified file pointer.
Scalars print their value; sequences print as "[item,item,...]"; mappings as "{key:value,...}". */
void print_inline_yaml(FILE *f, const YAMLNode *node) {
if (!node) return;
if (node->type == YAML_NODE_SCALAR) {
if (node->value)
fprintf(f, "%s", node->value);
} else if (node->type == YAML_NODE_SEQUENCE) {
fprintf(f, "[");
for (size_t i = 0; i < node->num_children; i++) {
print_inline_yaml(f, node->children[i]);
if (i < node->num_children - 1)
fprintf(f, ",");
}
fprintf(f, "]");
} else if (node->type == YAML_NODE_MAPPING) {
fprintf(f, "{");
for (size_t i = 0; i < node->num_children; i++) {
if (node->children[i]->key)
fprintf(f, "%s:", node->children[i]->key);
print_inline_yaml(f, node->children[i]);
if (i < node->num_children - 1)
fprintf(f, ",");
}
fprintf(f, "}");
}
}
/* Print inline representation to stdout (for -g). */
void print_inline(const YAMLNode *node) {
print_inline_yaml(stdout, node);
}
/* Pretty-print the entire YAML tree (used for sanity checking). */
void print_yaml(const YAMLNode *node, int depth) {
for (int i = 0; i < depth; i++) printf(" ");
if (node->key)
printf("%s: ", node->key);
if (node->value)
printf("%s", node->value);
if (node->num_children > 0)
printf(" {%s}", (node->type == YAML_NODE_SEQUENCE ? "sequence" : "mapping"));
printf("\n");
for (size_t i = 0; i < node->num_children; i++) {
print_yaml(node->children[i], depth + 1);
}
}
/* Standard parse_line() that updates the in-memory tree from one line of YAML. */
void parse_line(const char *line, int indent, YAMLNode *current_parent, int line_number) {
if (line[0] == '-') {
const char *value_start = line + 1;
while (*value_start == ' ') value_start++;
YAMLNode *node = create_node(NULL, value_start);
node->type = YAML_NODE_SCALAR;
if (current_parent->type != YAML_NODE_SEQUENCE)
current_parent->type = YAML_NODE_SEQUENCE;
add_child(current_parent, node);
} else {
const char *colon = strchr(line, ':');
if (!colon) {
fprintf(stderr, "Error at line %d: Missing ':' in mapping: %s\n", line_number, line);
exit(EXIT_FAILURE);
}
size_t key_len = colon - line;
char *key = strndup(line, key_len);
const char *val_start = colon + 1;
while (*val_start == ' ') val_start++;
YAMLNode *node = NULL;
if (*val_start != '\0') {
if (strcmp(val_start, "|") == 0) {
node = create_node(key, "");
node->type = YAML_NODE_SCALAR;
current_block_literal = node;
block_literal_base_indent = indent + 1;
} else if (val_start[0] == '[') {
node = parse_inline_sequence(val_start);
free(node->key);
node->key = strdup(key);
} else if (val_start[0] == '{') {
node = parse_inline_mapping(val_start);
free(node->key);
node->key = strdup(key);
} else {
node = create_node(key, val_start);
}
} else {
node = create_node(key, NULL);
node->type = YAML_NODE_MAPPING;
}
add_child(current_parent, node);
free(key);
}
}
void parse_yaml(FILE *f, YAMLNode *root) {
char line[1024];
YAMLNode *stack[10] = { 0 };
int current_level = 0;
int line_number = 0;
stack[0] = root;
while (fgets(line, sizeof(line), f)) {
line_number++;
line[strcspn(line, "\n")] = '\0';
int line_indent = 0;
while (line[line_indent] == ' ') line_indent++;
if (current_block_literal) {
if (line_indent >= block_literal_base_indent) {
char *text = line + block_literal_base_indent;
char *old_val = current_block_literal->value;
char *new_val = NULL;
asprintf(&new_val, "%s%s\n", old_val ? old_val : "", text);
free(current_block_literal->value);
current_block_literal->value = new_val;
continue;
} else {
current_block_literal = NULL;
}
}
if (line[0] == '\0' || line[0] == '#')
continue;
int level = line_indent / 2;
if (level > current_level) {
current_level = level;
stack[current_level] = stack[current_level - 1]->children[stack[current_level - 1]->num_children - 1];
} else if (level < current_level) {
current_level = level;
}
YAMLNode *current_parent = stack[current_level];
parse_line(line + line_indent, line_indent, current_parent, line_number);
}
}
YAMLNode *find_node(YAMLNode *node, const char *path) {
while (*path == '.') { path++; }
char *path_copy = strdup(path);
char *token, *saveptr;
token = strtok_r(path_copy, ".", &saveptr);
YAMLNode *current = node;
while (token && current) {
if (current->type != YAML_NODE_MAPPING) { current = NULL; break; }
int found = 0;
for (size_t i = 0; i < current->num_children; i++) {
if (current->children[i]->key && strcmp(current->children[i]->key, token) == 0) {
current = current->children[i];
found = 1;
break;
}
}
if (!found) { current = NULL; break; }
token = strtok_r(NULL, ".", &saveptr);
}
free(path_copy);
return current;
}
YAMLNode *find_or_create_node(YAMLNode *node, const char *path) {
while (*path == '.') { path++; }
char *path_copy = strdup(path);
char *token, *saveptr;
token = strtok_r(path_copy, ".", &saveptr);
YAMLNode *current = node;
while (token) {
YAMLNode *child = NULL;
for (size_t i = 0; i < current->num_children; i++) {
if (current->children[i]->key && strcmp(current->children[i]->key, token) == 0) {
child = current->children[i];
break;
}
}
if (!child) {
child = create_node(token, NULL);
child->type = YAML_NODE_MAPPING;
add_child(current, child);
}
current = child;
token = strtok_r(NULL, ".", &saveptr);
}
free(path_copy);
return current;
}
int delete_node_at_path(YAMLNode *node, const char *path) {
while (*path == '.') { path++; }
char *path_copy = strdup(path);
char *token, *saveptr;
token = strtok_r(path_copy, ".", &saveptr);
YAMLNode *current = node;
YAMLNode *parent = NULL;
char *last_token = NULL;
while (token && current) {
parent = current;
last_token = token;
token = strtok_r(NULL, ".", &saveptr);
if (token) {
int found = 0;
for (size_t i = 0; i < parent->num_children; i++) {
if (parent->children[i]->key && strcmp(parent->children[i]->key, last_token) == 0) {
current = parent->children[i];
found = 1;
break;
}
}
if (!found) { free(path_copy); return 0; }
}
}
if (!parent || !last_token) { free(path_copy); return 0; }
for (size_t i = 0; i < parent->num_children; i++) {
if (parent->children[i]->key && strcmp(parent->children[i]->key, last_token) == 0) {
free_node(parent->children[i]);
for (size_t j = i; j < parent->num_children - 1; j++) {
parent->children[j] = parent->children[j + 1];
}
parent->num_children--;
parent->children = realloc(parent->children, sizeof(YAMLNode*) * parent->num_children);
free(path_copy);
return 1;
}
}
free(path_copy);
return 0;
}
/* Dump a YAML node to file.
* For a sequence: if force_inline is true, dump it inline ([a,b,c]);
* otherwise, dump using dash notation.
*/
void dump_yaml_node(FILE *f, const YAMLNode *node, int indent) {
if (indent == 0 && node->key && strcmp(node->key, "root") == 0) {
for (size_t i = 0; i < node->num_children; i++) {
dump_yaml_node(f, node->children[i], indent);
}
return;
}
for (int i = 0; i < indent; i++) fputc(' ', f);
if (node->key)
fprintf(f, "%s:", node->key);
if (node->value) {
if (strchr(node->value, '\n')) {
fprintf(f, " |\n");
char *val_copy = strdup(node->value);
char *line = strtok(val_copy, "\n");
while (line) {
for (int i = 0; i < indent + 2; i++) fputc(' ', f);
fprintf(f, "%s\n", line);
line = strtok(NULL, "\n");
}
free(val_copy);
} else {
fprintf(f, " %s", node->value);
}
}
if (node->num_children > 0) {
if (node->type == YAML_NODE_SEQUENCE && node->force_inline) {
fprintf(f, " ");
print_inline_yaml(f, node);
fprintf(f, "\n");
return;
}
fprintf(f, "\n");
if (node->type == YAML_NODE_MAPPING) {
for (size_t i = 0; i < node->num_children; i++) {
dump_yaml_node(f, node->children[i], indent + 2);
}
} else if (node->type == YAML_NODE_SEQUENCE) {
for (size_t i = 0; i < node->num_children; i++) {
for (int j = 0; j < indent + 2; j++) fputc(' ', f);
fprintf(f, "- ");
if (node->children[i]->value && node->children[i]->num_children == 0) {
fprintf(f, "%s\n", node->children[i]->value);
} else {
fprintf(f, "\n");
dump_yaml_node(f, node->children[i], indent + 4);
}
}
}
} else {
fprintf(f, "\n");
}
}
void save_yaml(const char *filename, const YAMLNode *root) {
FILE *f = fopen(filename, "w");
if (!f) { perror("fopen for writing"); exit(EXIT_FAILURE); }
dump_yaml_node(f, root, 0);
fclose(f);
}
void handle_signal(int sig) {
fprintf(stderr, "\nReceived signal %d, exiting gracefully...\n", sig);
exit(EXIT_FAILURE);
}
void usage(const char *progname) {
fprintf(stderr, "Usage: %s -i <file> [ -g <key> | -s <key> <value> | -S <key> <value> | -d <key> ]\n", progname);
fprintf(stderr, "Options:\n");
fprintf(stderr, " -i <file> YAML file to parse\n");
fprintf(stderr, " -g <key> Get value at the dot-separated key path (outputs inline representation)\n");
fprintf(stderr, " -s <key> <value> Set value at the dot-separated key path using inline style for lists\n");
fprintf(stderr, " (e.g. -s list.key \"[1,2,3]\" saves the list inline)\n");
fprintf(stderr, " -S <key> <value> Set value at the dot-separated key path using dash notation for lists\n");
fprintf(stderr, " -d <key> Delete node at the dot-separated key path\n");
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[]) {
if (argc == 1) {
usage(argv[0]);
}
signal(SIGINT, handle_signal);
char *filename = NULL, *get_path = NULL, *set_path = NULL, *set_value = NULL, *delete_path = NULL;
int opt;
int set_dash = 0; /* 0 = inline style (-s), 1 = dash notation (-S) */
while ((opt = getopt(argc, argv, "i:g:s:S:d:")) != -1) {
switch (opt) {
case 'i':
filename = optarg;
break;
case 'g':
get_path = optarg;
break;
case 's':
set_path = optarg;
if (optind < argc) {
set_value = argv[optind++];
} else {
fprintf(stderr, "Error: -s requires a key and a value.\n");
usage(argv[0]);
}
set_dash = 0;
break;
case 'S':
set_path = optarg;
if (optind < argc) {
set_value = argv[optind++];
} else {
fprintf(stderr, "Error: -S requires a key and a value.\n");
usage(argv[0]);
}
set_dash = 1;
break;
case 'd':
delete_path = optarg;
break;
default:
usage(argv[0]);
}
}
if (!filename) {
fprintf(stderr, "Error: No input file specified.\n");
usage(argv[0]);
}
FILE *f = fopen(filename, "r");
if (!f) { perror("fopen"); exit(EXIT_FAILURE); }
YAMLNode *root = create_node("root", NULL);
root->type = YAML_NODE_MAPPING;
parse_yaml(f, root);
fclose(f);
if (get_path) {
YAMLNode *node = find_node(root, get_path);
if (node) {
if (node->type == YAML_NODE_SCALAR && node->value)
printf("%s\n", node->value);
else {
print_inline(node);
printf("\n");
}
fflush(stdout);
} else {
printf("Node not found.\n");
}
} else if (set_path) {
YAMLNode *node = find_or_create_node(root, set_path);
if (node) {
if (set_value[0] == '[') {
if (node->value) { free(node->value); node->value = NULL; }
for (size_t i = 0; i < node->num_children; i++) {
free_node(node->children[i]);
}
free(node->children);
YAMLNode *new_list = parse_inline_sequence(set_value);
node->children = new_list->children;
node->num_children = new_list->num_children;
node->type = YAML_NODE_SEQUENCE;
node->force_inline = (set_dash ? 0 : 1);
new_list->children = NULL;
free(new_list);
} else if (set_value[0] == '{') {
if (node->value) { free(node->value); node->value = NULL; }
for (size_t i = 0; i < node->num_children; i++) {
free_node(node->children[i]);
}
free(node->children);
YAMLNode *new_map = parse_inline_mapping(set_value);
node->children = new_map->children;
node->num_children = new_map->num_children;
node->type = YAML_NODE_MAPPING;
new_map->children = NULL;
free(new_map);
} else {
if (node->value) free(node->value);
for (size_t i = 0; i < node->num_children; i++) {
free_node(node->children[i]);
}
free(node->children);
node->children = NULL;
node->num_children = 0;
node->value = strdup(set_value);
node->type = YAML_NODE_SCALAR;
}
printf("Value set at '%s'.\n", set_path);
save_yaml(filename, root);
printf("Changes saved to file '%s'.\n", filename);
} else {
printf("Could not set value at '%s'.\n", set_path);
}
} else if (delete_path) {
int result = delete_node_at_path(root, delete_path);
if (result) {
printf("Node '%s' deleted.\n", delete_path);
save_yaml(filename, root);
printf("Changes saved to file '%s'.\n", filename);
} else {
printf("Node '%s' not found.\n", delete_path);
}
} else {
/* Sanity check: no operation specified, so dump the parsed structure */
printf("YAML configuration parsed successfully. Dumping structure:\n");
print_yaml(root, 0);
}
free_node(root);
return EXIT_SUCCESS;
}