logo

utils-std

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

mktemp.c (4768B)


  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. // getentropy got in POSIX.1-2024 but defining _POSIX_C_SOURCE causes too much side-effects
  5. #define _DEFAULT_SOURCE // getentropy
  6. #include "../config.h" // HAS_*
  7. #include "../libutils/getopt_nolong.h"
  8. #include "../libutils/lib_string.h"
  9. #include <errno.h>
  10. #include <limits.h> // PATH_MAX
  11. #include <stdbool.h>
  12. #include <stdio.h> // fprintf
  13. #include <stdlib.h> // getenv
  14. #include <string.h> // strerror
  15. #include <unistd.h> // getopt, getentropy
  16. #ifdef HAS_GETOPT_LONG
  17. #include <getopt.h>
  18. #endif
  19. const char *argv0 = "mktemp";
  20. static int
  21. unsafe_mktemp(char *template, size_t suffix)
  22. {
  23. size_t l = strlen(template);
  24. size_t len = 0;
  25. size_t off = l - 1 - suffix;
  26. while(off > 0 && template[off] == 'X')
  27. off--, len++;
  28. if(len < 6)
  29. {
  30. fprintf(stderr,
  31. "%s: error: Template '%s' is invalid (need >= 6 final X, got %zd)\n",
  32. argv0,
  33. template,
  34. len);
  35. return 1;
  36. }
  37. for(size_t i = 0; i < l; i++)
  38. {
  39. if(template[i] == '\n')
  40. {
  41. fprintf(stderr, "%s: error: Invalid character (newline) in template\n", argv0);
  42. return 1;
  43. }
  44. }
  45. for(int retries = 100; 0 < retries; retries--)
  46. {
  47. unsigned long r = 0;
  48. if(getentropy(&r, sizeof(r)) != 0)
  49. {
  50. fprintf(stderr,
  51. "%s: error: Failed to get entropy for random filename: %s\n",
  52. argv0,
  53. strerror(errno));
  54. return 1;
  55. }
  56. for(int i = len; i > 0; i--, r >>= 5)
  57. template[off + i] = 'A' + (r & 15) + (r & 16) * 2;
  58. if(access(template, F_OK) != 0 && errno == ENOENT)
  59. {
  60. puts(template);
  61. return 0;
  62. }
  63. }
  64. fprintf(stderr, "%s: error: Exhausted all 100 attempts at generating a filename\n", argv0);
  65. return 1;
  66. }
  67. int
  68. main(int argc, char *argv[])
  69. {
  70. bool o_create_dir = false, o_quiet = false, o_unsafe = false;
  71. static char template[PATH_MAX] = "./tmp.XXXXXXXXXX";
  72. const char *tmpdir = NULL;
  73. #ifdef HAS_GETOPT_LONG
  74. // Strictly for GNUisms compatibility so no long-only options
  75. // clang-format off
  76. static struct option opts[] = {
  77. {"directory", no_argument, NULL, 'd'},
  78. {"dry-run", required_argument, NULL, 'u'},
  79. {"quiet", no_argument, NULL, 'q'},
  80. {"tmpdir", required_argument, NULL, 'p'},
  81. {0, 0, 0, 0},
  82. };
  83. // clang-format on
  84. // Need + as first character to get POSIX-style option parsing
  85. for(int c = -1; (c = getopt_long(argc, argv, "+:dqp:tu", opts, NULL)) != -1;)
  86. #else
  87. for(int c = -1; (c = getopt_nolong(argc, argv, ":dqp:tu")) != -1;)
  88. #endif
  89. {
  90. switch(c)
  91. {
  92. case 'd':
  93. o_create_dir = true;
  94. break;
  95. case 'q':
  96. o_quiet = true;
  97. break;
  98. case 'p':
  99. tmpdir = optarg;
  100. break;
  101. case 't':
  102. if(tmpdir == NULL) tmpdir = getenv("TMPDIR");
  103. if(tmpdir == NULL) tmpdir = "/tmp";
  104. break;
  105. case 'u':
  106. o_unsafe = true;
  107. break;
  108. case '?':
  109. GETOPT_UNKNOWN_OPT
  110. return 1;
  111. default:
  112. fprintf(stderr, "%s: error: Unhandled getopt case '%c'\n", argv0, c);
  113. abort();
  114. }
  115. }
  116. argc -= optind;
  117. argv += optind;
  118. if(argc == 1)
  119. {
  120. lib_strlcpy(template, *argv, PATH_MAX);
  121. }
  122. else if(argc > 1)
  123. {
  124. fprintf(stderr, "%s: error: Only one template argument is supported, got %d\n", argv0, argc);
  125. return 1;
  126. }
  127. if(tmpdir)
  128. {
  129. if(chdir(tmpdir) < 0)
  130. {
  131. fprintf(stderr,
  132. "%s: error: Failed changing directory into tmpdir '%s': %s\n",
  133. argv0,
  134. tmpdir,
  135. strerror(errno));
  136. return 1;
  137. }
  138. printf("%s/", tmpdir);
  139. }
  140. char *suffix_start = strrchr(template, 'X');
  141. if(!suffix_start)
  142. {
  143. fprintf(stderr, "%s: error: template '%s' does not contains any 'X'\n", argv0, template);
  144. return 1;
  145. }
  146. size_t suffix_len = strlen(suffix_start + 1);
  147. static char template_copy[PATH_MAX] = "";
  148. memcpy(template_copy, template, PATH_MAX);
  149. if(o_unsafe) return unsafe_mktemp(template, suffix_len);
  150. if(o_create_dir)
  151. {
  152. #ifdef HAS_MKDTEMPS
  153. char *dir = mkdtemps(template, suffix_len);
  154. #else
  155. char *dir = mkdtemp(template);
  156. #endif
  157. if(dir == NULL)
  158. {
  159. if(!o_quiet)
  160. fprintf(stderr,
  161. "%s: error: Failed creating random directory with template '%s': %s\n",
  162. argv0,
  163. template_copy,
  164. strerror(errno));
  165. return 1;
  166. }
  167. puts(dir);
  168. return 0;
  169. }
  170. #ifdef HAS_MKSTEMPS
  171. int fd = mkstemps(template, suffix_len);
  172. #else
  173. int fd = mkstemp(template);
  174. #endif
  175. if(fd < 0)
  176. {
  177. if(!o_quiet)
  178. fprintf(stderr,
  179. "%s: error: Failed creating random file with template '%s': %s\n",
  180. argv0,
  181. template_copy,
  182. strerror(errno));
  183. return 1;
  184. }
  185. puts(template);
  186. if(close(fd) < 0)
  187. {
  188. if(!o_quiet) fprintf(stderr, "%s: error: Failed closing file: %s\n", argv0, strerror(errno));
  189. return 1;
  190. }
  191. return 0;
  192. }