web-dev-qa-db-fra.com

Appel système rkursif mkdir () sous Unix

Après avoir lu la page de manuel mkdir (2) pour l'appel système Unix portant ce nom, il apparaît que l'appel ne crée pas de répertoires intermédiaires dans un chemin, uniquement le dernier répertoire du chemin. Existe-t-il un moyen (ou une autre fonction) de créer tous les répertoires du chemin sans recourir à l'analyse manuelle de ma chaîne de répertoires et à la création individuelle de chaque répertoire?

62
Alex Marshall

Malheureusement, il n'y a pas d'appel système pour le faire pour vous. Je suppose que c'est parce qu'il n'y a aucun moyen d'avoir une sémantique vraiment bien définie pour ce qui devrait se produire dans les cas d'erreur. Doit-il laisser les répertoires déjà créés? Supprime-les? Et si les suppressions échouent? Etc...

Cependant, il est assez facile de lancer le vôtre, et un rapide google pour ' mkdir récursif ' a trouvé un certain nombre de solutions. Voici celui qui était près du sommet:

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);
}
85
Carl Norum

hmm je pensais que mkdir -p fait ça?

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

66
j03m

Voici ma solution. En appelant la fonction ci-dessous, vous vous assurez que tous les répertoires menant au chemin de fichier spécifié existent. Notez que l'argument file_path N'est pas le nom du répertoire ici mais plutôt un chemin vers un fichier que vous allez créer après avoir appelé mkpath().

Par exemple, mkpath("/home/me/dir/subdir/file.dat", 0755) doit créer /home/me/dir/subdir S'il n'existe pas. mkpath("/home/me/dir/subdir/", 0755) fait de même.

Fonctionne également avec les chemins relatifs.

Renvoie -1 Et définit errno en cas d'erreur.

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;
}

Notez que file_path Est modifié pendant l'action mais est restauré par la suite. Par conséquent, file_path N'est pas strictement const.

22
Yaroslav Stavnichiy

Voici une autre version de mkpath(), utilisant la récursion, qui est à la fois petite et lisible. Il utilise strdupa() pour éviter de modifier directement l'argument de chaîne dir donné et pour éviter d'utiliser malloc() & free(). Assurez-vous de compiler avec -D_GNU_SOURCE Pour activer strdupa() ... ce qui signifie que ce code ne fonctionne que sur GLIBC, EGLIBC, uClibc et d'autres bibliothèques C compatibles GLIBC.

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);
}

Après avoir été saisie ici et par Valery Frolov, dans le projet Inadyn, la version révisée suivante de mkpath() a maintenant été poussée vers 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);
}

Il utilise un autre syscall, mais le code est maintenant plus lisible.

11
troglobit

Jetez un œil au code source bash ici , et regardez spécifiquement dans examples/loadables/mkdir.c en particulier les lignes 136-210. Si vous ne voulez pas faire cela, voici quelques-unes des sources qui traitent de cela (tirées directement du tar.gz que j'ai lié):

/* 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;
}

Vous pouvez probablement vous en sortir avec une implémentation moins générale.

9
Chinmay Kanchi

Apparemment non, mes deux suggestions sont:

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

Ou si vous ne voulez pas utiliser system() essayez de regarder le code source de coreutils mkdir et voyez comment ils ont implémenté l'option -p.

8
SiegeX

En fait, vous pouvez simplement utiliser:

mkdir -p ./some/directories/to/be/created/
2
DataGreed

Ma façon récursive de le faire:

#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: correction de mon ancien extrait de code, rapport de bogue par Namchester

1
Kamiccolo

Je ne suis pas autorisé à commenter la première réponse (et acceptée) (pas assez de représentants), donc je posterai mes commentaires sous forme de code dans une nouvelle réponse. Le code ci-dessous est basé sur la première réponse, mais résout un certain nombre de problèmes:

  • S'il est appelé avec un chemin de longueur nulle, cela ne lit ni n'écrit le caractère avant le début du tableau opath[] (Oui, "pourquoi l'appelleriez-vous ainsi?", Mais d'un autre côté "pourquoi vous ne corrigez pas la vulnérabilité? ")
  • la taille de opath est maintenant PATH_MAX (ce qui n'est pas parfait, mais c'est mieux qu'une constante)
  • si le chemin est aussi long ou plus long que sizeof(opath) alors il est correctement terminé lors de la copie (ce que strncpy() ne fait pas)
  • vous pouvez spécifier le mode du répertoire écrit, tout comme vous pouvez le faire avec la fonction mkdir() standard (bien que si vous spécifiez non inscriptible par l'utilisateur ou non exécutable par l'utilisateur, la récursivité ne fonctionnera pas)
  • main () renvoie l'int (obligatoire?)
  • supprimé quelques #include s inutiles
  • J'aime mieux le nom de la fonction;)
// 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;
}
1
Daniel Griscom

Voici mon plan sur une solution plus générale:

#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;
}
0
Andreas Wehrmann

Assez droit. Cela peut être un bon point de départ

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;
}

Vous analysez cette fonction un chemin complet ainsi que les autorisations que vous souhaitez, c'est-à-dire S_IRUSR , pour une liste complète des modes, allez ici https: // techoverflow.net/2013/04/05/how-to-use-mkdir-from-sysstat-h/

La chaîne fullpath sera divisée par le caractère "/" et des répertoires individuels seront ajoutés à la chaîne aggrpaz une par une. Chaque itération de boucle appelle la fonction mkdir, en lui passant le chemin d'agrégation jusqu'à présent, plus les autorisations. Cet exemple peut être amélioré, je ne vérifie pas la sortie de la fonction mkdir et cette fonction ne fonctionne qu'avec des chemins absolus.

0
Daniel J.

Les deux autres réponses données sont pour mkdir(1) et non mkdir(2) comme vous le demandez, mais vous pouvez regarder le code source pour ce programme et voir comment il implémente les options -p qui appellent mkdir(2) à plusieurs reprises selon les besoins.

0
hlovdal

Ma 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;
}
0
user798267

Une solution très simple, il suffit de saisir en entrée: 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!!");
}
0
Harbeer Singh