logo

utils-std

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

head.c (5442B)


  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 "../lib/fs.h" // auto_fd_copy
  6. #include "../lib/truncation.h" // apply_size_suffix
  7. #include <assert.h>
  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. size_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. }
  40. int err = 0;
  41. if(auto_fd_copy(fd, STDOUT_FILENO, bytes) < 0)
  42. {
  43. fprintf(stderr,
  44. "%s: error: Failed copying data from '%s' to <stdout>: %s\n",
  45. argv0,
  46. filename,
  47. strerror(errno));
  48. err = 1;
  49. }
  50. if(fd != STDIN_FILENO)
  51. if(close(fd) != 0)
  52. {
  53. fprintf(
  54. stderr, "%s: error: Failed closing file '%s': %s\n", argv0, filename, strerror(errno));
  55. return 1;
  56. }
  57. return err;
  58. }
  59. static int
  60. copy_lines(const char *filename)
  61. {
  62. int err = 0;
  63. FILE *in = NULL;
  64. if(filename[0] == '-' && filename[1] == 0)
  65. {
  66. in = stdin;
  67. }
  68. else
  69. {
  70. in = fopen(filename, "r");
  71. if(in == NULL)
  72. {
  73. fprintf(
  74. stderr, "%s: error: Failed opening file '%s': %s\n", argv0, filename, strerror(errno));
  75. return 1;
  76. }
  77. }
  78. for(size_t i = 0; i < lines; i++)
  79. {
  80. assert(errno == 0);
  81. ssize_t nread = getdelim(&buf, &buflen, delim, in);
  82. if(nread < 0)
  83. {
  84. if(errno == 0) break;
  85. fprintf(stderr,
  86. "%s: error: Failed reading line from '%s': %s\n",
  87. argv0,
  88. filename,
  89. strerror(errno));
  90. err = 1;
  91. break;
  92. }
  93. if(write(STDOUT_FILENO, buf, nread) < 0)
  94. {
  95. fprintf(stderr,
  96. "%s: error: Failed writing line from '%s' to stdout: %s\n",
  97. argv0,
  98. filename,
  99. strerror(errno));
  100. err = 1;
  101. break;
  102. }
  103. }
  104. if(in != stdin)
  105. if(fclose(in) != 0)
  106. {
  107. fprintf(
  108. stderr, "%s: error: Failed closing file '%s': %s\n", argv0, filename, strerror(errno));
  109. return 1;
  110. }
  111. return err;
  112. }
  113. static void
  114. usage(void)
  115. {
  116. fprintf(stderr, "Usage: head [-qvz] [-c size | -n num | -num] [file...]\n");
  117. }
  118. int
  119. main(int argc, char *argv[])
  120. {
  121. int (*copy_action)(const char *filename) = copy_lines;
  122. int print_header = 0;
  123. while(optind < argc)
  124. {
  125. /* Skip getopt(3) for parsing -num option due to how it works:
  126. * - optstring = "12"; args="head -12" considered the same as -1 -2
  127. * - optstring = "1:"; args="head -12" considered the same as -1 2
  128. *
  129. * And we need args="head -12" as "12"
  130. */
  131. if(argv[optind] && argv[optind][0] == '-' && isdigit(argv[optind][1]))
  132. {
  133. assert(errno == 0);
  134. char *arg = argv[optind] + 1;
  135. char *endptr = NULL;
  136. lines = strtoul(arg, &endptr, 0);
  137. if(!(errno == 0 && endptr != NULL && *endptr == '\0'))
  138. {
  139. fprintf(stderr,
  140. "%s: error: Failed parsing number for `-%s`: %s\n",
  141. argv0,
  142. arg,
  143. strerror(errno));
  144. return 1;
  145. }
  146. optind++;
  147. continue;
  148. }
  149. int c = getopt(argc, argv, ":qvc:n:z");
  150. if(c == -1) break;
  151. switch(c)
  152. {
  153. case 'q':
  154. print_header = -1;
  155. break;
  156. case 'v':
  157. print_header = 1;
  158. break;
  159. case 'c':
  160. {
  161. assert(errno == 0);
  162. char *endptr = NULL;
  163. unsigned long size = strtoul(optarg, &endptr, 0);
  164. if(errno != 0)
  165. {
  166. fprintf(stderr,
  167. "%s: error: Failed parsing number for `-n %s`: %s\n",
  168. argv0,
  169. optarg,
  170. strerror(errno));
  171. return 1;
  172. }
  173. if(endptr != NULL && *endptr != 0)
  174. if(apply_size_suffix(&size, endptr) != 0) return 1;
  175. bytes = size;
  176. copy_action = &copy_bytes;
  177. break;
  178. }
  179. case 'n':
  180. {
  181. assert(errno == 0);
  182. char *endptr = NULL;
  183. lines = strtoul(optarg, &endptr, 0);
  184. if(!(errno == 0 && endptr != NULL && *endptr == '\0'))
  185. {
  186. fprintf(stderr,
  187. "%s: error: Failed parsing number for `-n %s`: %s\n",
  188. argv0,
  189. optarg,
  190. strerror(errno));
  191. return 1;
  192. }
  193. break;
  194. }
  195. case 'z':
  196. delim = '\0';
  197. break;
  198. case ':':
  199. fprintf(stderr, "%s: error: Missing operand for option: '-%c'\n", argv0, optopt);
  200. usage();
  201. return 1;
  202. case '?':
  203. fprintf(stderr, "%s: error: Unrecognised option: '-%c'\n", argv0, optopt);
  204. usage();
  205. return 1;
  206. default:
  207. abort();
  208. }
  209. }
  210. argc -= optind;
  211. argv += optind;
  212. if(argc <= 0)
  213. {
  214. const char *filename = "-";
  215. if(print_header == 1)
  216. {
  217. printf(header_fmt, filename);
  218. fflush(stdout);
  219. }
  220. int err = copy_action(filename);
  221. if(buflen != 0) free(buf);
  222. return err;
  223. }
  224. if(print_header == 0) print_header = argc > 1;
  225. int err = 0;
  226. for(int i = 0; i < argc; i++)
  227. {
  228. char *filename = argv[i];
  229. if(print_header > 0)
  230. {
  231. printf(header_fmt, filename);
  232. header_fmt = "\n==> %s <==\n";
  233. fflush(stdout);
  234. }
  235. if(copy_action(filename) != 0)
  236. {
  237. err = 1;
  238. break;
  239. }
  240. }
  241. if(buflen != 0) free(buf);
  242. return err;
  243. }