| 114 | } |
| 115 | |
| 116 | char *cbm_mkdtemp(char *tmpl) { |
| 117 | /* Build path in static buffer, then copy back to caller. |
| 118 | * Callers must provide buffers >= CBM_SZ_256 bytes (all test code does). */ |
| 119 | static char buf[CBM_SZ_512]; |
| 120 | if (strncmp(tmpl, "/tmp/", 5) == 0) { |
| 121 | const char *tmp = getenv("TEMP"); |
| 122 | if (!tmp) |
| 123 | tmp = getenv("TMP"); |
| 124 | if (!tmp) |
| 125 | tmp = "."; |
| 126 | snprintf(buf, sizeof(buf), "%s\\%s", tmp, tmpl + 5); |
| 127 | } else { |
| 128 | snprintf(buf, sizeof(buf), "%s", tmpl); |
| 129 | } |
| 130 | /* Wide-API template expansion: the ANSI CRT interprets the UTF-8 bytes of |
| 131 | * non-ASCII cache/temp components in the local codepage and fails. */ |
| 132 | wchar_t *wide_template = cbm_utf8_to_wide(buf); |
| 133 | if (!wide_template || !_wmktemp(wide_template)) { |
| 134 | free(wide_template); |
| 135 | return NULL; |
| 136 | } |
| 137 | char *expanded = cbm_wide_to_utf8(wide_template); |
| 138 | free(wide_template); |
| 139 | if (!expanded || strlen(expanded) >= sizeof(buf)) { |
| 140 | free(expanded); |
| 141 | return NULL; |
| 142 | } |
| 143 | strcpy(buf, expanded); |
| 144 | free(expanded); |
| 145 | if (!win_mkdtemp_private_create(buf)) { |
| 146 | /* One-time note: every private-namespace validation downstream |
| 147 | * depends on the explicit descriptor, so a silent fallback turns |
| 148 | * into unexplained owner/DACL refusals far from this call site. */ |
| 149 | static bool fallback_reported; |
| 150 | DWORD create_error = GetLastError(); |
| 151 | wchar_t *wide_directory = cbm_utf8_to_wide(buf); |
| 152 | int mkdir_result = wide_directory ? _wmkdir(wide_directory) : -1; |
| 153 | free(wide_directory); |
| 154 | if (mkdir_result != 0) |
| 155 | return NULL; |
| 156 | if (!fallback_reported) { |
| 157 | fallback_reported = true; |
| 158 | (void)fprintf(stderr, |
| 159 | "warning: private temp-directory descriptor unavailable " |
| 160 | "(os %lu); using default directory security\n", |
| 161 | (unsigned long)create_error); |
| 162 | } |
| 163 | } |
| 164 | /* Normalize to forward slashes. Callers embed this path in JSON repo_path |
| 165 | * (where "\t"/"\a" are invalid escapes → index fails) and pass it to git -C. |
| 166 | * Windows file APIs accept forward slashes, so the created dir is unaffected. */ |
| 167 | for (char *p = buf; *p; p++) { |
| 168 | if (*p == '\\') { |
| 169 | *p = '/'; |
| 170 | } |
| 171 | } |
| 172 | /* Copy result back — callers now use char[CBM_SZ_256]+ buffers */ |
| 173 | strcpy(tmpl, buf); |