44

I'm trying to create a function that takes the name of a directory (C:\foo\bar, or ..\foo\bar\..\baz, or \\someserver\foo\bar), and creates directories as necessary so that the whole path is created.

I am attempting a pretty naive implementation of this myself and it seems to be a string processing nightmare. There is / vs \, there is the special case of network shares which begin with \\ (also you can't attempt to mkdir() the first two levels of the path which are machine name and share name), and there is \.\ type nonsense that can exist in a path.

Does there exist a simple way to do this in C++?

pauldoo
  • 18,087
  • 20
  • 94
  • 116

14 Answers14

61

If you don't need to support Windows versions prior to Windows 2000, you can use the SHCreateDirectoryEx function for this. Consider this:

int createDirectoryRecursively( LPCTSTR path )
{
    return SHCreateDirectoryEx( NULL, path, NULL );
}

// ...
if ( createDirectoryRecursively( T("C:\\Foo\\Bar\\Baz") ) == ERROR_SUCCESS ) {
   // Bingo!
} 

In case using such shell32.dll API ever becomes an issue, you can always reimplement the createDirectoryRecursively function above with something else (possibly a hand-wired loop).

Frerich Raabe
  • 90,689
  • 19
  • 115
  • 207
  • Word of warning that SHCreateDirectoryEx has special behavior when dealing with hidden directories and may show user interface dialogs to the user. – ShadowChaser Mar 08 '12 at 21:16
  • 1
    It only shows a user interface dialog if a hwnd reference is passed, not if it is NULL. – Joakim Dec 13 '12 at 13:07
  • @Joakim: Even if HWND is NULL you can be in for a nasty surprise because it CAN process non-queued messages. – EFraim Mar 03 '13 at 14:53
  • 1
    Also `SHCreateDirectoryEx` can't handle pathes longer than `MAX_PATH` (prefix `"\\?\"`). – zett42 Oct 09 '17 at 16:57
  • @zett42: is `SHCreateDirectoryEx()` one of the functions that supports [Windows 10's removal of the `MAX_PATH` limitation](https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx#maxpath)? "*Starting in Windows 10, version 1607, MAX_PATH limitations have been removed from common Win32 file and directory functions. However, you must opt-in to the new behavior.*" The article only lists Win32 functions that have been updated, but not any Shell functions. – Remy Lebeau Feb 28 '18 at 18:31
  • @RemyLebeau The function is marked deprecated, so it would really surprise me if it would support removal of `MAX_PATH` limitation. Have not tried though. – zett42 Feb 28 '18 at 18:37
11

Here's a version that works with no external libraries, so Win32-only, and that function in all versions of Windows (including Windows CE, where I needed it):

wchar_t *path = GetYourPathFromWherever();

wchar_t folder[MAX_PATH];
wchar_t *end;
ZeroMemory(folder, MAX_PATH * sizeof(wchar_t));

end = wcschr(path, L'\\');

while(end != NULL)
{
    wcsncpy(folder, path, end - path + 1);
    if(!CreateDirectory(folder, NULL))
    {
        DWORD err = GetLastError();

        if(err != ERROR_ALREADY_EXISTS)
        {
            // do whatever handling you'd like
        }
    }
    end = wcschr(++end, L'\\');
}
Julien Ruffin
  • 822
  • 1
  • 7
  • 8
ctacke
  • 66,480
  • 18
  • 94
  • 155
  • 1
    Looks nice, but if I'm reading it correctly, it requires the path to have a trailing "\", otherwise a path like "C:\folder\subfolder" will not create "subfolder", but "C:\folder\subfolder\" will. You probably need an extra call to CreateDirectory(path) after the loop is done. – Remy Lebeau May 27 '16 at 22:22
  • 2
    This was intended to take in a fully-qualified file path and it will ensure that the folder for the target exists. If you want to only pass it a folder path, then yes, it would need further work – ctacke May 28 '16 at 17:26
10

With C++17, this can be done quite easily using std::filesystem::create_directories().

Example:

#include <filesystem>
...

const char* path = "C:\\foo\\bar";
std::filesystem::create_directories(path);
Nathan Reed
  • 3,583
  • 1
  • 26
  • 33
7

Here is a function I wrote which iteratively creates a folder tree. Here is the main function:

#include <io.h>
#include <string>
#include <direct.h>
#include <list>

// Returns false on success, true on error
bool createFolder(std::string folderName) {
    list<std::string> folderLevels;
    char* c_str = (char*)folderName.c_str();

    // Point to end of the string
    char* strPtr = &c_str[strlen(c_str) - 1];

    // Create a list of the folders which do not currently exist
    do {
        if (folderExists(c_str)) {
            break;
        }
        // Break off the last folder name, store in folderLevels list
        do {
            strPtr--;
        } while ((*strPtr != '\\') && (*strPtr != '/') && (strPtr >= c_str));
        folderLevels.push_front(string(strPtr + 1));
        strPtr[1] = 0;
    } while (strPtr >= c_str);

    if (_chdir(c_str)) {
        return true;
    }

    // Create the folders iteratively
    for (list<std::string>::iterator it = folderLevels.begin(); it != folderLevels.end(); it++) {
        if (CreateDirectory(it->c_str(), NULL) == 0) {
            return true;
        }
        _chdir(it->c_str());
    }

    return false;
}

The folderExists routine is as follows:

// Return true if the folder exists, false otherwise
bool folderExists(const char* folderName) {
    if (_access(folderName, 0) == -1) {
        //File not found
        return false;
    }

    DWORD attr = GetFileAttributes((LPCSTR)folderName);
    if (!(attr & FILE_ATTRIBUTE_DIRECTORY)) {
        // File is not a directory
        return false;
    }

    return true;
}

An example call I tested the above functions with is as follows (and it works):

createFolder("C:\\a\\b\\c\\d\\e\\f\\g\\h\\i\\j\\k\\l\\m\\n\\o\\p\\q\\r\\s\\t\\u\\v\\w\\x\\y\\z");

This function hasn't gone through very thorough testing, and I'm not sure it yet works with other operating systems (but probably is compatible with a few modifications). I am currently using Visual Studio 2010 with Windows 7.

  • 1
    This does work at first glance, but it changes the current directory to the newly created one, which is something you might not want to happen if using relative paths. – antipattern Jan 13 '15 at 14:18
  • 1
    Not using Unicode is a capital crime, and has been throughout this millennium. Not sure why you cast the first argument to `GetFileAttributes` to the exact same type it gets passed as, either. – IInspectable Feb 02 '16 at 11:32
6

SHCreateDirectory function can do this. But the document states that it can get deprecated in later version of Windows.

From MSDN

Note This function is available through Windows XP Service Pack 2 (SP2) and Microsoft Windows Server 2003. It might be altered or unavailable in subsequent versions of Windows.

Aamir
  • 14,882
  • 6
  • 45
  • 69
3

Good example:

#ifndef UNICODE
#define UNICODE
#define UNICODE_WAS_UNDEFINED
#endif

#include <Windows.h>

#ifdef UNICODE_WAS_UNDEFINED
#undef UNICODE
#endif

#include <string>

BOOL DirectoryExists(LPCTSTR szPath)
{
  DWORD dwAttrib = GetFileAttributes(szPath);

  return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
    (dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
}

void createDirectoryRecursively(std::wstring path)
{
  signed int pos = 0;
  do
  {
    pos = path.find_first_of(L"\\/", pos + 1);
    CreateDirectory(path.substr(0, pos).c_str(), NULL);
  } while (pos != std::wstring::npos);
}

//in application
int main()
{
  std::wstring directory = L"../temp/dir";
  if (DirectoryExists(directory.c_str()) == FALSE)
    createDirectoryRecursively(directory);
  return 0;
}
sailfish009
  • 2,561
  • 1
  • 24
  • 31
2

For Windows XP and up. Expects widechar null terminated string and amount of recursive actions as parameters. Was not tested with more than 1 level yet.

Note: Path seperators must be '\'

bool CreateRecursiveDirectoryW(const wchar_t* filepath, const int max_level)
{
    bool result = false;
    wchar_t path_copy[MAX_PATH] = {0};
    wcscat_s(path_copy, MAX_PATH, filepath);
    std::vector<std::wstring> path_collection;

    for(int level=0; PathRemoveFileSpecW(path_copy) && level < max_level; level++)
    {
        path_collection.push_back(path_copy);
    }
    for(int i=path_collection.size()-1; i >= 0; i--)
    {
        if(!PathIsDirectoryW(path_collection[i].c_str()))
            if(CreateDirectoryW(path_collection[i].c_str(), NULL))
                result = true;
    }
    return result;
};
xezon
  • 91
  • 5
1

ctacke You forgot the last segment. e.g. '\aa\bb\"cc"' Following is the modification for ctacke:

//---------------------------------------------------------------------
int isfexist(char *fn)
{
    struct stat stbuf;
    extern int errno;

    if (stat(fn, &stbuf)) {
        if (errno == ENOENT) return(0);
        else {
            printf("isfexist: stat");
            return(0);
        }
    } else {
        if (stbuf.st_mode & S_IFDIR) return(2);
        else return(1);
    }
}
//---------------------------------------------------------------------
int MakeDirTree(char *path)
{
    char *end1, *end2;

    if (path[0] == '\\') end1 = path + 1;       // Case '\aa\bb'
    else if (path[1] == ':' && path[2] == '\\') end1 = path + 3;    // Case 'C:\\aa\\bb'
    else end1 = path;

    for(;;) {
        end2 = strchr(end1, '\\');
        if (end2 == NULL) {
            // Case '\aa\bb\'
            if (*end1 == 0) break;
            // Last segment '\aa\bb\"cc"' not yet proceed
        } else *end2 = 0;
        if (isfexist(path) <= 0) mkdir(path);
        if (end2 == NULL) break;    // Last segment finished
        else {
            *end2 = '\\';
            end1 = end2 + 1;
        }
    }
}
steel17
  • 11
  • 1
1

I'm modifying an old Windows CE app, and this is what I'm planning to use. Should work in Windows CE, too. This is actually recursive, too:

static void createPath(const CString& p)
{
   // only create directories that don't exist
   if (::GetFileAttributes(p) == INVALID_FILE_ATTRIBUTES)
   {
      // check if our parent needs to be created, too...
      int i = p.ReverseFind('\\');
      if (i > 0)
      {
         // ...yes, create the parent (recursively)
         createPath(p.Left(i));
      }

      // finally, actually create the directory in p
      ::CreateDirectory(p, NULL);
   }
}
Tommi
  • 31
  • 5
0

From http://www.cplusplus.com/reference/string/string/find_last_of/:

// string::find_last_of
#include <iostream>
#include <string>
using namespace std;

void SplitFilename (const string& str)
{
  size_t found;
  cout << "Splitting: " << str << endl;
  found=str.find_last_of("/\\");
  cout << " folder: " << str.substr(0,found) << endl;
  cout << " file: " << str.substr(found+1) << endl;
}

int main ()
{
  string str1 ("/usr/bin/man");
  string str2 ("c:\\windows\\winhelp.exe");

  SplitFilename (str1);
  SplitFilename (str2);

  return 0;

That should give you an idea on how to deal with the path string. Then after that, all you need to do is loop through the paths starting from the drive down to the deepest folder. Check if the folder exists, and if it doesn't, create it.

xian
  • 4,657
  • 5
  • 34
  • 38
0

Here is my example of code (copied from How can I create directory tree in C++/Linux?). Maybe it's not conform all requirements from first post, but pretty well and it works for both Windows and Linux:

#include <iostream>
#include <string>
#include <sys/stat.h> // stat
#include <errno.h>    // errno, ENOENT, EEXIST
#if defined(_WIN32)
#include <direct.h>   // _mkdir
#endif

bool isDirExist(const std::string& path)
{
#if defined(_WIN32)
    struct _stat info;
    if (_stat(path.c_str(), &info) != 0)
    {
        return false;
    }
    return (info.st_mode & _S_IFDIR) != 0;
#else 
    struct stat info;
    if (stat(path.c_str(), &info) != 0)
    {
        return false;
    }
    return (info.st_mode & S_IFDIR) != 0;
#endif
}

bool makePath(const std::string& path)
{
#if defined(_WIN32)
    int ret = _mkdir(path.c_str());
#else
    mode_t mode = 0755;
    int ret = mkdir(path.c_str(), mode);
#endif
    if (ret == 0)
        return true;

    switch (errno)
    {
    case ENOENT:
        // parent didn't exist, try to create it
        {
            int pos = path.find_last_of('/');
            if (pos == std::string::npos)
#if defined(_WIN32)
                pos = path.find_last_of('\\');
            if (pos == std::string::npos)
#endif
                return false;
            if (!makePath( path.substr(0, pos) ))
                return false;
        }
        // now, try to create again
#if defined(_WIN32)
        return 0 == _mkdir(path.c_str());
#else 
        return 0 == mkdir(path.c_str(), mode);
#endif

    case EEXIST:
        // done!
        return isDirExist(path);

    default:
        return false;
    }
}

int main(int argc, char* ARGV[])
{
    for (int i=1; i<argc; i++)
    {
        std::cout << "creating " << ARGV[i] << " ... " << (makePath(ARGV[i]) ? "OK" : "failed") << std::endl;
    }
    return 0;
}

Usage:

d:\Work\c++\make_path> makePath 1/2 folderA/folderB/folderC
creating 1/2 ... OK
creating folderA/folderB/folderC ... OK
Community
  • 1
  • 1
Maxim Suslov
  • 4,335
  • 1
  • 35
  • 29
0
UnicodeString path = "C:\\Test\\Test\\Test\\";
TStringList *list = new TStringList();

try
{
    list->Delimiter = '\\';
    list->StrictDelimiter = true;
    list->DelimitedText = path;
    path = list->Strings[0]; \\drive letter
    for(int i = 1; i < list->Count - 1; i++)
    {
        try
        {
            path += "\\" + list->Strings[i];
            CreateDirectory(path.w_str(), NULL);
        }
        catch(...) { }
    }
}
catch(...) { }
delete list;
0

If you're using Microsoft's Windows Implementation Libraries (WIL), you can use the wil::CreateDirectoryDeep function. If not, you might want to consider using it, or borrowing the code.

Paul
  • 6,061
  • 6
  • 39
  • 70
-1

The below method helped me create several directories until achieving the whole path.

Let's say if you have:

C:\d1 and you pass C:\d1\d2\d3 as parameter.

This function would go ahead and create d2 inside d1 and d3 inside d2. Also, it won't mess with files that already exist in your d1 directory.

// HRESULT CreateDirectoryStructure(directoryPath)

std::wstring directoryPath;
directoryPath= L"C:\\d1\\d2\\d3\\";
hr = CreateDirectoryStructure(directoryPath);

//----------------------------------------------//

HRESULT
CreateDirectoryStructure(
    _In_ const std::wstring& directory_path
)
/*++

Routine Description:

    Creates the directory and all parent directories specified by the
    directory_path. The path should end with backslash.

Arguments:

    directory_path - path of the directory to be created.

Return Value:

    S_OK on success. On failure appropriate HRESULT is returned.

--*/
{
    HRESULT hr = S_OK;
    DWORD error = ERROR_SUCCESS;
    bool result = false;
    PWSTR normalized_path = NULL;
    PWSTR last_path = NULL;

    if (directory_path.size() == 0)
    {
        hr = ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }
    normalized_path = _wcsdup(directory_path.c_str());
    //
    // Find the first directory separator since this returns the system root.
    //
    last_path = wcschr(normalized_path, L'\\');
    if (last_path != NULL)
    {
        last_path++;
    }

    //
    // Create all directories and subdirectories in the path.
    //
    while ((last_path = wcschr(last_path, L'\\')) != NULL)
    {
        *last_path = '\0';
        result = CreateDirectory(
            normalized_path,
            NULL);
        *last_path = L'\\';
        last_path++;
        if (result == false)
        {
            error = GetLastError();
            if(error != ERROR_ALREADY_EXISTS)
            {
                hr = HRESULT_FROM_WIN32(error);
                goto Cleanup;
            }
        }
    }

Cleanup:
    return hr;
}
Luis Armando
  • 444
  • 1
  • 7
  • 17
  • It's completely meaningless how you name a function. A useful answer would have included the code. This proposed answer is not useful. – IInspectable Sep 11 '21 at 12:43
  • @IInspectable thanks for your suggestion, I addressed your comment adding a code example. Hopefully these changes make the answer useful to you. – Luis Armando Sep 16 '21 at 19:38
  • That doesn't help. `CreateDirectoryStructure` is not a system API call, nor a C++ Standard Library call. This proposed answer isn't useful to anyone that doesn't have access to the implementation of said function. – IInspectable Sep 16 '21 at 20:16
  • I'm sorry for the confusion @IInspectable . I have added the whole code, hope it helps! – Luis Armando Dec 07 '21 at 20:26