logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/

wc.c (6592B)


  1. // utils-std: Collection of commonly available Unix tools
  2. // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
  3. // SPDX-License-Identifier: MPL-2.0
  4. #define _POSIX_C_SOURCE 200809L
  5. #include "../config.h" // HAS_*
  6. #include "../lib/bitmasks.h"
  7. #include <ctype.h> // isspace
  8. #include <errno.h>
  9. #include <fcntl.h> // posix_fadvise
  10. #include <locale.h> // setlocale
  11. #include <stdbool.h>
  12. #include <stdint.h> // uint8_t
  13. #include <stdio.h> // fprintf, fopen
  14. #include <stdlib.h> // abort
  15. #include <string.h> // strchr, strerror
  16. #include <sys/stat.h>
  17. #include <unistd.h> // getopt
  18. #include <wchar.h>
  19. #include <wctype.h> // iswspace
  20. #ifdef HAS_GETOPT_LONG
  21. #include <getopt.h>
  22. #endif
  23. #define WC_BUFSIZ 16320
  24. static char buf[WC_BUFSIZ] = "";
  25. static const char *argv0 = "wc";
  26. static enum {
  27. WC_OPT_C = 1 << 0,
  28. WC_OPT_L = 1 << 1,
  29. WC_OPT_W = 1 << 2,
  30. WC_OPT_ALL = WC_OPT_C | WC_OPT_L | WC_OPT_W,
  31. } wc_opts = 0;
  32. off_t total_bytes = 0, total_lines = 0, total_words = 0;
  33. static void
  34. print_counts(off_t lines, off_t words, off_t bytes, const char *filename)
  35. {
  36. const char *fmt = "%ld";
  37. if(FIELD_MATCH(wc_opts, WC_OPT_L))
  38. {
  39. printf(fmt, lines);
  40. fmt = " %ld";
  41. }
  42. if(FIELD_MATCH(wc_opts, WC_OPT_W))
  43. {
  44. printf(fmt, words);
  45. fmt = " %ld";
  46. }
  47. if(FIELD_MATCH(wc_opts, WC_OPT_C))
  48. {
  49. printf(fmt, bytes);
  50. }
  51. if(filename != NULL) printf(" %s", filename);
  52. printf("\n");
  53. }
  54. static int
  55. wc_file_bytes(int fd, char *filename)
  56. {
  57. off_t bytes = 0, lines = 0, words = 0, wordlen = 0;
  58. ssize_t nread = -1;
  59. if(FIELD_MATCH(wc_opts, WC_OPT_L) || FIELD_MATCH(wc_opts, WC_OPT_W))
  60. {
  61. while((nread = read(fd, buf, WC_BUFSIZ)) > 0)
  62. {
  63. bytes += nread;
  64. for(ssize_t i = 0; i < nread; i++)
  65. {
  66. int c = buf[i];
  67. if(c == '\n') lines++;
  68. if(isspace(c))
  69. {
  70. if(wordlen > 0)
  71. {
  72. words++;
  73. wordlen = 0;
  74. }
  75. }
  76. else
  77. {
  78. wordlen++;
  79. }
  80. }
  81. }
  82. }
  83. else
  84. {
  85. struct stat status;
  86. if(fstat(fd, &status) < 0)
  87. {
  88. fprintf(stderr,
  89. "%s: error: Failed getting status for file '%s': %s\n",
  90. argv0,
  91. filename,
  92. strerror(errno));
  93. return 1;
  94. }
  95. if(S_ISREG(status.st_mode) && status.st_size != 0)
  96. {
  97. bytes += status.st_size;
  98. }
  99. else
  100. {
  101. while((nread = read(fd, buf, WC_BUFSIZ)) > 0)
  102. bytes += nread;
  103. }
  104. }
  105. if(nread < 0 && errno != 0)
  106. {
  107. fprintf(stderr,
  108. "%s: error: Failed reading from file '%s': %s\n",
  109. argv0,
  110. filename != NULL ? filename : "<stdin>",
  111. strerror(errno));
  112. return -1;
  113. }
  114. if(wordlen > 0) words++;
  115. total_bytes += bytes, total_lines += lines, total_words += words;
  116. print_counts(lines, words, bytes, filename);
  117. return 0;
  118. }
  119. static int
  120. wc_file_chars(int fd, char *filename)
  121. {
  122. off_t chars = 0, lines = 0, words = 0, wordlen = 0;
  123. FILE *file = fdopen(fd, "r");
  124. if(file == NULL)
  125. {
  126. fprintf(stderr,
  127. "%s: error: Failed getting file stream for file '%s': %s\n",
  128. argv0,
  129. filename,
  130. strerror(errno));
  131. return -1;
  132. }
  133. if(setvbuf(file, buf, _IOFBF, WC_BUFSIZ) != 0)
  134. {
  135. fprintf(stderr,
  136. "%s: warning: Failed setting a new buffer for <stdin>: %s\n",
  137. argv0,
  138. strerror(errno));
  139. errno = 0;
  140. }
  141. while(true)
  142. {
  143. wint_t c = getwc(file);
  144. if(c == WEOF)
  145. {
  146. if(errno != 0)
  147. {
  148. fprintf(stderr,
  149. "%s: error: Failed reading from file '%s': %s\n",
  150. argv0,
  151. filename != NULL ? filename : "<stdin>",
  152. strerror(errno));
  153. return -1;
  154. }
  155. break;
  156. }
  157. chars++;
  158. if(c == '\n') lines++;
  159. if(iswspace(c))
  160. {
  161. if(wordlen > 0)
  162. {
  163. words++;
  164. wordlen = 0;
  165. }
  166. }
  167. else
  168. {
  169. wordlen++;
  170. }
  171. }
  172. if(wordlen > 0) words++;
  173. print_counts(lines, words, chars, filename);
  174. return 0;
  175. }
  176. static void
  177. usage(void)
  178. {
  179. fprintf(stderr, "Usage: wc [-c|-m] [-lw] [file...]\n");
  180. }
  181. int
  182. main(int argc, char *argv[])
  183. {
  184. setlocale(LC_ALL, "");
  185. errno = 0;
  186. int (*wc_file)(int, char *) = &wc_file_bytes;
  187. #ifdef HAS_GETOPT_LONG
  188. // Strictly for GNUisms compatibility so no long-only options
  189. // clang-format off
  190. static struct option opts[] = {
  191. {"bytes", no_argument, 0, 'c'},
  192. {"lines", no_argument, 0, 'l'},
  193. {"chars", no_argument, 0, 'm'},
  194. {"words", no_argument, 0, 'w'},
  195. {0, 0, 0, 0},
  196. };
  197. // clang-format on
  198. // Need + as first character to get POSIX-style option parsing
  199. for(int c = -1; (c = getopt_long(argc, argv, "+:clmw", opts, NULL)) != -1;)
  200. #else
  201. for(int c = -1; (c = getopt(argc, argv, ":clmw")) != -1;)
  202. #endif
  203. {
  204. switch(c)
  205. {
  206. case 'c':
  207. wc_opts |= WC_OPT_C;
  208. wc_file = wc_file_bytes;
  209. break;
  210. case 'l':
  211. wc_opts |= WC_OPT_L;
  212. break;
  213. case 'm':
  214. wc_opts |= WC_OPT_C;
  215. wc_file = wc_file_chars;
  216. break;
  217. case 'w':
  218. wc_opts |= WC_OPT_W;
  219. break;
  220. case ':':
  221. fprintf(stderr, "%s: error: Missing operand for option: '-%c'\n", argv0, optopt);
  222. usage();
  223. return 1;
  224. case '?':
  225. fprintf(stderr, "%s: error: Unrecognised option: '-%c'\n", argv0, optopt);
  226. usage();
  227. return 1;
  228. default:
  229. abort();
  230. }
  231. }
  232. if(wc_opts == 0) wc_opts = WC_OPT_ALL;
  233. argc -= optind;
  234. argv += optind;
  235. if((errno = posix_fadvise(STDIN_FILENO, 0, 0, POSIX_FADV_SEQUENTIAL)) != 0)
  236. {
  237. if(errno != ESPIPE)
  238. {
  239. fprintf(stderr,
  240. "%s: warning: Failure from posix_fadvise sequential for <stdin>: %s\n",
  241. argv0,
  242. strerror(errno));
  243. }
  244. errno = 0;
  245. }
  246. if(argc < 1)
  247. {
  248. if(wc_file(STDIN_FILENO, NULL) < 0) return 1;
  249. }
  250. for(int i = 0; i < argc; i++)
  251. {
  252. char *path = argv[i];
  253. if(path[0] == '-' && path[1] == 0)
  254. {
  255. if(wc_file(STDIN_FILENO, NULL) < 0) return 1;
  256. continue;
  257. }
  258. // https://www.austingroupbugs.net/view.php?id=251
  259. if(strchr(path, '\n') != NULL)
  260. fprintf(
  261. stderr,
  262. "%s: warning: Filename '%s' contains a newline while wc(1) uses newlines as separators\n",
  263. argv0,
  264. path);
  265. int arg_fd = open(path, O_RDONLY | O_NOCTTY);
  266. if(arg_fd < 0)
  267. {
  268. fprintf(stderr, "%s: error: Failed opening file '%s': %s\n", argv0, path, strerror(errno));
  269. return 1;
  270. }
  271. if((errno = posix_fadvise(arg_fd, 0, 0, POSIX_FADV_SEQUENTIAL)) != 0)
  272. {
  273. fprintf(stderr,
  274. "%s: warning: Failure from posix_fadvise sequential for file '%s': %s\n",
  275. argv0,
  276. path,
  277. strerror(errno));
  278. errno = 0;
  279. }
  280. if(wc_file(arg_fd, path) < 0) return 1;
  281. if(close(arg_fd) < 0)
  282. {
  283. fprintf(stderr, "%s: error: Failed closing file '%s': %s\n", argv0, path, strerror(errno));
  284. return 1;
  285. }
  286. }
  287. if(argc > 1) print_counts(total_lines, total_words, total_bytes, "total");
  288. return 0;
  289. }