logo

utils-std

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

tee.c (3155B)


  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/getopt_nolong.h"
  7. #include <assert.h> /* assert() */
  8. #include <errno.h> /* errno */
  9. #include <fcntl.h>
  10. #include <signal.h> /* signal() */
  11. #include <stdio.h> /* fprintf() */
  12. #include <stdlib.h> /* calloc(), free(), abort() */
  13. #include <string.h> /* strerror() */
  14. #include <unistd.h> /* getopt(), opt… */
  15. #ifdef HAS_GETOPT_LONG
  16. #include <getopt.h>
  17. #endif
  18. const char *argv0 = "tee";
  19. static void
  20. cleanup(int *fds)
  21. {
  22. free(fds);
  23. }
  24. int
  25. main(int argc, char *argv[])
  26. {
  27. mode_t mode = O_WRONLY | O_NOCTTY | O_CREAT;
  28. int *fds = NULL;
  29. int c;
  30. #ifdef HAS_GETOPT_LONG
  31. // Strictly for GNUisms compatibility so no long-only options
  32. // clang-format off
  33. static struct option opts[] = {
  34. {"append", required_argument, NULL, 'a'},
  35. {"ignore-interrupts", required_argument, NULL, 'i'},
  36. {0, 0, 0, 0},
  37. };
  38. // clang-format on
  39. // Need + as first character to get POSIX-style option parsing
  40. for(int c = -1; (c = getopt_long(argc, argv, "+:ai", opts, NULL)) != -1;)
  41. #else
  42. for(int c = -1; (c = getopt_nolong(argc, argv, ":ai")) != -1;)
  43. #endif
  44. {
  45. switch(c)
  46. {
  47. case 'a':
  48. mode |= O_APPEND;
  49. break;
  50. case 'i': /* ignore SIGINT */;
  51. signal(SIGINT, SIG_IGN);
  52. break;
  53. case ':':
  54. fprintf(stderr, "tee: error: Missing operand for option: '-%c'\n", optopt);
  55. return 1;
  56. case '?':
  57. GETOPT_UNKNOWN_OPT
  58. return 1;
  59. default:
  60. abort();
  61. }
  62. }
  63. argc -= optind;
  64. argv += optind;
  65. if(argc > 0)
  66. {
  67. fds = calloc(argc, sizeof(*fds));
  68. if(!fds)
  69. {
  70. fprintf(stderr, "tee: error: Failed to allocate fd array: %s\n", strerror(errno));
  71. return 1;
  72. }
  73. }
  74. for(int argi = 0; argi < argc; argi++)
  75. {
  76. assert(argv[argi]);
  77. // POSIX: implementations shouldn't treat '-' as stdin
  78. fds[argi] = open(argv[argi], mode, 0666);
  79. if(fds[argi] < 0)
  80. {
  81. fprintf(stderr, "tee: error: Failed opening file ‘%s’: %s\n", argv[argi], strerror(errno));
  82. cleanup(fds);
  83. return 1;
  84. }
  85. }
  86. // main loop, note that failed writes shouldn't make tee exit
  87. int err = 0;
  88. while(true)
  89. {
  90. static char buf[BUFSIZ];
  91. ssize_t sread = read(STDIN_FILENO, buf, sizeof(buf));
  92. if(sread == 0) break;
  93. if(sread < 0)
  94. {
  95. fprintf(stderr, "tee: error: Failed reading from stdin: %s\n", strerror(errno));
  96. cleanup(fds);
  97. return 1;
  98. }
  99. size_t nread = (size_t)sread;
  100. if(write(STDOUT_FILENO, buf, nread) < 0)
  101. {
  102. fprintf(stderr, "tee: error: Failed writing to stdout: %s\n", strerror(errno));
  103. err = 1;
  104. errno = 0;
  105. }
  106. for(int argi = 0; argi < argc; argi++)
  107. {
  108. if(write(fds[argi], buf, nread) < 0)
  109. {
  110. fprintf(
  111. stderr, "tee: error: Failed writing to file '%s': %s\n", argv[argi], strerror(errno));
  112. err = 1;
  113. errno = 0;
  114. }
  115. }
  116. }
  117. // cleanup
  118. for(int argi = 0; argi < argc; argi++)
  119. {
  120. if(close(fds[argi]) != 0)
  121. {
  122. fprintf(stderr, "tee: error: Failed closing file '%s': %s\n", argv[argi], strerror(errno));
  123. err++;
  124. }
  125. }
  126. cleanup(fds);
  127. return err;
  128. }