Advertisement
aaaaaa123456789

binsrch.c (search for bytestrings in files)

Nov 7th, 2014
385
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 19.40 KB | None | 0 0
  1. /*
  2.    This code is hereby released to the public domain.
  3.    ~aaaaaa123456789, 2014-11-07
  4. */
  5.  
  6. // there's two ways of handling strings
  7. // Linux uses UTF-8, so strings are still char *, and internationalization works directly
  8. // Windows uses UTF-16LE, locale-specific ANSI/OEM-to-Unicode conversions, and lots of
  9. // annoying things that make internationalization a massive clusterfuck from beginning to end
  10. // if you're wondering why these constants exist, ask Bill Gates
  11. #define UNICODE
  12. #define _UNICODE
  13.  
  14. #include <windows.h>
  15. #include <wchar.h>
  16. #include <stdlib.h>
  17. #include <string.h>
  18. #include <stdarg.h>
  19.  
  20. enum options {
  21.   OPT_RECURSE        = 1,
  22.   OPT_DECIMAL        = 2,
  23.   OPT_SHOW_NO_MATCH  = 4,
  24.   OPT_FILENAMES_ONLY = 8,
  25. };
  26.  
  27. struct file_info {
  28.   unsigned long long size;
  29.   struct file_info * next;
  30.   wchar_t name[];
  31. };
  32.  
  33. #define dword_to_qword(dw1, dw2) ((((unsigned long long) (unsigned) (dw1)) << 32) | ((unsigned long long) (unsigned) (dw2)))
  34. #define hex_char_to_number(hex) (                          \
  35.   (((hex) >= 0x30) && ((hex) <= 0x39)) ? (hex) - 0x30 :    \
  36.   (((hex) >= 0x41) && ((hex) <= 0x46)) ? (hex) - 0x37 :    \
  37.   (((hex) >= 0x61) && ((hex) <= 0x66)) ? (hex) - 0x57 :    \
  38.   -1                                                       \
  39. )
  40.  
  41. int wmain(int, wchar_t **);
  42. void console_write(int, const wchar_t *, ...);
  43. void console_write_line(int, const wchar_t *, ...);
  44. wchar_t console_get_char(void);
  45. int parse_options(wchar_t ***, const wchar_t *);
  46. struct file_info * find_files(const wchar_t *, int);
  47. struct file_info * get_node_for_file(WIN32_FIND_DATA *, const wchar_t *, int);
  48. struct file_info * get_dir_contents(const wchar_t *, int);
  49. struct file_info * find_last_node(struct file_info *);
  50. void insert_node(struct file_info **, struct file_info **, struct file_info *);
  51. void destroy_file_info(struct file_info *);
  52. void print_usage(const wchar_t *);
  53. unsigned get_match_buffer(wchar_t **, void **);
  54. unsigned get_match_from_cmdline(wchar_t **, void **);
  55. unsigned get_match_from_input(void **);
  56. wchar_t * get_line(void);
  57. unsigned parse_match_item(wchar_t *, void **);
  58. void find_in_file(const struct file_info *, const char *, unsigned, int, unsigned char);
  59. int find_in_buffer(const char *, unsigned, const char *, unsigned, unsigned long long, const wchar_t *, int, char, unsigned char);
  60. wchar_t * get_error_message(int);
  61. unsigned long long max_file_size(struct file_info *);
  62. unsigned char size_length(unsigned long long, int);
  63. wchar_t * repeat_character(wchar_t, unsigned);
  64. void print_headers(unsigned char);
  65. const wchar_t * get_filename(const wchar_t *);
  66. wchar_t * print_number(wchar_t *, unsigned long long, int, unsigned char);
  67. const wchar_t * get_dir_separator(const wchar_t *);
  68. void show_help(const wchar_t *);
  69.  
  70. // this is a hack to make things work on GCC, since it doesn't define wmain() -- another one of Microsoft's new spectacular inventions
  71. #ifdef __GNUC__
  72. // from <internal.h>, I believe; easier to just copy the declarations
  73. extern int __wgetmainargs(int *, wchar_t ***, wchar_t ***, int, int *);
  74.  
  75. int main (void) {
  76.   int argc;
  77.   wchar_t ** argv;
  78.   wchar_t ** envp;
  79.   int startinfo; // type should be _startupinfo, but that's typedef'd to int
  80.   if (__wgetmainargs(&argc, &argv, &envp, 0, &startinfo) < 0) abort();
  81.   return wmain(argc, argv);
  82. }
  83. #endif
  84.  
  85. int wmain (int argc, wchar_t ** argv) {
  86.   wchar_t * invocation_name = *(argv ++);
  87.   int options = parse_options(&argv, invocation_name);
  88.   const wchar_t * location = *(argv ++);
  89.   if (!location) print_usage(invocation_name);
  90.   void * match_buffer;
  91.   unsigned match_length = get_match_buffer(argv, &match_buffer);
  92.   if (!match_length) return 1;
  93.   struct file_info * files = find_files(location, options & OPT_RECURSE);
  94.   if (!files) {
  95.     free(match_buffer);
  96.     if (GetLastError()) {
  97.       wchar_t * error_text = get_error_message(GetLastError());
  98.       console_write_line(1, invocation_name, L": error: ", error_text, NULL);
  99.       free(error_text);
  100.       return 2;
  101.     }
  102.     return 0;
  103.   }
  104.   struct file_info * current_file;
  105.   unsigned char size_chars = size_length(max_file_size(files), !(options & OPT_DECIMAL));
  106.   print_headers(size_chars);
  107.   for (current_file = files; current_file; current_file = current_file -> next)
  108.     find_in_file(current_file, match_buffer, match_length, options, size_chars);
  109.   destroy_file_info(files);
  110.   free(match_buffer);
  111.   return 0;
  112. }
  113.  
  114. void console_write (int error, const wchar_t * string, ...) {
  115.   // you'd expect C99 to have a wide char equivalent of stdin and stdout, huh?
  116.   va_list ap;
  117.   HANDLE console = GetStdHandle(error ? STD_ERROR_HANDLE: STD_OUTPUT_HANDLE);
  118.   DWORD dummy; // like variable name, like API call
  119.   va_start(ap, string);
  120.   while (string) {
  121.     WriteConsole(console, string, wcslen(string), &dummy, NULL);
  122.     string = va_arg(ap, const wchar_t *);
  123.   }
  124.   va_end(ap);
  125. }
  126.  
  127. void console_write_line (int error, const wchar_t * string, ...) {
  128.   va_list ap;
  129.   va_start(ap, string);
  130.   while (string) {
  131.     console_write(error, string, NULL);
  132.     string = va_arg(ap, const wchar_t *);
  133.   }
  134.   va_end(ap);
  135.   console_write(error, L"\r\n", NULL);
  136. }
  137.  
  138. wchar_t console_get_char (void) {
  139.   // but there isn't a wide char equivalent of this, so leave it to the poor programmer to rewrite getchar() with syscalls
  140.   wchar_t result;
  141.   DWORD nr;
  142.   nr = ReadConsole(GetStdHandle(STD_INPUT_HANDLE), &result, 1, &nr, NULL) || nr;
  143.   if (!nr) return 0;
  144.   return result;
  145. }
  146.  
  147. int parse_options (wchar_t *** pcmd, const wchar_t * program_name) {
  148.   int options = 0;
  149.   int keep_parsing = 1;
  150.   const wchar_t * option_text;
  151.   while (keep_parsing) {
  152.     if (!**pcmd) return options;
  153.     if (***pcmd != L'-') return options;
  154.     for (option_text = *((*pcmd) ++) + 1; *option_text; option_text ++)
  155.       switch (*option_text) {
  156.         case L'R': options |= OPT_RECURSE; break;
  157.         case L'd': options |= OPT_DECIMAL; break;
  158.         case L'f': options |= OPT_FILENAMES_ONLY; break;
  159.         case L'N': options |= OPT_SHOW_NO_MATCH; break;
  160.         case L'-': keep_parsing = 0; break;
  161.         case L'?': show_help(program_name);
  162.         default: print_usage(program_name);
  163.       }
  164.   }
  165.   return options;
  166. }
  167.  
  168. struct file_info * find_files (const wchar_t * location, int recursive) {
  169.   if (!location) exit(0);
  170.   int len = wcslen(location);
  171.   if (!len) exit(0);
  172.   if (location[len - 1] == L'\\') return get_dir_contents(location, recursive);
  173.   WIN32_FIND_DATA fd;
  174.   HANDLE handle = FindFirstFile(location, &fd);
  175.   if (handle == INVALID_HANDLE_VALUE) {
  176.     if (GetLastError() == ERROR_FILE_NOT_FOUND) SetLastError(0);
  177.     return NULL;
  178.   }
  179.   struct file_info * result = NULL;
  180.   struct file_info * last = NULL;
  181.   struct file_info * current_file = get_node_for_file(&fd, location, recursive);
  182.   if (current_file) insert_node(&result, &last, current_file);
  183.   while (FindNextFile(handle, &fd)) {
  184.     current_file = get_node_for_file(&fd, location, recursive);
  185.     if (current_file) insert_node(&result, &last, current_file);
  186.   }
  187.   if (GetLastError() == ERROR_NO_MORE_FILES) SetLastError(0);
  188.   FindClose(handle);
  189.   return result;
  190. }
  191.  
  192. struct file_info * get_node_for_file (WIN32_FIND_DATA * fd, const wchar_t * search_string, int recursive) {
  193.   if (!(wcscmp(fd -> cFileName, L".") && wcscmp(fd -> cFileName, L".."))) return NULL;
  194.   if (fd -> dwFileAttributes & FILE_ATTRIBUTE_DEVICE) return NULL;
  195.   struct file_info * newNode;
  196.   wchar_t * full_name = malloc(sizeof(wchar_t) * (wcslen(search_string) + wcslen(fd -> cFileName) + 1));
  197.   const wchar_t * dir_separator = get_dir_separator(search_string);
  198.   if (dir_separator) {
  199.     unsigned length = (dir_separator - search_string) + 1;
  200.     memcpy(full_name, search_string, length * sizeof(wchar_t));
  201.     wcscpy(full_name + length, fd -> cFileName);
  202.   } else
  203.     wcscpy(full_name, fd -> cFileName);
  204.   if (fd -> dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  205.     if (recursive)
  206.       newNode = get_dir_contents(full_name, 1);
  207.     else
  208.       newNode = NULL;
  209.   } else {
  210.     newNode = malloc(sizeof(struct file_info) + sizeof(wchar_t) * (wcslen(full_name) + 1));
  211.     newNode -> next = NULL;
  212.     newNode -> size = dword_to_qword(fd -> nFileSizeHigh, fd -> nFileSizeLow); // someone tell Microsoft that we have 64-bit variables now
  213.     wcscpy(newNode -> name, full_name);
  214.   }
  215.   free(full_name);
  216.   return newNode;
  217. }
  218.  
  219. struct file_info * get_dir_contents (const wchar_t * directory, int recursive) {
  220.   int len = wcslen(directory);
  221.   wchar_t * target = malloc((3 + len) * sizeof(wchar_t));
  222.   memcpy(target, directory, sizeof(wchar_t) * len);
  223.   if (target[len - 1] != L'\\') target[len ++] = L'\\';
  224.   target[len ++] = L'*';
  225.   target[len] = 0;
  226.   struct file_info * files = find_files(target, recursive);
  227.   free(target);
  228.   return files;
  229. }
  230.  
  231. struct file_info * find_last_node (struct file_info * list) {
  232.   if (!list) return NULL;
  233.   while (list -> next) list = list -> next;
  234.   return list;
  235. }
  236.  
  237. void insert_node (struct file_info ** pFirst, struct file_info ** pLast, struct file_info * node) {
  238.   if (!node) return;
  239.   if (!*pFirst)
  240.     *pFirst = node;
  241.   else
  242.     (**pLast).next = node;
  243.   *pLast = find_last_node(node);
  244. }
  245.  
  246. void destroy_file_info (struct file_info * file_info) {
  247.   struct file_info * prev;
  248.   while (file_info) {
  249.     prev = file_info;
  250.     file_info = file_info -> next;
  251.     free(prev);
  252.   }
  253. }
  254.  
  255. void print_usage (const wchar_t * program_name) {
  256.   console_write_line(1, L"usage: ", program_name, L" [-RdfN?] file-mask [hex-string]", NULL);
  257.   exit(1);
  258. }
  259.  
  260. unsigned get_match_buffer (wchar_t ** cmdline, void ** pBuffer) {
  261.   if (*cmdline)
  262.     return get_match_from_cmdline(cmdline, pBuffer);
  263.   else
  264.     return get_match_from_input(pBuffer);
  265. }
  266.  
  267. unsigned get_match_from_cmdline (wchar_t ** cmdline, void ** pBuffer) {
  268.   char * match_buffer = NULL;
  269.   char * temp_buffer;
  270.   unsigned temp_length, length = 0;
  271.   *pBuffer = NULL;
  272.   for (; *cmdline; cmdline ++) {
  273.     temp_length = parse_match_item(*cmdline, (void **) &temp_buffer);
  274.     if (!temp_length) {
  275.       console_write_line(1, L"'", *cmdline, L"' is not a valid hexadecimal byte string", NULL);
  276.       free(match_buffer);
  277.       return 0;
  278.     }
  279.     match_buffer = realloc(match_buffer, length + temp_length);
  280.     memcpy(match_buffer + length, temp_buffer, temp_length);
  281.     length += temp_length;
  282.     free(temp_buffer);
  283.   }
  284.   *pBuffer = match_buffer;
  285.   return length;
  286. }
  287.  
  288. unsigned get_match_from_input (void ** pBuffer) {
  289.   console_write(1, L"Enter search string: ", NULL);
  290.   wchar_t * input_line = get_line();
  291.   wchar_t * current = input_line;
  292.   wchar_t * limit;
  293.   wchar_t * token;
  294.   char * result = NULL;
  295.   void * item;
  296.   unsigned item_length, length = 0;
  297.   while (*current) {
  298.     while (*current == L' ') current ++;
  299.     if (!*current) break;
  300.     limit = wcschr(current, L' ');
  301.     if (!limit) limit = current + wcslen(current);
  302.     token = malloc(sizeof(wchar_t) * (limit - current + 1));
  303.     memcpy(token, current, sizeof(wchar_t) * (limit - current));
  304.     token[limit - current] = 0;
  305.     item_length = parse_match_item(token, &item);
  306.     if (item_length) {
  307.       free(token);
  308.       result = realloc(result, length + item_length);
  309.       memcpy(result + length, item, item_length);
  310.       length += item_length;
  311.     } else {
  312.       console_write_line(1, L"'", token, L"' is not a valid hexadecimal byte string", NULL);
  313.       free(token);
  314.       free(result);
  315.       free(input_line);
  316.       *pBuffer = NULL;
  317.       return 0;
  318.     }
  319.     current = limit;
  320.   }
  321.   free(input_line);
  322.   *pBuffer = result;
  323.   return length;
  324. }
  325.  
  326. unsigned parse_match_item (wchar_t * item, void ** result) {
  327.   *result = NULL;
  328.   if (!(item && *item)) return 0;
  329.   unsigned length = wcslen(item);
  330.   if (length % 2) return 0;
  331.   length >>= 1;
  332.   unsigned char * buffer = malloc(length);
  333.   unsigned pos;
  334.   for (pos = 0; pos < length; pos ++) {
  335.     if ((hex_char_to_number(item[pos * 2]) < 0) || (hex_char_to_number(item[pos * 2 + 1]) < 0)) {
  336.       free(buffer);
  337.       return 0;
  338.     }
  339.     buffer[pos] = hex_char_to_number(item[pos * 2]) * 16 + hex_char_to_number(item[pos * 2 + 1]);
  340.   }
  341.   *result = buffer;
  342.   return length;
  343. }
  344.  
  345. wchar_t * get_line (void) {
  346.   wchar_t * line = NULL;
  347.   unsigned length = 0;
  348.   wchar_t character;
  349.   do {
  350.     character = console_get_char();
  351.     if ((character == L'\r') || (character == L'\n')) break;
  352.     line = realloc(line, sizeof(wchar_t) * (length + 1));
  353.     line[length ++] = character;
  354.   } while (character);
  355.   if (character == L'\r') console_get_char(); // Windows newline is \r\n, and of course the console doesn't convert that to \n
  356.   line = realloc(line, sizeof(wchar_t) * (length + 1));
  357.   line[length] = 0;
  358.   return line;
  359. }
  360.  
  361. void find_in_file (const struct file_info * file, const char * search_item, unsigned length, int options, unsigned char size_chars) {
  362.   const wchar_t * filename = (options & OPT_FILENAMES_ONLY) ? get_filename(file -> name) : file -> name;
  363.   HANDLE fh = CreateFile(file -> name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
  364.   if (fh == INVALID_HANDLE_VALUE) {
  365.     wchar_t * strbuf = repeat_character(L' ', size_chars - 5);
  366.     console_write_line(0, strbuf, L"error ", filename, NULL);
  367.     free(strbuf);
  368.     return;
  369.   }
  370.   char * read_buffer = NULL;
  371.   unsigned buffer_length = 4 << 20;
  372.   while ((buffer_length >> 1) < length) buffer_length <<= 1;
  373.   while (!read_buffer) {
  374.     if ((buffer_length >> 1) < length) {
  375.       console_write_line(1, L"*** out of memory ***", NULL);
  376.       abort();
  377.     }
  378.     read_buffer = malloc(buffer_length);
  379.     buffer_length >>= 1;
  380.   }
  381.   DWORD read_length; // uint32_t. Windows just doesn't like standard stuff. Reinventing the wheel FTW!
  382.   ReadFile(fh, read_buffer, buffer_length << 1, &read_length, NULL);
  383.   int found = 0;
  384.   if (read_length < (buffer_length << 1)) {
  385.     found = find_in_buffer(read_buffer, read_length, search_item, length, 0, filename, !(options & OPT_DECIMAL), 0, size_chars);
  386.     goto end; // you don't like goto? I don't like bracing and indenting lots of lines on an else just to avoid one. Moving on...
  387.   }
  388.   unsigned long long offset = 0;
  389.   do {
  390.     found |= find_in_buffer(read_buffer, buffer_length, search_item, length, offset, filename, !(options & OPT_DECIMAL), 1, size_chars);
  391.     offset += buffer_length;
  392.     memcpy(read_buffer, read_buffer + buffer_length, buffer_length);
  393.     ReadFile(fh, read_buffer + buffer_length, buffer_length, &read_length, NULL);
  394.   } while (read_length == buffer_length);
  395.   found |= find_in_buffer(read_buffer, buffer_length + read_length, search_item, length, offset, filename, !(options & OPT_DECIMAL), 0, size_chars);
  396.   end:
  397.   CloseHandle(fh);
  398.   free(read_buffer);
  399.   if ((options & OPT_SHOW_NO_MATCH) && !found) {
  400.     wchar_t * strbuf = repeat_character(L' ', size_chars - 8);
  401.     console_write_line(0, strbuf, L"no match ", filename, NULL);
  402.     free(strbuf);
  403.   }
  404. }
  405.  
  406. int find_in_buffer (const char * buffer, unsigned buffer_length, const char * search, unsigned search_length, unsigned long long base_offset,
  407.                      const wchar_t * filename, int use_hex, char more_data, unsigned char size_chars) {
  408.   // 9 parameters in a function, feels like I'm finally getting along with the Windows API
  409.   if (!search_length) return 0;
  410.   if (!more_data) {
  411.     if (buffer_length < search_length) return 0;
  412.     buffer_length -= search_length - 1;
  413.   }
  414.   unsigned pos;
  415.   int found = 0;
  416.   wchar_t * strbuf = malloc(sizeof(wchar_t) * (size_chars + 1));
  417.   for (pos = 0; pos < buffer_length; pos ++) {
  418.     if (memcmp(buffer + pos, search, search_length)) continue;
  419.     console_write_line(0, print_number(strbuf, base_offset + pos, use_hex, size_chars), L" ", filename, NULL);
  420.     found = 1;
  421.   }
  422.   free(strbuf);
  423.   return found;
  424. }
  425.  
  426. wchar_t * get_error_message (int error_code) {
  427.   wchar_t * error_message = malloc(1200 * sizeof(wchar_t));
  428.   unsigned rv = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM + FORMAT_MESSAGE_IGNORE_INSERTS + 79, NULL, error_code, 0, error_message, 1200, NULL);
  429.   // the above line does indeed return the error text for a certain code -- it also shows the API's efforts in aiding you with code obfuscation
  430.   return realloc(error_message, sizeof(wchar_t) * (1 + rv));
  431. }
  432.  
  433. unsigned long long max_file_size (struct file_info * files) {
  434.   unsigned long long max = 0;
  435.   while (files) {
  436.     if (files -> size > max) max = files -> size;
  437.     files = files -> next;
  438.   }
  439.   return max;
  440. }
  441.  
  442. unsigned char size_length (unsigned long long max_size, int use_hex) {
  443.   unsigned char result = 0;
  444.   while (max_size) {
  445.     result ++;
  446.     max_size /= use_hex ? 16 : 10;
  447.   }
  448.   if (result && !use_hex) result += (result - 1) / 3;
  449.   return (result < 8) ? 8 : result;
  450. }
  451.  
  452. wchar_t * repeat_character (wchar_t character, unsigned count) {
  453.   wchar_t * result = malloc(sizeof(wchar_t) * (count + 1));
  454.   wchar_t * current;
  455.   for (current = result; count; current ++, count --) *current = character;
  456.   *current = 0;
  457.   return result;
  458. }
  459.  
  460. void print_headers (unsigned char size_chars) {
  461.   wchar_t * padding = repeat_character(L' ', size_chars - 8);
  462.   console_write_line(0, L"Location", padding, L" Filename", NULL);
  463.   free(padding);
  464.   padding = repeat_character(L'-', size_chars);
  465.   console_write_line(0, padding, L" --------", NULL);
  466.   free(padding);
  467. }
  468.  
  469. const wchar_t * get_filename (const wchar_t * path) {
  470.   const wchar_t * backslash = get_dir_separator(path);
  471.   if (!backslash) return path;
  472.   return backslash + 1;
  473. }
  474.  
  475. wchar_t * print_number (wchar_t * buf, unsigned long long number, int use_hex, unsigned char size) {
  476.   wchar_t * current;
  477.   unsigned char digit;
  478.   for (current = buf; size; current ++, size --) *current = L' ';
  479.   *current = 0;
  480.   if (!number) {
  481.     current[-1] = L'0';
  482.     return buf;
  483.   }
  484.   use_hex = use_hex ? 16 : 10;
  485.   size = 0;
  486.   while (number) {
  487.     digit = number % use_hex;
  488.     number /= use_hex;
  489.     if ((use_hex == 10) && size && !(size % 3)) *(-- current) = L',';
  490.     size ++;
  491.     *(-- current) = L"0123456789ABCDEF"[digit];
  492.   }
  493.   return buf;
  494. }
  495.  
  496. const wchar_t * get_dir_separator (const wchar_t * path) {
  497.   const wchar_t * backslash = wcsrchr(path, L'\\');
  498.   const wchar_t * fwdslash = wcsrchr(path, L'/');
  499.   return (backslash > fwdslash) ? backslash : fwdslash;
  500. }
  501.  
  502. void show_help (const wchar_t * program_name) {
  503.   const wchar_t * help_text_1 = L"This program searches one or more files for a certain byte string.\r\n\r\n";
  504.   const wchar_t * invocation = L" [-RdfN?] file-mask [hex-string]\r\n\r\n";
  505.   const wchar_t * help_text_2 =
  506.     L"The file mask allows wildcards (? and *). The hex string must be a string of\r\n"
  507.     L"valid hexadecimal characters. Spaces are allowed as long as each token has an\r\n"
  508.     L"even number of characters (i.e., each token must contain an integral number of\r\n"
  509.     L"bytes). If no hex string is entered, the program will prompt for one.\r\n\r\n";
  510.   const wchar_t * options =
  511.     L"The options have the following meanings:\r\n"
  512.     L"  -R    search recursively (if a directory is found, search its contents)\r\n"
  513.     L"  -d    show offsets in decimal (default is hexadecimal)\r\n"
  514.     L"  -f    show filenames only (default is to show the path, relative or absolute\r\n"
  515.     L"        depending on the file mask)\r\n"
  516.     L"  -N    show also the files for which no match is found (\"no match\" will be\r\n"
  517.     L"        shown as offset)\r\n"
  518.     L"  --    stop parsing options (useful if the filename begins with -)\r\n"
  519.     L"  -?    show this help text and quit\r\n";
  520.   console_write(0, help_text_1, L"usage: ", program_name, invocation, help_text_2, options, NULL);
  521.   exit(0);
  522. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement