logo

utils-std

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

head.c (6875B)


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