logo

utils-std

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

head.c (5551B)


  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/truncation.h" // apply_size_suffix
  6. #include <assert.h>
  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. #define MIN(a, b) (((a) < (b)) ? (a) : (b))
  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. if(buflen == 0)
  26. {
  27. // Note: de-allocated in main()
  28. buf = malloc(BUFSIZ);
  29. if(buf == NULL)
  30. {
  31. fprintf(stderr, "head: Failed to allocate buffer: %s\n", strerror(errno));
  32. return 1;
  33. }
  34. buflen = BUFSIZ;
  35. }
  36. int fd = -1;
  37. if(filename[0] == '-' && filename[1] == 0)
  38. {
  39. fd = STDIN_FILENO;
  40. }
  41. else
  42. {
  43. fd = open(filename, O_RDONLY | O_CLOEXEC | O_NOCTTY);
  44. if(fd < 0)
  45. {
  46. fprintf(stderr, "head: Failed opening file '%s': %s\n", filename, strerror(errno));
  47. return 1;
  48. }
  49. }
  50. int err = 0;
  51. for(size_t i = 0; i < bytes;)
  52. {
  53. ssize_t nread = read(fd, buf, MIN(buflen, bytes - i));
  54. if(nread < 0)
  55. {
  56. fprintf(stderr, "head: Failed reading file '%s': %s\n", filename, strerror(errno));
  57. err = 1;
  58. break;
  59. }
  60. if(nread == 0) break;
  61. if(write(STDOUT_FILENO, buf, nread) < 0)
  62. {
  63. fprintf(
  64. stderr, "head: Failed writing line from '%s' to stdout: %s\n", filename, strerror(errno));
  65. err = 1;
  66. break;
  67. }
  68. i += nread;
  69. }
  70. if(fd != STDIN_FILENO)
  71. if(close(fd) != 0)
  72. {
  73. fprintf(stderr, "head: Failed closing file '%s': %s\n", filename, strerror(errno));
  74. return 1;
  75. }
  76. return err;
  77. }
  78. static int
  79. copy_lines(const char *filename)
  80. {
  81. int err = 0;
  82. FILE *in = NULL;
  83. if(filename[0] == '-' && filename[1] == 0)
  84. {
  85. in = stdin;
  86. }
  87. else
  88. {
  89. in = fopen(filename, "r");
  90. if(in == NULL)
  91. {
  92. fprintf(stderr, "head: Failed opening file '%s': %s\n", filename, strerror(errno));
  93. return 1;
  94. }
  95. }
  96. for(size_t i = 0; i < lines; i++)
  97. {
  98. assert(errno == 0);
  99. ssize_t nread = getdelim(&buf, &buflen, delim, in);
  100. if(nread < 0)
  101. {
  102. if(errno == 0) break;
  103. fprintf(stderr, "head: Failed reading line from '%s': %s\n", filename, strerror(errno));
  104. err = 1;
  105. break;
  106. }
  107. if(write(STDOUT_FILENO, buf, nread) < 0)
  108. {
  109. fprintf(
  110. stderr, "head: Failed writing line from '%s' to stdout: %s\n", filename, strerror(errno));
  111. err = 1;
  112. break;
  113. }
  114. }
  115. if(in != stdin)
  116. if(fclose(in) != 0)
  117. {
  118. fprintf(stderr, "head: Failed closing file '%s': %s\n", filename, strerror(errno));
  119. return 1;
  120. }
  121. return err;
  122. }
  123. static void
  124. usage(void)
  125. {
  126. fprintf(stderr, "Usage: head [-qvz] [-c size | -n num | -num] [file...]\n");
  127. }
  128. int
  129. main(int argc, char *argv[])
  130. {
  131. int (*copy_action)(const char *filename) = copy_lines;
  132. int print_header = 0;
  133. while(optind < argc)
  134. {
  135. /* Skip getopt(3) for parsing -num option due to how it works:
  136. * - optstring = "12"; args="head -12" considered the same as -1 -2
  137. * - optstring = "1:"; args="head -12" considered the same as -1 2
  138. *
  139. * And we need args="head -12" as "12"
  140. */
  141. if(argv[optind] && argv[optind][0] == '-' && isdigit(argv[optind][1]))
  142. {
  143. assert(errno == 0);
  144. char *arg = argv[optind] + 1;
  145. char *endptr = NULL;
  146. lines = strtoul(arg, &endptr, 0);
  147. if(!(errno == 0 && endptr != NULL && *endptr == '\0'))
  148. {
  149. fprintf(stderr, "head: Error while parsing number for `-%s`: %s\n", arg, strerror(errno));
  150. return 1;
  151. }
  152. optind++;
  153. continue;
  154. }
  155. int c = getopt(argc, argv, ":qvc:n:z");
  156. if(c == -1) break;
  157. switch(c)
  158. {
  159. case 'q':
  160. print_header = -1;
  161. break;
  162. case 'v':
  163. print_header = 1;
  164. break;
  165. case 'c':
  166. {
  167. assert(errno == 0);
  168. char *endptr = NULL;
  169. unsigned long size = strtoul(optarg, &endptr, 0);
  170. if(errno != 0)
  171. {
  172. fprintf(
  173. stderr, "head: Error while parsing number for `-n %s`: %s\n", optarg, strerror(errno));
  174. return 1;
  175. }
  176. if(endptr != NULL && *endptr != 0)
  177. if(apply_size_suffix(&size, endptr) != 0) return 1;
  178. bytes = size;
  179. copy_action = &copy_bytes;
  180. break;
  181. }
  182. case 'n':
  183. {
  184. assert(errno == 0);
  185. char *endptr = NULL;
  186. lines = strtoul(optarg, &endptr, 0);
  187. if(!(errno == 0 && endptr != NULL && *endptr == '\0'))
  188. {
  189. fprintf(
  190. stderr, "head: Error while parsing number for `-n %s`: %s\n", optarg, strerror(errno));
  191. return 1;
  192. }
  193. break;
  194. }
  195. case 'z':
  196. delim = '\0';
  197. break;
  198. case ':':
  199. fprintf(stderr, "head: Error: Missing operand for option: '-%c'\n", optopt);
  200. usage();
  201. return 1;
  202. case '?':
  203. fprintf(stderr, "head: Error: Unrecognised option: '-%c'\n", 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. }