1/*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. 9 * 10 * This software is licensed as described in the file COPYING, which 11 * you should have received as part of this distribution. The terms 12 * are also available at https://curl.se/docs/copyright.html. 13 * 14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell 15 * copies of the Software, and permit persons to whom the Software is 16 * furnished to do so, under the terms of the COPYING file. 17 * 18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 19 * KIND, either express or implied. 20 * 21 * SPDX-License-Identifier: curl 22 * 23 ***************************************************************************/ 24#include "tool_setup.h" 25 26#include <sys/stat.h> 27 28#ifdef _WIN32 29# include <direct.h> 30#endif 31 32#define ENABLE_CURLX_PRINTF 33/* use our own printf() functions */ 34#include "curlx.h" 35 36#include "tool_dirhie.h" 37#include "tool_msgs.h" 38 39#include "memdebug.h" /* keep this as LAST include */ 40 41#if defined(_WIN32) || (defined(MSDOS) && !defined(__DJGPP__)) 42# define mkdir(x,y) (mkdir)((x)) 43# ifndef F_OK 44# define F_OK 0 45# endif 46#endif 47 48static void show_dir_errno(struct GlobalConfig *global, const char *name) 49{ 50 switch(errno) { 51#ifdef EACCES 52 case EACCES: 53 errorf(global, "You don't have permission to create %s", name); 54 break; 55#endif 56#ifdef ENAMETOOLONG 57 case ENAMETOOLONG: 58 errorf(global, "The directory name %s is too long", name); 59 break; 60#endif 61#ifdef EROFS 62 case EROFS: 63 errorf(global, "%s resides on a read-only file system", name); 64 break; 65#endif 66#ifdef ENOSPC 67 case ENOSPC: 68 errorf(global, "No space left on the file system that will " 69 "contain the directory %s", name); 70 break; 71#endif 72#ifdef EDQUOT 73 case EDQUOT: 74 errorf(global, "Cannot create directory %s because you " 75 "exceeded your quota", name); 76 break; 77#endif 78 default: 79 errorf(global, "Error creating directory %s", name); 80 break; 81 } 82} 83 84/* 85 * Create the needed directory hierarchy recursively in order to save 86 * multi-GETs in file output, ie: 87 * curl "http://example.org/dir[1-5]/file[1-5].txt" -o "dir#1/file#2.txt" 88 * should create all the dir* automagically 89 */ 90 91#if defined(_WIN32) || defined(__DJGPP__) 92/* systems that may use either or when specifying a path */ 93#define PATH_DELIMITERS "\\/" 94#else 95#define PATH_DELIMITERS DIR_CHAR 96#endif 97 98 99CURLcode create_dir_hierarchy(const char *outfile, struct GlobalConfig *global) 100{ 101 char *tempdir; 102 char *tempdir2; 103 char *outdup; 104 char *dirbuildup; 105 CURLcode result = CURLE_OK; 106 size_t outlen; 107 108 outlen = strlen(outfile); 109 outdup = strdup(outfile); 110 if(!outdup) 111 return CURLE_OUT_OF_MEMORY; 112 113 dirbuildup = malloc(outlen + 1); 114 if(!dirbuildup) { 115 Curl_safefree(outdup); 116 return CURLE_OUT_OF_MEMORY; 117 } 118 dirbuildup[0] = '\0'; 119 120 /* Allow strtok() here since this isn't used threaded */ 121 /* !checksrc! disable BANNEDFUNC 2 */ 122 tempdir = strtok(outdup, PATH_DELIMITERS); 123 124 while(tempdir) { 125 bool skip = false; 126 tempdir2 = strtok(NULL, PATH_DELIMITERS); 127 /* since strtok returns a token for the last word even 128 if not ending with DIR_CHAR, we need to prune it */ 129 if(tempdir2) { 130 size_t dlen = strlen(dirbuildup); 131 if(dlen) 132 msnprintf(&dirbuildup[dlen], outlen - dlen, "%s%s", DIR_CHAR, tempdir); 133 else { 134 if(outdup == tempdir) { 135#if defined(_WIN32) || defined(MSDOS) 136 /* Skip creating a drive's current directory. 137 It may seem as though that would harmlessly fail but it could be 138 a corner case if X: did not exist, since we would be creating it 139 erroneously. 140 eg if outfile is X:\foo\bar\filename then don't mkdir X: 141 This logic takes into account unsupported drives !:, 1:, etc. */ 142 char *p = strchr(tempdir, ':'); 143 if(p && !p[1]) 144 skip = true; 145#endif 146 /* the output string doesn't start with a separator */ 147 strcpy(dirbuildup, tempdir); 148 } 149 else 150 msnprintf(dirbuildup, outlen, "%s%s", DIR_CHAR, tempdir); 151 } 152 /* Create directory. Ignore access denied error to allow traversal. */ 153 if(!skip && (-1 == mkdir(dirbuildup, (mode_t)0000750)) && 154 (errno != EACCES) && (errno != EEXIST)) { 155 show_dir_errno(global, dirbuildup); 156 result = CURLE_WRITE_ERROR; 157 break; /* get out of loop */ 158 } 159 } 160 tempdir = tempdir2; 161 } 162 163 Curl_safefree(dirbuildup); 164 Curl_safefree(outdup); 165 166 return result; 167} 168