logo

utils-std

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

head.c (7579B)


  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"
  6. #include "../libutils/fs.h" // auto_fd_copy
  7. #include "../libutils/getopt_nolong.h"
  8. #include "../libutils/truncation.h" // apply_size_suffix
  9. #include <assert.h>
  10. #include <ctype.h> // isdigit
  11. #include <errno.h>
  12. #include <fcntl.h> // open
  13. #include <stdio.h> // fprintf, fopen, fread
  14. #include <stdlib.h> // strtoul
  15. #include <string.h> // strerror
  16. #include <unistd.h> // read
  17. #ifdef HAS_GETOPT_LONG
  18. #include <getopt.h>
  19. #endif
  20. static const char *header_fmt = "==> %s <==\n";
  21. const char *argv0 = "head";
  22. ssize_t lines = 10;
  23. size_t bytes = 0;
  24. char *buf = NULL;
  25. size_t buflen = 0;
  26. int delim = '\n';
  27. static int
  28. copy_bytes(const char *filename)
  29. {
  30. int fd = -1;
  31. if(filename[0] == '-' && filename[1] == 0)
  32. {
  33. fd = STDIN_FILENO;
  34. }
  35. else
  36. {
  37. fd = open(filename, O_RDONLY | O_CLOEXEC | O_NOCTTY);
  38. if(fd < 0)
  39. {
  40. fprintf(
  41. stderr, "%s: error: Failed opening file '%s': %s\n", argv0, filename, strerror(errno));
  42. return 1;
  43. }
  44. posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);
  45. }
  46. int err = 0;
  47. if(auto_fd_copy(fd, STDOUT_FILENO, bytes) < 0)
  48. {
  49. fprintf(stderr,
  50. "%s: error: Failed copying data from '%s' to <stdout>: %s\n",
  51. argv0,
  52. filename,
  53. strerror(errno));
  54. err = 1;
  55. }
  56. if(fd != STDIN_FILENO)
  57. if(close(fd) != 0)
  58. {
  59. fprintf(
  60. stderr, "%s: error: Failed closing file '%s': %s\n", argv0, filename, strerror(errno));
  61. return 1;
  62. }
  63. return err;
  64. }
  65. static int
  66. copy_lines(const char *filename)
  67. {
  68. int err = 0;
  69. FILE *in = NULL;
  70. if(filename[0] == '-' && filename[1] == 0)
  71. {
  72. in = stdin;
  73. }
  74. else
  75. {
  76. in = fopen(filename, "r");
  77. if(in == NULL)
  78. {
  79. fprintf(
  80. stderr, "%s: error: Failed opening file '%s': %s\n", argv0, filename, strerror(errno));
  81. return 1;
  82. }
  83. assert(in != stdin);
  84. }
  85. // Relative to EOF, GNU extension
  86. if(lines < 0)
  87. {
  88. struct strbuf
  89. {
  90. size_t len;
  91. size_t cap;
  92. char *buf;
  93. };
  94. size_t nlines = (size_t)-lines;
  95. size_t wini = 0;
  96. struct strbuf *win = calloc(nlines, sizeof(struct strbuf));
  97. if(!win)
  98. {
  99. fprintf(stderr, "%s: error: Failed allocating lines array: %s\n", argv0, strerror(errno));
  100. err = 1;
  101. goto copy_lines_end;
  102. }
  103. while(true)
  104. {
  105. ssize_t ret = getdelim(&buf, &buflen, delim, in);
  106. if(ret < 0)
  107. {
  108. if(errno == 0) break;
  109. fprintf(stderr,
  110. "%s: error: Failed reading line from '%s': %s\n",
  111. argv0,
  112. filename,
  113. strerror(errno));
  114. err = 1;
  115. break;
  116. }
  117. size_t nread = ret;
  118. if(win[wini].len)
  119. {
  120. if(write(STDOUT_FILENO, win[wini].buf, win[wini].len) < 0)
  121. {
  122. fprintf(stderr,
  123. "%s: error: Failed writing line from '%s' to stdout: %s\n",
  124. argv0,
  125. filename,
  126. strerror(errno));
  127. err = 1;
  128. break;
  129. }
  130. }
  131. if(nread == 0) continue;
  132. if(win[wini].cap < nread)
  133. {
  134. win[wini].buf = realloc(win[wini].buf, nread);
  135. if(!win[wini].buf)
  136. {
  137. fprintf(
  138. stderr, "%s: error: Failed (re)allocating line buffer: %s\n", argv0, strerror(errno));
  139. err = 1;
  140. break;
  141. }
  142. win[wini].cap = nread;
  143. }
  144. win[wini].len = nread;
  145. memcpy(win[wini].buf, buf, nread);
  146. wini = (wini + 1) % nlines;
  147. }
  148. for(size_t i = 0; i < nlines; i++)
  149. free(win[i].buf);
  150. free(win);
  151. }
  152. else
  153. {
  154. size_t nlines = lines;
  155. for(size_t i = 0; i < nlines; i++)
  156. {
  157. ssize_t nread = getdelim(&buf, &buflen, delim, in);
  158. if(nread < 0)
  159. {
  160. if(errno == 0) break;
  161. fprintf(stderr,
  162. "%s: error: Failed reading line from '%s': %s\n",
  163. argv0,
  164. filename,
  165. strerror(errno));
  166. err = 1;
  167. break;
  168. }
  169. if(write(STDOUT_FILENO, buf, nread) < 0)
  170. {
  171. fprintf(stderr,
  172. "%s: error: Failed writing line from '%s' to stdout: %s\n",
  173. argv0,
  174. filename,
  175. strerror(errno));
  176. err = 1;
  177. break;
  178. }
  179. }
  180. }
  181. copy_lines_end:
  182. if(in != stdin)
  183. if(fclose(in) != 0)
  184. {
  185. fprintf(
  186. stderr, "%s: error: Failed closing file '%s': %s\n", argv0, filename, strerror(errno));
  187. return 1;
  188. }
  189. return err;
  190. }
  191. static void
  192. usage(void)
  193. {
  194. fprintf(stderr, "Usage: head [-qvz] [-c size | -n num | -num] [file...]\n");
  195. }
  196. int
  197. main(int argc, char *argv[])
  198. {
  199. int (*copy_action)(const char *filename) = copy_lines;
  200. int print_header = 0;
  201. while(optind < argc)
  202. {
  203. /* Skip getopt(3) for parsing -num option due to how it works:
  204. * - optstring = "12"; args="head -12" considered the same as -1 -2
  205. * - optstring = "1:"; args="head -12" considered the same as -1 2
  206. *
  207. * And we need args="head -12" as "12"
  208. */
  209. if(argv[optind] && argv[optind][0] == '-' && isdigit(argv[optind][1]))
  210. {
  211. char *arg = argv[optind] + 1;
  212. char *endptr = NULL;
  213. lines = strtol(arg, &endptr, 0);
  214. if(!(errno == 0 && endptr != NULL && *endptr == '\0'))
  215. {
  216. fprintf(stderr,
  217. "%s: error: Failed parsing number for `-%s`: %s\n",
  218. argv0,
  219. arg,
  220. strerror(errno));
  221. return 1;
  222. }
  223. optind++;
  224. continue;
  225. }
  226. #ifdef HAS_GETOPT_LONG
  227. // Strictly for GNUisms compatibility so no long-only options
  228. // clang-format off
  229. static struct option opts[] = {
  230. {"bytes", required_argument, NULL, 'c'},
  231. {"lines", optional_argument, NULL, 'n'},
  232. {"quiet", no_argument, NULL, 'q'},
  233. {"silent", no_argument, NULL, 'q'},
  234. {"zero-terminated", no_argument, NULL, 'z'},
  235. {0, 0, 0, 0},
  236. };
  237. // clang-format on
  238. // Need + as first character to get POSIX-style option parsing
  239. int c = getopt_long(argc, argv, "+:qvc:n:z", opts, NULL);
  240. #else
  241. int c = getopt_nolong(argc, argv, ":qvc:n:z");
  242. #endif
  243. if(c == -1) break;
  244. switch(c)
  245. {
  246. case 'q':
  247. print_header = -1;
  248. break;
  249. case 'v':
  250. print_header = 1;
  251. break;
  252. case 'c':
  253. {
  254. char *endptr = NULL;
  255. unsigned long size = strtoul(optarg, &endptr, 0);
  256. if(errno != 0)
  257. {
  258. fprintf(stderr,
  259. "%s: error: Failed parsing number for `-n %s`: %s\n",
  260. argv0,
  261. optarg,
  262. strerror(errno));
  263. return 1;
  264. }
  265. if(endptr != NULL && *endptr != 0)
  266. if(apply_size_suffix(&size, endptr) != 0) return 1;
  267. bytes = size;
  268. copy_action = &copy_bytes;
  269. break;
  270. }
  271. case 'n':
  272. {
  273. char *endptr = NULL;
  274. lines = strtol(optarg, &endptr, 0);
  275. if(!(errno == 0 && endptr != NULL && *endptr == '\0'))
  276. {
  277. fprintf(stderr,
  278. "%s: error: Failed parsing number for `-n %s`: %s\n",
  279. argv0,
  280. optarg,
  281. strerror(errno));
  282. return 1;
  283. }
  284. break;
  285. }
  286. case 'z':
  287. delim = '\0';
  288. break;
  289. case ':':
  290. fprintf(stderr, "%s: error: Missing operand for option: '-%c'\n", argv0, optopt);
  291. usage();
  292. return 1;
  293. case '?':
  294. GETOPT_UNKNOWN_OPT
  295. usage();
  296. return 1;
  297. default:
  298. abort();
  299. }
  300. }
  301. argc -= optind;
  302. argv += optind;
  303. if(argc <= 0)
  304. {
  305. const char *filename = "-";
  306. if(print_header == 1)
  307. {
  308. printf(header_fmt, filename);
  309. fflush(stdout);
  310. }
  311. int err = copy_action(filename);
  312. free(buf);
  313. return err;
  314. }
  315. if(print_header == 0) print_header = argc > 1;
  316. int err = 0;
  317. for(int i = 0; i < argc; i++)
  318. {
  319. char *filename = argv[i];
  320. if(print_header > 0)
  321. {
  322. printf(header_fmt, filename);
  323. header_fmt = "\n==> %s <==\n";
  324. fflush(stdout);
  325. }
  326. if(copy_action(filename) != 0)
  327. {
  328. err = 1;
  329. break;
  330. }
  331. }
  332. free(buf);
  333. return err;
  334. }