mirror of https://github.com/OpenIPC/firmware.git
Add files via upload
parent
82e3ff9c82
commit
41caa7017b
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue