logo

utils-std

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

head.c (6991B)


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