From 4b887d8a8a849a8c878934009976dd47300b43cb Mon Sep 17 00:00:00 2001 From: madmurphy Date: Thu, 10 Feb 2022 22:24:56 +0000 Subject: New version of `gnunet-search` is ready to be tested --- src/fs/gnunet-search.c | 616 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 524 insertions(+), 92 deletions(-) (limited to 'src') diff --git a/src/fs/gnunet-search.c b/src/fs/gnunet-search.c index 3bf013650..124a0a1a4 100644 --- a/src/fs/gnunet-search.c +++ b/src/fs/gnunet-search.c @@ -1,6 +1,6 @@ /* This file is part of GNUnet. - Copyright (C) 2001, 2002, 2004, 2005, 2006, 2007, 2009 GNUnet e.V. + Copyright (C) 2001, 2002, 2004, 2005, 2006, 2007, 2009, 2022 GNUnet e.V. GNUnet is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -24,10 +24,57 @@ * @author Krista Bennett * @author James Blackwell * @author Igor Wronsky + * @author madmurphy */ +#include +#include +#include #include "platform.h" #include "gnunet_fs_service.h" + +#define GNUNET_SEARCH_log(kind, ...) \ + GNUNET_log_from(kind, "gnunet-search", __VA_ARGS__) + + +/* The default settings that we use for the printed output */ + +#define DEFAULT_DIR_FORMAT "#%n:\ngnunet-download -o \"%f\" -R %u\n\n" +#define HELP_DEFAULT_DIR_FORMAT "#%n:\\ngnunet-download -o \"%f\" -R %u\\n\\n" +#define DEFAULT_FILE_FORMAT "#%n:\ngnunet-download -o \"%f\" %u\n\n" +#define HELP_DEFAULT_FILE_FORMAT "#%n:\\ngnunet-download -o \"%f\" %u\\n\\n" +#define VERB_DEFAULT_DIR_FORMAT DEFAULT_DIR_FORMAT "%a\n" +#define VERB_DEFAULT_FILE_FORMAT DEFAULT_FILE_FORMAT "%a\n" + +#if HAVE_LIBEXTRACTOR +#define DEFAULT_META_FORMAT " %t: %p\n" +#define HELP_DEFAULT_META_FORMAT " %t: %p\\n" +#define HELP_EXTRACTOR_TEXTADD ", %t is the property type" +#else +#define DEFAULT_META_FORMAT " MetaType #%i: %p\n" +#define HELP_DEFAULT_META_FORMAT " MetaType #%i: %p\\n" +#define HELP_EXTRACTOR_TEXTADD "" +#endif + +#define GENERIC_DIRECTORY_NAME "collection" +#define GENERIC_FILE_NAME "no-name" +#define GENERIC_FILE_MIMETYPE "application/octet-stream" + + +enum GNUNET_SEARCH_MetadataPrinterFlags { + METADATA_PRINTER_FLAG_NONE = 0, + METADATA_PRINTER_FLAG_ONE_RUN = 1, + METADATA_PRINTER_FLAG_HAVE_TYPE = 2 +}; + + +struct GNUNET_SEARCH_MetadataPrinterInfo { + unsigned int counter; + unsigned int flags; + int type; +}; + + static int ret; static const struct GNUNET_CONFIGURATION_Handle *cfg; @@ -38,6 +85,12 @@ static struct GNUNET_FS_SearchContext *sc; static char *output_filename; +static char *format_string; + +static char *dir_format_string; + +static char *meta_format_string; + static struct GNUNET_FS_DirectoryBuilder *db; static unsigned int anonymity = 1; @@ -53,16 +106,79 @@ static unsigned int results; static unsigned int verbose; +static int bookmark_only; + static int local_only; +static int silent_mode; + static struct GNUNET_SCHEDULER_Task *tt; +/** + * Print the escape sequence at the beginning of a string. + * + * @param esc a string that **must** begin with a backslash (the function only + * assumes that it does, but does not check) + * @return the fragment that follows what has been printed + * @author madmurphy + * + * If `"\\nfoo"` is passed as argument, this function prints a new line and + * returns `"foo"` + */ +static const char * +print_escape_sequence (const char *const esc) +{ + unsigned int probe; + const char * cursor = esc + 1; + char tmp; + switch (*cursor) + { + /* Trivia */ + case '\\': putchar ('\\'); return cursor + 1; + case 'a': putchar ('\a'); return cursor + 1; + case 'b': putchar ('\b'); return cursor + 1; + case 'e': putchar ('\x1B'); return cursor + 1; + case 'f': putchar ('\f'); return cursor + 1; + case 'n': putchar ('\n'); return cursor + 1; + case 'r': putchar ('\r'); return cursor + 1; + case 't': putchar ('\t'); return cursor + 1; + case 'v': putchar ('\v'); return cursor + 1; + + /* Possibly hexadecimal code point */ + case 'x': + probe = 0; + while (probe < 256 && isxdigit((tmp = *++cursor))) + probe = (probe << 4) + tmp - (tmp > 96 ? 87 : tmp > 64 ? 55 : 48); + goto maybe_codepoint; + + /* Possibly octal code point */ + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + probe = *cursor++ - 48; + do probe = (probe << 3) + *cursor++ - 48; + while (probe < 256 && cursor < esc + 4 && *cursor > 47 && *cursor < 56); + goto maybe_codepoint; + + /* Boredom */ + case '\0': putchar ('\\'); return cursor; + default: printf ("\\%c", *cursor); return cursor + 1; + } + +maybe_codepoint: + if (probe < 256) + putchar (probe); + else + fwrite (esc, 1, cursor - esc, stdout); + return cursor; +} + + /** * Type of a function that libextractor calls for each * meta data item found. * - * @param cls closure (user-defined, unused) + * @param cls closure (user-defined, used for the iteration info) * @param plugin_name name of the plugin that produced this value; * special values can be used (e.g. '<zlib>' for zlib being * used in the main libextractor library and yielding @@ -76,33 +192,228 @@ static struct GNUNET_SCHEDULER_Task *tt; * @return 0 to continue extracting, 1 to abort */ static int -item_printer (void *cls, - const char *plugin_name, - enum EXTRACTOR_MetaType type, - enum EXTRACTOR_MetaFormat format, - const char *data_mime_type, - const char *data, - size_t data_size) +item_printer (void *const cls, + const char *const plugin_name, + const enum EXTRACTOR_MetaType type, + const enum EXTRACTOR_MetaFormat format, + const char *const data_mime_type, + const char *const data, + const size_t data_size) { - if ((format != EXTRACTOR_METAFORMAT_UTF8) && - (format != EXTRACTOR_METAFORMAT_C_STRING)) +#define info ((struct GNUNET_SEARCH_MetadataPrinterInfo *) cls) + if ((format != EXTRACTOR_METAFORMAT_UTF8 && + format != EXTRACTOR_METAFORMAT_C_STRING) || + type == EXTRACTOR_METATYPE_GNUNET_ORIGINAL_FILENAME) return 0; - if (type == EXTRACTOR_METATYPE_GNUNET_ORIGINAL_FILENAME) + info->counter++; + if ((info->flags & METADATA_PRINTER_FLAG_HAVE_TYPE) && type != info->type) return 0; + + const char *cursor = meta_format_string; + const char *next_spec = strchr(cursor, '%'); + const char *next_esc = strchr(cursor, '\\'); + +parse_format: + + /* If an escape sequence exists before the next format specifier... */ + if (next_esc && (!next_spec || next_esc < next_spec)) + { + if (next_esc > cursor) + fwrite (cursor, 1, next_esc - cursor, stdout); + + cursor = print_escape_sequence (next_esc); + next_esc = strchr(cursor, '\\'); + goto parse_format; + } + + /* If a format specifier exists before the next escape sequence... */ + if (next_spec && (!next_esc || next_spec < next_esc)) + { + if (next_spec > cursor) + fwrite (cursor, 1, next_spec - cursor, stdout); + + switch (*++next_spec) + { + case '%': putchar('%'); break; + case 'i': printf ("%d", type); break; + case 'l': printf ("%lu", (long unsigned int) data_size); break; + case 'n': printf ("%u", info->counter); break; + case 'p': printf ("%s", data); break; #if HAVE_LIBEXTRACTOR - printf ("\t%20s: %s\n", - dgettext (LIBEXTRACTOR_GETTEXT_DOMAIN, - EXTRACTOR_metatype_to_string (type)), - data); -#else - printf ("\t%20d: %s\n", type, data); + case 't': + printf ("%s", + dgettext (LIBEXTRACTOR_GETTEXT_DOMAIN, + EXTRACTOR_metatype_to_string (type))); + break; #endif - return 0; + case 'w': printf ("%s", plugin_name); break; + case '\0': putchar('%'); return 0; + default: printf ("%%%c", *next_spec); break; + } + cursor = next_spec + 1; + next_spec = strchr(cursor, '%'); + goto parse_format; + } + + if (*cursor) + printf ("%s", cursor); + + return info->flags & METADATA_PRINTER_FLAG_ONE_RUN; +#undef info +} + + +/** + * Print a search result according to the current formats + * + * @param filename the filename for this result + * @param uri the `struct GNUNET_FS_Uri` this result refers to + * @param metadata the `struct GNUNET_CONTAINER_MetaData` associated with this + result + * @param resultnum the result number + * @param is_directory GNUNET_YES if this is a directory, otherwise GNUNET_NO + * @author madmurphy + */ +static void +print_search_result (const char *const filename, + const struct GNUNET_FS_Uri *const uri, + const struct GNUNET_CONTAINER_MetaData *const metadata, + const unsigned int resultnum, + const int is_directory) +{ + + const char *cursor = GNUNET_YES == is_directory ? + dir_format_string + : format_string; + + const char *next_spec = strchr(cursor, '%'); + const char *next_esc = strchr(cursor, '\\'); + char *placeholder; + struct GNUNET_SEARCH_MetadataPrinterInfo info; + +parse_format: + /* If an escape sequence exists before the next format specifier... */ + if (next_esc && (!next_spec || next_esc < next_spec)) + { + if (next_esc > cursor) + fwrite (cursor, 1, next_esc - cursor, stdout); + + cursor = print_escape_sequence (next_esc); + next_esc = strchr(cursor, '\\'); + goto parse_format; + } + + /* If a format specifier exists before the next escape sequence... */ + if (next_spec && (!next_esc || next_spec < next_esc)) + { + if (next_spec > cursor) + fwrite (cursor, 1, next_spec - cursor, stdout); + + switch (*++next_spec) + { + /* All metadata fields */ + case 'a': + info.flags = METADATA_PRINTER_FLAG_NONE; + +iterate_meta: + info.counter = 0; + GNUNET_CONTAINER_meta_data_iterate (metadata, &item_printer, &info); + break; + /* File's name */ + case 'f': + if (GNUNET_YES == is_directory) + { + printf ("%s%s", filename, GNUNET_FS_DIRECTORY_EXT); + break; + } + printf ("%s", filename); + break; + /* Only the first metadata field */ + case 'j': + info.flags = METADATA_PRINTER_FLAG_ONE_RUN; + goto iterate_meta; + /* File name's length */ + case 'l': + printf ("%lu", + (long unsigned int) ( GNUNET_YES == is_directory ? + strlen(filename) + + (sizeof(GNUNET_FS_DIRECTORY_EXT) - 1) + : + strlen(filename))); + break; + /* File's mime type */ + case 'm': + if (GNUNET_YES == is_directory) + { + printf ("%s", GNUNET_FS_DIRECTORY_MIME); + break; + } + placeholder = GNUNET_CONTAINER_meta_data_get_by_type ( + metadata, + EXTRACTOR_METATYPE_MIMETYPE); + printf ("%s", placeholder ? placeholder : GENERIC_FILE_MIMETYPE); + GNUNET_free (placeholder); + break; + /* Result number */ + case 'n': printf ("%u", resultnum); break; + /* File's size */ + case 's': + printf ("%" PRIu64, GNUNET_FS_uri_chk_get_file_size (uri)); + break; + /* File's URI */ + case 'u': + placeholder = GNUNET_FS_uri_to_string (uri); + printf ("%s", placeholder); + GNUNET_free (placeholder); + break; + + /* We can add as many cases as we want here... */ + + /* Handle `%123#a` and `%123#j` (e.g. `%5#j` is a book title) */ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + cursor = next_spec; + info.type = *cursor - 48; + while (isdigit(*++cursor) && info.type < (INT_MAX - *cursor + 48) / 10) + info.type = info.type * 10 + *cursor - 48; + if (info.type == 0 || *cursor != '#') + goto not_a_specifier; + switch (*++cursor) + { + /* All metadata fields of type `info.type` */ + case 'a': + next_spec = cursor; + info.flags = METADATA_PRINTER_FLAG_HAVE_TYPE; + goto iterate_meta; + + /* Only the first metadata field of type `info.type` */ + case 'j': + next_spec = cursor; + info.flags = METADATA_PRINTER_FLAG_HAVE_TYPE | + METADATA_PRINTER_FLAG_ONE_RUN; + goto iterate_meta; + } + goto not_a_specifier; + + /* All other cases */ + case '%': putchar('%'); break; + case '\0': putchar('%'); return; + +not_a_specifier: + default: printf ("%%%c", *next_spec); break; + } + cursor = next_spec + 1; + next_spec = strchr(cursor, '%'); + goto parse_format; + } + + if (*cursor) + printf ("%s", cursor); } static void -clean_task (void *cls) +clean_task (void *const cls) { size_t dsize; void *ddata; @@ -126,9 +437,10 @@ clean_task (void *cls) GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE)) { - fprintf (stderr, - _ ("Failed to write directory with search results to `%s'\n"), - output_filename); + GNUNET_SEARCH_log(GNUNET_ERROR_TYPE_ERROR, + _ ("Failed to write directory with search results to " + "`%s'\n"), + output_filename); } GNUNET_free (ddata); GNUNET_free (output_filename); @@ -149,11 +461,11 @@ clean_task (void *cls) * field in the GNUNET_FS_ProgressInfo struct. */ static void * -progress_cb (void *cls, const struct GNUNET_FS_ProgressInfo *info) +progress_cb (void *const cls, + const struct GNUNET_FS_ProgressInfo *const info) { static unsigned int cnt; int is_directory; - char *uri; char *filename; switch (info->status) @@ -162,13 +474,17 @@ progress_cb (void *cls, const struct GNUNET_FS_ProgressInfo *info) break; case GNUNET_FS_STATUS_SEARCH_RESULT: + if (silent_mode) + break; + if (db != NULL) - GNUNET_FS_directory_builder_add (db, - info->value.search.specifics.result.uri, - info->value.search.specifics.result.meta, - NULL); - uri = GNUNET_FS_uri_to_string (info->value.search.specifics.result.uri); - printf ("#%u:\n", ++cnt); + GNUNET_FS_directory_builder_add ( + db, + info->value.search.specifics.result.uri, + info->value.search.specifics.result.meta, + NULL); + + cnt++; filename = GNUNET_CONTAINER_meta_data_get_by_type ( info->value.search.specifics.result.meta, EXTRACTOR_METATYPE_GNUNET_ORIGINAL_FILENAME); @@ -179,45 +495,37 @@ progress_cb (void *cls, const struct GNUNET_FS_ProgressInfo *info) while ((filename[0] != '\0') && ('/' == filename[strlen (filename) - 1])) filename[strlen (filename) - 1] = '\0'; GNUNET_DISK_filename_canonicalize (filename); - if (GNUNET_YES == is_directory) - printf ("gnunet-download -o \"%s%s\" -R %s\n", - filename, - GNUNET_FS_DIRECTORY_EXT, - uri); - else - printf ("gnunet-download -o \"%s\" %s\n", filename, uri); } - else if (GNUNET_YES == is_directory) - printf ("gnunet-download -o \"collection%s\" -R %s\n", - GNUNET_FS_DIRECTORY_EXT, - uri); - else - printf ("gnunet-download %s\n", uri); - if (verbose) - GNUNET_CONTAINER_meta_data_iterate (info->value.search.specifics.result - .meta, - &item_printer, - NULL); - printf ("\n"); + print_search_result ( filename ? + filename + : is_directory ? + GENERIC_DIRECTORY_NAME + : + GENERIC_FILE_NAME, + info->value.search.specifics.result.uri, + info->value.search.specifics.result.meta, + cnt, + is_directory); fflush (stdout); GNUNET_free (filename); - GNUNET_free (uri); results++; if ((results_limit > 0) && (results >= results_limit)) + { GNUNET_SCHEDULER_shutdown (); + /* otherwise the function might keep printing results for a while... */ + silent_mode = GNUNET_YES; + } break; case GNUNET_FS_STATUS_SEARCH_UPDATE: - break; - case GNUNET_FS_STATUS_SEARCH_RESULT_STOPPED: /* ignore */ break; case GNUNET_FS_STATUS_SEARCH_ERROR: - fprintf (stderr, - _ ("Error searching: %s.\n"), - info->value.search.specifics.error.message); + GNUNET_SEARCH_log(GNUNET_ERROR_TYPE_ERROR, + _ ("Error searching: %s.\n"), + info->value.search.specifics.error.message); GNUNET_SCHEDULER_shutdown (); break; @@ -226,7 +534,9 @@ progress_cb (void *cls, const struct GNUNET_FS_ProgressInfo *info) break; default: - fprintf (stderr, _ ("Unexpected status: %d\n"), info->status); + GNUNET_SEARCH_log(GNUNET_ERROR_TYPE_ERROR, + _ ("Unexpected status: %d\n"), + info->status); break; } return NULL; @@ -234,7 +544,7 @@ progress_cb (void *cls, const struct GNUNET_FS_ProgressInfo *info) static void -shutdown_task (void *cls) +shutdown_task (void *const cls) { if (sc != NULL) { @@ -245,9 +555,10 @@ shutdown_task (void *cls) static void -timeout_task (void *cls) +timeout_task (void *const cls) { tt = NULL; + silent_mode = GNUNET_YES; GNUNET_SCHEDULER_shutdown (); } @@ -258,18 +569,47 @@ timeout_task (void *cls) * @param cls closure * @param args remaining command-line arguments * @param cfgfile name of the configuration file used (for saving, can be NULL!) - * @param c configuration + * @param cfgarg configuration */ static void -run (void *cls, - char *const *args, - const char *cfgfile, - const struct GNUNET_CONFIGURATION_Handle *c) +run (void *const cls, + char *const *const args, + const char *const cfgfile, + const struct GNUNET_CONFIGURATION_Handle *const cfgarg) { struct GNUNET_FS_Uri *uri; unsigned int argc; enum GNUNET_FS_SearchOptions options; + if (silent_mode && bookmark_only) + { + fprintf (stderr, + _ ("Conflicting options --bookmark-only and --silent.\n")); + ret = 1; + return; + } + if (bookmark_only && output_filename) + { + fprintf (stderr, + _ ("Conflicting options --bookmark-only and --output.\n")); + ret = 1; + return; + } + if (silent_mode && !output_filename) + { + fprintf (stderr, _ ("An output file is mandatory for silent mode.\n")); + ret = 1; + return; + } + if (NULL == dir_format_string) + dir_format_string = format_string ? format_string + : verbose ? VERB_DEFAULT_DIR_FORMAT + : DEFAULT_DIR_FORMAT; + if (NULL == format_string) + format_string = verbose ? VERB_DEFAULT_FILE_FORMAT + : DEFAULT_FILE_FORMAT; + if (NULL == meta_format_string) + meta_format_string = DEFAULT_META_FORMAT; argc = 0; while (NULL != args[argc]) argc++; @@ -282,7 +622,27 @@ run (void *cls, ret = 1; return; } - cfg = c; + if (!GNUNET_FS_uri_test_ksk (uri) && !GNUNET_FS_uri_test_sks (uri)) + { + fprintf (stderr, + "%s", + _ ("Invalid URI. Valid URIs for searching are keyword query " + "URIs\n(\"gnunet://fs/ksk/...\") and namespace content URIs " + "(\"gnunet://fs/sks/...\").\n")); + GNUNET_FS_uri_destroy (uri); + ret = 1; + return; + } + if (bookmark_only) + { + char * bmstr = GNUNET_FS_uri_to_string (uri); + printf ("%s\n", bmstr); + GNUNET_free (bmstr); + GNUNET_FS_uri_destroy (uri); + ret = 0; + return; + } + cfg = cfgarg; ctx = GNUNET_FS_start (cfg, "gnunet-search", &progress_cb, @@ -291,7 +651,7 @@ run (void *cls, GNUNET_FS_OPTIONS_END); if (NULL == ctx) { - fprintf (stderr, _ ("Could not initialize `%s' subsystem.\n"), "FS"); + fprintf (stderr, _ ("Could not initialize the `%s` subsystem.\n"), "FS"); GNUNET_FS_uri_destroy (uri); ret = 1; return; @@ -321,18 +681,74 @@ run (void *cls, * * @param argc number of arguments from the command line * @param argv command line arguments - * @return 0 ok, 1 on error + * @return 0 ok, an error number on error */ int main (int argc, char *const *argv) { struct GNUNET_GETOPT_CommandLineOption options[] = - { GNUNET_GETOPT_option_uint ('a', - "anonymity", - "LEVEL", - gettext_noop ( - "set the desired LEVEL of receiver-anonymity"), - &anonymity), + { GNUNET_GETOPT_option_uint ( + 'a', + "anonymity", + "LEVEL", + gettext_noop ("set the desired LEVEL of receiver-anonymity (default: " + "1)"), + &anonymity), + GNUNET_GETOPT_option_flag ( + 'b', + "bookmark-only", + gettext_noop ("do not search, print only the URI that points to this " + "search"), + &bookmark_only), + GNUNET_GETOPT_option_string ( + 'F', + "dir-printf", + "FORMAT", + gettext_noop ("write search results for directories according to " + "FORMAT; the format specifiers supported here are " + "identical to those supported in the --printf argument " + "(please refer to it for more information); if missing, " + "--dir-printf defaults to --printf; if --printf is " + "missing too --dir-printf defaults to `" + HELP_DEFAULT_DIR_FORMAT "`"), + &dir_format_string), + GNUNET_GETOPT_option_string ( + 'f', + "printf", + "FORMAT", + gettext_noop ("write search results according to FORMAT, where %a is " + "the complete list of all the printable metadata " + "available (each member will be displayed according to " + "the --iter-printf argument) - use %j for printing only " + "one field - %f is the file's name, %l is the file name's " + "length, %m is the file's mime type, %n is the search " + "result number, %s is the file's size in bytes and %u is " + "the file's URI; the %a and %j specifiers optionally " + "support metatype filtering via hash sign (e.g. `%5#j` " + "prints a book title, if present - see libextractor's " + "metatypes for the complete list of numerical " + "identifiers); if missing, --printf defaults to `" + HELP_DEFAULT_FILE_FORMAT "`"), + &format_string), + GNUNET_GETOPT_option_string ( + 'i', + "iter-printf", + "FORMAT", + gettext_noop ("when the %a or %j format specifiers appear in --printf " + "or --dir-printf, list each metadata property according " + "to FORMAT, where %p is the property's content, %l is the " + "content's length in bytes" HELP_EXTRACTOR_TEXTADD ", %i " + "is the property type's unique identifier, %n is the " + "property number and %w is the name of the plugin that " + "provided the information; if missing, --iter-printf " + "defaults to `" HELP_DEFAULT_META_FORMAT "`"), + &meta_format_string), + GNUNET_GETOPT_option_uint ('N', + "results", + "VALUE", + gettext_noop ("automatically terminate search " + "after VALUE results are found"), + &results_limit), GNUNET_GETOPT_option_flag ( 'n', "no-network", @@ -341,39 +757,55 @@ main (int argc, char *const *argv) GNUNET_GETOPT_option_string ( 'o', "output", - "PREFIX", - gettext_noop ("write search results to file starting with PREFIX"), + "FILENAME", + gettext_noop ("create a GNUnet directory with search results at " + "FILENAME (e.g. `gnunet-search --output=commons" + GNUNET_FS_DIRECTORY_EXT " commons`)"), &output_filename), + GNUNET_GETOPT_option_flag ( + 's', + "silent", + gettext_noop ("silent mode (requires the --output argument)"), + &silent_mode), GNUNET_GETOPT_option_relative_time ( 't', "timeout", "DELAY", - gettext_noop ("automatically terminate search after DELAY"), + gettext_noop ("automatically terminate search after DELAY (in number of " + "microseconds - 1 second = 1000000); if 0 or omitted it " + "means to wait for CTRL-C"), &timeout), - GNUNET_GETOPT_option_verbose (&verbose), - GNUNET_GETOPT_option_uint ('N', - "results", - "VALUE", - gettext_noop ("automatically terminate search " - "after VALUE results are found"), - &results_limit), + GNUNET_GETOPT_option_increment_uint ( + 'V', + "verbose", + gettext_noop ("be verbose (append \"%a\\n\" to the default --printf and " + "--dir-printf arguments - ignored when these are provided " + "by the user)"), + &verbose), GNUNET_GETOPT_OPTION_END }; if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv)) return 2; - ret = - (GNUNET_OK == - GNUNET_PROGRAM_run (argc, - argv, - "gnunet-search [OPTIONS] KEYWORD", - gettext_noop ( - "Search GNUnet for files that were published on GNUnet"), + if (GNUNET_SYSERR == + GNUNET_PROGRAM_run (argc, + argv, + "gnunet-search [OPTIONS] KEYWORD1 KEYWORD2 ...", + gettext_noop ("Search for files that have been " + "published on GNUnet\n\nKeywords " + "should start with a plus sign to " + "indicate that they are required -\n" + "e.g. `gnunet-search commons gpl` " + "searches for files that match " + "*either*\n\"commons\" or \"gpl\", " + "whereas `gnunet-search +commons " + "+gpl` searches for files\nthat match " + "*both* \"commons\" and \"gpl\".\n"), options, &run, NULL)) - ? ret - : 1; + ret = 1; + GNUNET_free_nz ((void *) argv); return ret; } -- cgit v1.2.3