logo

utils-std

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

head.c (5331B)


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