HOWTO · C

Crea una nuova directory in C

Questo articolo introduce come creare una nuova directory in C

In questa pagina

Usa la funzione POSIX mkdir per creare una directory direttamente da C. La funzione accetta un percorso e i bit dei permessi e segnala gli errori tramite il valore restituito e errno. I permessi richiesti vengono filtrati dalla umask del processo, quindi la modalità risultante può essere più restrittiva di quella passata alla chiamata. Se il percorso deve essere interpretato rispetto a una directory aperta specifica anziché alla directory di lavoro del processo, mkdirat consente di adottare questo approccio.

Creare una directory con mkdir

Includi <sys/stat.h> per usare mkdir. Il primo argomento è il percorso della nuova directory; il secondo è una modalità, per esempio 0750. Con questa modalità, il proprietario può leggere, scrivere ed esplorare la directory, mentre i membri del gruppo possono leggerla ed esplorarla. I bit richiesti non concedono permessi agli altri utenti. La umask del processo può rimuovere permessi, ma non aggiungerne.

Esegui questo esempio. Le etichette e le stringhe visibili nel codice restano in inglese:

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>

int main(void) {
    char parent[] = "/tmp/mkdir-demo-XXXXXX";
    char path[sizeof(parent) + sizeof("/reports")];

    if (mkdtemp(parent) == NULL) {
        perror("mkdtemp");
        return EXIT_FAILURE;
    }
    int path_length = snprintf(path, sizeof(path), "%s/reports", parent);
    if (path_length < 0 || (size_t)path_length >= sizeof(path)) {
        puts("path formatting failed");
        if (rmdir(parent) == -1) {
            perror("rmdir parent");
        }
        return EXIT_FAILURE;
    }

    if (mkdir(path, 0750) == -1) {
        perror("mkdir");
        if (rmdir(parent) == -1) {
            perror("rmdir parent");
        }
        return EXIT_FAILURE;
    }

    puts("Created reports directory.");
    int result = EXIT_SUCCESS;
    if (rmdir(path) == -1) {
        perror("rmdir child");
        result = EXIT_FAILURE;
    }
    if (rmdir(parent) == -1) {
        perror("rmdir parent");
        result = EXIT_FAILURE;
    }
    return result;
}
Created reports directory.

Il record di esecuzione fornito mostra che il caso riuscito stampa Created reports directory. I casi di errore non sono documentati in quel record. L’esempio crea una directory genitore temporanea, vi crea reports, stampa il messaggio di successo e rimuove entrambe le directory. Nella tua applicazione, sostituisci il percorso temporaneo con quello che ti serve e valuta se i permessi scelti sono adatti ai dati che conserverai nella directory. Il messaggio visualizzato e le stringhe presenti nel codice restano in inglese.

Gestire gli errori e i percorsi esistenti

mkdir restituisce 0 in caso di successo e -1 in caso di errore. In caso di errore, imposta errno: chiama subito perror oppure salva errno prima di svolgere altre operazioni che potrebbero modificarlo. Tra le cause comuni ci sono una directory genitore inesistente, permessi insufficienti per modificarla, un percorso che supera il limite del sistema oppure un componente del percorso che non è una directory. Se la destinazione esiste già, anche mkdir fallisce con EEXIST, sia che si tratti di una directory sia che sia un altro tipo di file.

Considera autorevole il risultato della creazione. Un controllo con stat prima di mkdir non garantisce che il percorso resti inesistente fino alla chiamata: un altro processo potrebbe crearlo nel frattempo. Se il tuo programma considera valida una directory già esistente, gestisci EEXIST e poi usa stat o lstat per verificarne il tipo, tenendo conto delle condizioni di gara se il percorso può essere modificato contemporaneamente. Non accettare in silenzio un file regolare già esistente.

L’argomento 0750 indica la modalità richiesta, non necessariamente quella finale. Per esempio, una umask restrittiva può azzerare bit del proprietario o del gruppo. Se sono necessari permessi esatti, controlla la modalità della directory dopo averla creata e applica una politica dei permessi deliberata; considera anche le implicazioni di sicurezza prima di modificare i permessi di un percorso che un altro processo potrebbe aver sostituito.

Creare una directory rispetto a una directory aperta con mkdirat

mkdirat accetta un descrittore di file di directory, un percorso e una modalità. Se il percorso è relativo, il kernel lo risolve a partire dalla directory indicata dal descrittore, indipendentemente da eventuali cambiamenti successivi della directory di lavoro del processo. È utile quando un’applicazione ha già aperto una directory e vuole ancorarvi le operazioni successive. Un percorso assoluto non viene risolto rispetto al descrittore.

L’esempio apre una directory genitore temporanea, crea una directory figlia relativa al suo descrittore e poi chiude il descrittore. In seguito rimuove la directory temporanea. Il record di esecuzione fornito mostra che il caso riuscito termina senza produrre output standard; non documenta esecuzioni dei casi di errore. Le stringhe visibili nel codice restano in inglese.

#define _POSIX_C_SOURCE 200809L
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>

int main(void) {
    char parent[] = "/tmp/mkdirat-demo-XXXXXX";
    char child[sizeof(parent) + sizeof("/reports")];
    int dirfd;
    int result = EXIT_FAILURE;
    int created = 0;

    if (mkdtemp(parent) == NULL) {
        perror("mkdtemp");
        return EXIT_FAILURE;
    }
    dirfd = open(parent, O_RDONLY);
    if (dirfd == -1) {
        perror("open");
        if (rmdir(parent) == -1) {
            perror("rmdir parent");
        }
        return EXIT_FAILURE;
    }

    if (mkdirat(dirfd, "reports", 0750) == -1) {
        perror("mkdirat");
    } else {
        created = 1;
        result = EXIT_SUCCESS;
    }

    if (close(dirfd) == -1) {
        perror("close");
        result = EXIT_FAILURE;
    }
    if (created) {
        int child_length = snprintf(child, sizeof(child), "%s/reports", parent);
        if (child_length < 0 || (size_t)child_length >= sizeof(child)) {
            puts("path formatting failed");
            result = EXIT_FAILURE;
        } else {
            if (rmdir(child) == -1) {
                perror("rmdir child");
                result = EXIT_FAILURE;
            }
        }
    }
    if (rmdir(parent) == -1) {
        perror("rmdir parent");
        result = EXIT_FAILURE;
    }
    return result;
}

Mantieni aperto il descrittore finché ti serve e chiudilo in ogni percorso di esecuzione. Nel codice di produzione, assicurati che la pulizia rimuova soltanto gli oggetti creati dal programma. Per le operazioni sensibili alla sicurezza, valuta l’apertura della directory con flag adatti alla piattaforma e verifica la directory che intendi usare; mkdirat ancora la risoluzione dei percorsi relativi, ma da solo non convalida ogni componente del percorso fornito.

Perché evitare system() per creare una directory

In genere è superfluo chiamare system() per eseguire un comando shell, perché C mette già a disposizione mkdir. La shell comporta l’avvio di un processo e l’interpretazione del comando; inoltre, costruire una stringa di comando a partire da un percorso può consentire ai metacaratteri shell presenti in un percorso non attendibile di modificare ciò che viene eseguito. Fare l’escaping dei percorsi correttamente tra shell diverse è difficile. Chiama direttamente mkdir o mkdirat, controlla il risultato e segnala errno. Una shell è appropriata solo quando l’applicazione ha intenzionalmente bisogno del comportamento della shell e gestisce la costruzione e l’esecuzione del comando con una politica di sicurezza progettata con cura.