Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Align examples and remove reading from stdin #255

Merged
merged 22 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5464c80
Remove reading from stdin, align example implementations
oteffahi Feb 22, 2024
482ec03
Add argument parsing implementation for examples
oteffahi Mar 21, 2024
2ab6dc0
Add argument parsing to examples, format files
oteffahi Mar 21, 2024
f8ecc57
Replace getchar with sleep in z_pong example
oteffahi Mar 21, 2024
c7eb3a0
Fix typo in include
oteffahi Mar 22, 2024
d16cba6
Use null-pointers instead of empty strings, remove unnecessary mallocs
oteffahi Mar 22, 2024
dc3ec9c
Free returned pointer after parse_pos_args usage
oteffahi Mar 22, 2024
d022944
Add common and positional args parsing to z_ping example
oteffahi Mar 22, 2024
e4fcb3c
Add formatting for parsed config options
oteffahi Mar 22, 2024
1396838
Add const to function parameters
oteffahi Mar 22, 2024
bd8c0ec
Update mode option help
oteffahi Mar 22, 2024
e8fddc9
Fix pos_args memory leak
oteffahi Mar 28, 2024
6fd4091
Refactor parse_args, remove possible strcpy buffer overflow
oteffahi Mar 28, 2024
184ea9f
Change parse_args function returns to const where applicable
oteffahi Apr 3, 2024
5846a79
Fix const initialization warning
oteffahi Apr 5, 2024
00a54cf
Remove redundant const for value parameters
oteffahi Apr 5, 2024
333839c
Fix buf variable memory leak
oteffahi Apr 12, 2024
bc16f57
Update insert json-list config error message
oteffahi Apr 12, 2024
bdfb131
Add usage example for -e and -l arguments in help
oteffahi Apr 12, 2024
c386aa8
Update example notation in help message
oteffahi Apr 12, 2024
46b82d8
Update example notation in help message (2/2)
oteffahi Apr 12, 2024
70ac694
Fix parameter in error message
oteffahi Apr 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 213 additions & 0 deletions examples/parse_args.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
//
// Copyright (c) 2024 ZettaScale Technology
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
//
// Contributors:
// ZettaScale Zenoh Team, <zenoh@zettascale.tech>
//

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "zenoh.h"

#define COMMON_HELP \
"\
-c <CONFIG> (optional, string): The path to a configuration file for the session. If this option isn't passed, the default configuration will be used.\n\
-m <MODE> (optional, string, default='peer'): The zenoh session mode. [possible values: peer, client, router]\n\
-e <CONNECT> (optional, string): endpoint to connect to. Repeat option to pass multiple endpoints. If none are given, endpoints will be discovered through multicast-scouting if it is enabled.\n\
-l <LISTEN> (optional, string): locator to listen on. Repeat option to pass multiple locators. If none are given, the default configuration will be used.\n\
--no-multicast-scouting (optional): By default zenohd replies to multicast scouting messages for being discovered by peers and clients. This option disables this feature.\n\
"

/**
* Parse an option of format `-f`, `--flag`, `-f <value>` or `--flag <value>` from `argv`. If found, the option and its
* eventual value are each replaced by NULL in `argv`
* @param argc: argc passed from `main` function
* @param argv: argv passed from `main` function
* @param opt: option to parse (without `-` or `--` prefix)
* @param opt_has_value: if true, the option is of format `-f <value>` or `--flag <value>` and `value` will be returned
* if found, else an error message is printed and program will exit. If false, option has no value and a non-null
* pointer will be returned if option is found.
* @returns NULL if option was not found, else a non-null value depending on if `opt_has_value`.
*/
oteffahi marked this conversation as resolved.
Show resolved Hide resolved
char* parse_opt(const int argc, char** argv, const char* opt, const bool opt_has_value) {
oteffahi marked this conversation as resolved.
Show resolved Hide resolved
size_t optlen = strlen(opt);
for (int i = 1; i < argc; i++) {
if (argv[i] == NULL) {
continue;
}
size_t len = strlen(argv[i]);
if (len >= 2) {
oteffahi marked this conversation as resolved.
Show resolved Hide resolved
if (optlen == 1) {
if (argv[i][0] == '-' && argv[i][1] == opt[0]) {
argv[i] = NULL;
if (!opt_has_value) {
return (char*)opt;
} else if (i + 1 < argc && argv[i + 1]) {
char* value = argv[i + 1];
argv[i + 1] = NULL;
return value;
} else {
printf("Option -%s given without a value\n", opt);
exit(-1);
}
}
} else if (optlen > 1 && len > 3 && argv[i][0] == '-' && argv[i][1] == '-') {
// Note: support for '--arg=<value>' syntax can be added here
if (strcmp(argv[i] + 2, opt) == 0) {
argv[i] = NULL;
if (!opt_has_value) {
return (char*)opt;
} else if (i + 1 < argc && argv[i + 1]) {
char* value = argv[i + 1];
argv[i + 1] = NULL;
return value;
} else {
printf("Option --%s given without a value\n", opt);
exit(-1);
}
}
}
}
}
return NULL;
}

/**
* Check if any options remains in `argv`. Must be called after all expected options are parsed
* @param argc
* @param argv
* @returns NULL if no option was found, else the first option string that was found
*/
char* check_unknown_opts(const int argc, char** argv) {
for (int i = 1; i < argc; i++) {
if (argv[i] && argv[i][0] == '-') {
return argv[i];
}
}
return NULL;
}

/**
* Parse positional arguments from `argv`. Must be called after all expected options are parsed, and after checking that
* no unknown options remain in `argv`
* @param argc
* @param argv
* @param nb_args: number of expected positional arguments
* @returns NULL if found more positional arguments than `nb_args`. Else an array of found arguments in order, followed
* by NULL values if found less positional arguments than `nb_args`
* @note Returned pointer is dynamically allocated and must be freed
*/
oteffahi marked this conversation as resolved.
Show resolved Hide resolved
char** parse_pos_args(const int argc, char** argv, const size_t nb_args) {
char** pos_argv = (char**)malloc(nb_args * sizeof(char*));
// Initialize pointers to NULL to detect when example is called with number of args < nb_args
for (int i = 0; i < nb_args; i++) {
pos_argv[i] = NULL;
}
oteffahi marked this conversation as resolved.
Show resolved Hide resolved
size_t pos_argc = 0;
for (int i = 1; i < argc; i++) {
if (argv[i]) {
pos_argc++;
if (pos_argc > nb_args) {
free(pos_argv);
return NULL;
}
pos_argv[pos_argc - 1] = argv[i];
}
}
return pos_argv;
}

/**
* Parse zenoh options that require a JSON-serialized list (-e, -l from common args) and add them to
* `config`. Prints error message and exits if fails to insert parsed values
* @param argc
* @param argv
* @param opt: option to parse (without `-` or `--` prefix)
* @param config: address of an owned zenoh configuration
* @param config_key: zenoh configuration key under which the parsed values will be inserted
*/
void parse_zenoh_json_list_config(const int argc, char** argv, const char* opt, const char* config_key,
const z_owned_config_t* config) {
char buf[256] = "";
char* value = parse_opt(argc, argv, opt, true);
while (value) {
size_t len_format_value = strlen(value) + 4; // value + quotes + comma + nullbyte
char* format_value = (char*)malloc(len_format_value);
snprintf(format_value, len_format_value, "'%s',", value);
strcat(buf, format_value);
oteffahi marked this conversation as resolved.
Show resolved Hide resolved
free(format_value);
value = parse_opt(argc, argv, opt, true);
}
size_t buflen = strlen(buf);
if (buflen > 0) {
// remove trailing comma
buf[buflen - 1] = '\0';
buflen--;
// add list delimiters
size_t json_list_len = buflen + 3; // buf + brackets + nullbyte
char* json_list = (char*)malloc(json_list_len);
snprintf(json_list, json_list_len, "[%s]", buf);
// insert in config
if (zc_config_insert_json(z_loan(*config), config_key, json_list) < 0) {
oteffahi marked this conversation as resolved.
Show resolved Hide resolved
printf(
"Couldn't insert value `%s` in configuration at `%s`. This is likely because `%s` expects a "
"JSON-serialized list of strings\n",
json_list, config_key, config_key);
free(json_list);
exit(-1);
}
free(json_list);
}
oteffahi marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Parse zenoh options that are common to all examples (-c, -m, -e, -l, --no-multicast-scouting) and add them to
* `config`
* @param argc
* @param argv
* @param config: address of an owned zenoh configuration
*/
void parse_zenoh_common_args(const int argc, char** argv, z_owned_config_t* config) {
// -c: A configuration file.
char* config_file = parse_opt(argc, argv, "c", true);
if (config_file) {
oteffahi marked this conversation as resolved.
Show resolved Hide resolved
*config = zc_config_from_file(config_file);
}
// -m: The Zenoh session mode [default: peer].
char* mode = parse_opt(argc, argv, "m", true);
if (mode) {
size_t buflen = strlen(mode) + 3; // mode + quotes + nullbyte
char* buf = (char*)malloc(buflen);
snprintf(buf, buflen, "'%s'", mode);
if (zc_config_insert_json(z_loan(*config), Z_CONFIG_MODE_KEY, buf) < 0) {
printf(
"Couldn't insert value `%s` in configuration at `%s`. Value must be one of: 'client', 'peer' or "
"'router'\n",
mode, Z_CONFIG_MODE_KEY);
free(buf);
exit(-1);
}
free(buf);
}
// -e: Endpoint to connect to. Can be repeated
parse_zenoh_json_list_config(argc, argv, "e", Z_CONFIG_CONNECT_KEY, config);
// -l: Endpoint to listen on. Can be repeated
parse_zenoh_json_list_config(argc, argv, "l", Z_CONFIG_LISTEN_KEY, config);
// --no-multicast-scrouting: Disable the multicast-based scouting mechanism.
char* no_multicast_scouting = parse_opt(argc, argv, "no-multicast-scouting", false);
if (no_multicast_scouting && zc_config_insert_json(z_loan(*config), Z_CONFIG_MULTICAST_SCOUTING_KEY, "false") < 0) {
printf("Couldn't disable multicast-scouting.\n");
exit(-1);
}
}
61 changes: 47 additions & 14 deletions examples/z_delete.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,19 @@
#include <stdio.h>
#include <string.h>

#include "parse_args.h"
#include "zenoh.h"

int main(int argc, char **argv) {
char *keyexpr = "demo/example/zenoh-c-put";
#define DEFAULT_KEYEXPR "demo/example/zenoh-c-put"

if (argc > 1) keyexpr = argv[1];
struct args_t {
char* keyexpr; // -k
};
struct args_t parse_args(int argc, char** argv, z_owned_config_t* config);

int main(int argc, char** argv) {
z_owned_config_t config = z_config_default();
if (argc > 3) {
if (zc_config_insert_json(z_loan(config), Z_CONFIG_CONNECT_KEY, argv[3]) < 0) {
printf(
"Couldn't insert value `%s` in configuration at `%s`. This is likely because `%s` expects a "
"JSON-serialized list of strings\n",
argv[3], Z_CONFIG_CONNECT_KEY, Z_CONFIG_CONNECT_KEY);
exit(-1);
}
}
struct args_t args = parse_args(argc, argv, &config);

printf("Opening session...\n");
z_owned_session_t s = z_open(z_move(config));
Expand All @@ -39,13 +35,50 @@ int main(int argc, char **argv) {
exit(-1);
}

printf("Deleting resources matching '%s'...\n", keyexpr);
printf("Deleting resources matching '%s'...\n", args.keyexpr);
z_delete_options_t options = z_delete_options_default();
int res = z_delete(z_loan(s), z_keyexpr(keyexpr), &options);
int res = z_delete(z_loan(s), z_keyexpr(args.keyexpr), &options);
if (res < 0) {
printf("Delete failed...\n");
}

z_close(z_move(s));
return 0;
}

void print_help() {
printf(
"\
Usage: z_delete [OPTIONS]\n\n\
Options:\n\
-k <KEY> (optional, string, default='%s'): The key expression to write to\n",
DEFAULT_KEYEXPR);
printf(COMMON_HELP);
printf(
"\
-h: print help\n");
}

struct args_t parse_args(int argc, char** argv, z_owned_config_t* config) {
if (parse_opt(argc, argv, "h", false)) {
print_help();
exit(1);
}
char* keyexpr = parse_opt(argc, argv, "k", true);
if (!keyexpr) {
keyexpr = DEFAULT_KEYEXPR;
}
parse_zenoh_common_args(argc, argv, config);
char* arg = check_unknown_opts(argc, argv);
if (arg) {
printf("Unknown option %s\n", arg);
exit(-1);
}
char** pos_args = parse_pos_args(argc, argv, 1);
if (!pos_args || pos_args[0]) {
printf("Unexpected positional arguments\n");
free(pos_args);
exit(-1);
}
oteffahi marked this conversation as resolved.
Show resolved Hide resolved
return (struct args_t){.keyexpr = keyexpr};
}
Loading