Recursive mkdir() system call on Unix

After reading the mkdir(2) man page for the Unix system call with that name, it appears that the call doesn't create intermediate directories in a path, only the last directory in the path. Is there any way (or other function) to create all the directories in the path without resorting to manually parsing my directory string and individually creating each directory ?

114094 次浏览

There is not a system call to do it for you, unfortunately. I'm guessing that's because there isn't a way to have really well-defined semantics for what should happen in error cases. Should it leave the directories that have already been created? Delete them? What if the deletions fail? And so on...

It is pretty easy to roll your own, however, and a quick google for 'recursive mkdir' turned up a number of solutions. Here's one that was near the top:

http://nion.modprobe.de/blog/archives/357-Recursive-directory-creation.html

static void _mkdir(const char *dir) {
char tmp[256];
char *p = NULL;
size_t len;


snprintf(tmp, sizeof(tmp),"%s",dir);
len = strlen(tmp);
if (tmp[len - 1] == '/')
tmp[len - 1] = 0;
for (p = tmp + 1; *p; p++)
if (*p == '/') {
*p = 0;
mkdir(tmp, S_IRWXU);
*p = '/';
}
mkdir(tmp, S_IRWXU);
}

The two other answers given are for mkdir(1) and not mkdir(2) like you ask for, but you can look at the source code for that program and see how it implements the -p options which calls mkdir(2) repeatedly as needed.

Apparently not, my two suggestions are:

char dirpath[80] = "/path/to/some/directory";
sprintf(mkcmd, "mkdir -p %s", dirpath);
system(mkcmd);

Or if you don't want to use system() try looking at the coreutils mkdir source code and see how they implemented the -p option.

Take a look at the bash source code here, and specifically look in examples/loadables/mkdir.c especially lines 136-210. If you don't want to do that, here's some of the source that deals with this (taken straight from the tar.gz that I've linked):

/* Make all the directories leading up to PATH, then create PATH.  Note that
this changes the process's umask; make sure that all paths leading to a
return reset it to ORIGINAL_UMASK */


static int
make_path (path, nmode, parent_mode)
char *path;
int nmode, parent_mode;
{
int oumask;
struct stat sb;
char *p, *npath;


if (stat (path, &sb) == 0)
{
if (S_ISDIR (sb.st_mode) == 0)
{
builtin_error ("`%s': file exists but is not a directory", path);
return 1;
}


if (chmod (path, nmode))
{
builtin_error ("%s: %s", path, strerror (errno));
return 1;
}


return 0;
}


oumask = umask (0);
npath = savestring (path);    /* So we can write to it. */


/* Check whether or not we need to do anything with intermediate dirs. */


/* Skip leading slashes. */
p = npath;
while (*p == '/')
p++;


while (p = strchr (p, '/'))
{
*p = '\0';
if (stat (npath, &sb) != 0)
{
if (mkdir (npath, parent_mode))
{
builtin_error ("cannot create directory `%s': %s", npath, strerror (errno));
umask (original_umask);
free (npath);
return 1;
}
}
else if (S_ISDIR (sb.st_mode) == 0)
{
builtin_error ("`%s': file exists but is not a directory", npath);
umask (original_umask);
free (npath);
return 1;
}


*p++ = '/';   /* restore slash */
while (*p == '/')
p++;
}


/* Create the final directory component. */
if (stat (npath, &sb) && mkdir (npath, nmode))
{
builtin_error ("cannot create directory `%s': %s", npath, strerror (errno));
umask (original_umask);
free (npath);
return 1;
}


umask (original_umask);
free (npath);
return 0;
}

You can probably get away with a less general implementation.

Here is my solution. By calling the function below you ensure that all dirs leading to the file path specified exist. Note that file_path argument is not directory name here but rather a path to a file that you are going to create after calling mkpath().

Eg., mkpath("/home/me/dir/subdir/file.dat", 0755) shall create /home/me/dir/subdir if it does not exist. mkpath("/home/me/dir/subdir/", 0755) does the same.

Works with relative paths as well.

Returns -1 and sets errno in case of an error.

int mkpath(char* file_path, mode_t mode) {
assert(file_path && *file_path);
for (char* p = strchr(file_path + 1, '/'); p; p = strchr(p + 1, '/')) {
*p = '\0';
if (mkdir(file_path, mode) == -1) {
if (errno != EEXIST) {
*p = '/';
return -1;
}
}
*p = '/';
}
return 0;
}

Note that file_path is modified during the action but gets restored afterwards. Therefore file_path is not strictly const.

hmm I thought that mkdir -p does that?

mkdir -p this/is/a/full/path/of/stuff

My recursive way of doing this:

#include <libgen.h> /* Only POSIX version of dirname() */
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>


static void recursive_mkdir(const char *path, mode_t mode)
{
char *spath = NULL;
const char *next_dir = NULL;


/* dirname() modifies input! */
spath = strdup(path);
if (spath == NULL)
{
/* Report error, no memory left for string duplicate. */
goto done;
}


/* Get next path component: */
next_dir = dirname(spath);


if (access(path, F_OK) == 0)
{
/* The directory in question already exists! */
goto done;
}


if (strcmp(next_dir, ".") == 0 || strcmp(next_dir, "/") == 0)
{
/* We reached the end of recursion! */
goto done;
}


recursive_mkdir(next_dir, mode);
if (mkdir(path, mode) != 0)
{
/* Report error on creating directory */
}


done:
free(spath);
return;
}

EDIT: fixed my old code snippet, bug-report by Namchester

Here's another take on mkpath(), using recursion, which is both small and readable. It makes use of strdupa() to avoid altering the given dir string argument directly and to avoid using malloc() & free(). Make sure to compile with -D_GNU_SOURCE to activate strdupa() ... meaning this code only works on GLIBC, EGLIBC, uClibc, and other GLIBC compatible C libraries.

int mkpath(char *dir, mode_t mode)
{
if (!dir) {
errno = EINVAL;
return 1;
}


if (strlen(dir) == 1 && dir[0] == '/')
return 0;


mkpath(dirname(strdupa(dir)), mode);


return mkdir(dir, mode);
}

After input both here and from Valery Frolov, in the Inadyn project, the following revised version of mkpath() has now been pushed to libite

int mkpath(char *dir, mode_t mode)
{
struct stat sb;


if (!dir) {
errno = EINVAL;
return 1;
}


if (!stat(dir, &sb))
return 0;


mkpath(dirname(strdupa(dir)), mode);


return mkdir(dir, mode);
}

It uses one more syscall, but otoh the code is more readable now.

My solution:

int mkrdir(const char *path, int index, int permission)
{
char bf[NAME_MAX];
if(*path == '/')
index++;
char *p = strchr(path + index, '/');
int len;
if(p) {
len = MIN(p-path, sizeof(bf)-1);
strncpy(bf, path, len);
bf[len]=0;
} else {
len = MIN(strlen(path)+1, sizeof(bf)-1);
strncpy(bf, path, len);
bf[len]=0;
}


if(access(bf, 0)!=0) {
mkdir(bf, permission);
if(access(bf, 0)!=0) {
return -1;
}
}
if(p) {
return mkrdir(path, p-path+1, permission);
}
return 0;
}

I'm not allowed to comment on the first (and accepted) answer (not enough rep), so I'll post my comments as code in a new answer. The code below is based on the first answer, but fixes a number of problems:

  • If called with a zero-length path, this does not read or write the character before the beginning of array opath[] (yes, "why would you call it that way?", but on the other hand "why would you not fix the vulnerability?")
  • the size of opath is now PATH_MAX (which isn't perfect, but is better than a constant)
  • if the path is as long as or longer than sizeof(opath) then it is properly terminated when copied (which strncpy() doesn't do)
  • you can specify the mode of the written directory, just as you can with the standard mkdir() (although if you specify non-user-writeable or non-user-executable then the recursion won't work)
  • main() returns the (required?) int
  • removed a few unnecessary #includes
  • I like the function name better ;)
// Based on http://nion.modprobe.de/blog/archives/357-Recursive-directory-creation.html
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <limits.h>


static void mkdirRecursive(const char *path, mode_t mode) {
char opath[PATH_MAX];
char *p;
size_t len;


strncpy(opath, path, sizeof(opath));
opath[sizeof(opath) - 1] = '\0';
len = strlen(opath);
if (len == 0)
return;
else if (opath[len - 1] == '/')
opath[len - 1] = '\0';
for(p = opath; *p; p++)
if (*p == '/') {
*p = '\0';
if (access(opath, F_OK))
mkdir(opath, mode);
*p = '/';
}
if (access(opath, F_OK))         /* if path is not terminated with / */
mkdir(opath, mode);
}




int main (void) {
mkdirRecursive("/Users/griscom/one/two/three", S_IRWXU);
return 0;
}

Here's my shot at a more general solution:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>


typedef int (*dirhandler_t)( const char*, void* );
/// calls itfunc for each directory in path (except for . and ..)
int iterate_path( const char* path, dirhandler_t itfunc, void* udata )
{
int rv = 0;
char tmp[ 256 ];
char *p = tmp;
char *lp = tmp;
size_t len;
size_t sublen;
int ignore_entry;


strncpy( tmp, path, 255 );


tmp[ 255 ] = '\0';
len = strlen( tmp );


if( 0 == len ||
(1 == len && '/' == tmp[ 0 ]) )
return 0;


if( tmp[ len - 1 ] == '/' )
tmp[ len - 1 ] = 0;


while( (p = strchr( p, '/' )) != NULL )
{
ignore_entry = 0;
*p = '\0';
lp = strrchr( tmp, '/' );


if( NULL == lp ) { lp = tmp; }
else { lp++; }


sublen = strlen( lp );


if( 0 == sublen )   /* ignore things like '//' */
ignore_entry = 1;
else if( 1 == sublen &&  /* ignore things like '/./' */
'.' == lp[ 0 ] )
ignore_entry = 1;
else if( 2 == sublen &&    /* also ignore things like '/../' */
'.' == lp[ 0 ] &&
'.' == lp[ 1 ] )
ignore_entry = 1;


if( ! ignore_entry )
{
if( (rv = itfunc( tmp, udata )) != 0 )
return rv;
}


*p = '/';
p++;
lp = p;
}


if( strcmp( lp, "." ) && strcmp( lp, ".." ) )
return itfunc( tmp, udata );


return 0;
}


mode_t get_file_mode( const char* path )
{
struct stat statbuf;
memset( &statbuf, 0, sizeof( statbuf ) );


if( NULL == path ) { return 0; }


if( 0 != stat( path, &statbuf ) )
{
fprintf( stderr, "failed to stat '%s': %s\n",
path, strerror( errno ) );
return 0;
}


return statbuf.st_mode;
}


static int mymkdir( const char* path, void* udata )
{
(void)udata;
int rv = mkdir( path, S_IRWXU );
int errnum = errno;


if( 0 != rv )
{
if( EEXIST == errno &&
S_ISDIR( get_file_mode( path ) ) )  /* it's all good, the directory already exists */
return 0;


fprintf( stderr, "mkdir( %s ) failed: %s\n",
path, strerror( errnum ) );
}
//     else
//     {
//         fprintf( stderr, "created directory: %s\n", path );
//     }


return rv;
}


int mkdir_with_leading( const char* path )
{
return iterate_path( path, mymkdir, NULL );
}


int main( int argc, const char** argv )
{
size_t i;
int rv;


if( argc < 2 )
{
fprintf( stderr, "usage: %s <path> [<path>...]\n",
argv[ 0 ] );
exit( 1 );
}


for( i = 1; i < argc; i++ )
{
rv = mkdir_with_leading( argv[ i ] );
if( 0 != rv )
return rv;
}


return 0;
}

A very simple solution, just pass in input: mkdir dirname

void execute_command_mkdir(char *input)
{
char rec_dir[500];
int s;
if(strcmp(input,"mkdir") == 0)
printf("mkdir: operand required");
else
{
char *split = strtok(input," \t");
while(split)
{
if(strcmp(split,"create_dir") != 0)
strcpy(rec_dir,split);
split = strtok(NULL, " \t");
}
char *split2 = strtok(rec_dir,"/");
char dir[500];
strcpy(dir, "");
while(split2)
{
strcat(dir,split2);
strcat(dir,"/");
printf("%s %s\n",split2,dir);
s = mkdir(dir,0700);
split2 = strtok(NULL,"/");
}
strcpy(output,"ok");
}
if(s < 0)
printf(output,"Error!! Cannot Create Directory!!");
}

Quite straight. This can be a good starting point

int makeDir(char *fullpath, mode_t permissions){
int i=0;
char *arrDirs[20];
char aggrpaz[255];
arrDirs[i] = strtok(fullpath,"/");
strcpy(aggrpaz, "/");
while(arrDirs[i]!=NULL)
{
arrDirs[++i] = strtok(NULL,"/");
strcat(aggrpaz, arrDirs[i-1]);
mkdir(aggrpaz,permissions);
strcat(aggrpaz, "/");
}
i=0;
return 0;
}

You parse this function a full path plus the permissions you want, i.e S_IRUSR, for a full list of modes go here https://techoverflow.net/2013/04/05/how-to-use-mkdir-from-sysstat-h/

The fullpath string will be split by the "/" character and individual dirs will be appended to the aggrpaz string one at a time. Each loop iteration calls the mkdir function, passing it the aggregate path so far plus the permissions. This example can be improved, I am not checking the mkdir function output and this function only works with absolute paths.

here is my solution

void mkpath(char *p) {
char *path = strdup(p);
char  *save_path = path;
char *sep1;
char *sep2=0;
do {
int idx = (sep2-path)<0 ? 0 : sep2-path;
sep1 = strchr(path + idx , '/');
sep2 = strchr(sep1+1, '/');
if (sep2) {
path[sep2-path]=0;
}
if(mkdir(path, 0777) && errno != EEXIST)
break;
if (sep2) {
path[sep2-path]='/';
}
} while (sep2);


free(save_path);


}


.
.
.
mkpath ("./the/new/path")

If you like recursion because it's fun!

#include <string.h>
#include <sys/stat.h> /* mkdir(2) */
#include <limits.h> /* PATH_MAX */
int mkdirp(const char *dir, const mode_t mode){
struct stat sb;
//if dir already exists and is a directory
if (stat(dir, &sb) == 0){
if (S_ISDIR(sb.st_mode)) {
return 0;
}
else return -1;
}
else {
char tmp[PATH_MAX];
size_t len = strnlen(dir, PATH_MAX);
memcpy(tmp, dir, len);
//remove trailing slash
if (tmp[len-1]=='/'){
tmp[len-1]='\0';
}
char *p = strrchr(tmp, '/');
*p='\0';
int ret = mkdirp(tmp, mode);
if (ret == 0){
return mkdir(dir, mode);
}
}
return 0;
}