/* Version: 17 juillet 2025 Pour sauvegarder un dossier et ses sous-dossiers vers une partition/un disque externe (c'est l'équivalent de Git push). Il scanne tout le www (20Go 37000 fichiers) en 1 seconde!. En mode copie il tourne à 2% CPU, console non visible. Tellement pratique pour n'oublier aucun fichier. Après avoir travaillé sur un projet ex. /home/joe/www/apps/test Pour voir les fichiers nouveaux/modifiés/supprimés, mais sans effectuer de modifications sur la sauvegarde: $ cd ~ $ ./sauv www/apps/test /media/truc/C416-16A6 sanscopie *!=taille* www/apps/test/ausarmor.sh *!=date* www/apps/test/index.html *existe pas* www/apps/test/nbmessages *fic supprimé* www/apps/test/2/index00.php Scanné dans www/test 8795 dossiers, 37168 fichiers → 3 fichiers à copier. 0 dossier, 1 fichier à supprimer $ ./sauv www/apps/test /media/truc/C416-16A6 si le dossier de destination n'existe pas il le crée récursivement. si le fichier de destination n'existe pas il le crée. si le fichier de destination a une taille différente il l'écrase. si le fichier de destination a une date de modification antérieure à la source il l'écrase. 2e scan de la sauvegarde vers le dossier local: si le fichier existe sur la sauvegarde, mais pas en local alors il a été supprimé en local → le supprime sur la sauvegarde Une option permet de copier vers la sauvegarde mais sans supprimer $ ./sauv www/apps/test /media/truc/C416-16A6 nepassupprimer Scanné dans www/test 8795 dossiers, 37168 fichiers → 3 fichiers ont été copiés. 0 dossier, 0 fichier ont été supprimés Pour restaurer une sauvegarde: $ cd /media/truc/C416-16A6 $ ./sauv www /home/joe sanscopie */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <dirent.h> #include <errno.h> #include <unistd.h> #include <sys/stat.h> int dossiersscannés = 0; int fichiersscannés = 0; int nbfichiersacopier = 0; int nbdossiersasupprimer = 0; int nbfichiersasupprimer = 0; /*==========================================================*/ // Copie un fichier vers la sauvegarde; si le dossier de destination n'existe pas, le crée // ex: copie("/media/truc","www/test", "/home/joe/www/test/1.c", "/media/truc/www/test/1.c") void copie(char* repsauv, char* dosrel, char* flocal, char* fsauv){ // Pour gagner du temps regarde si le dossier de destination existe, si non il les crée un par un char* destfolder = NULL; asprintf(&destfolder, "%s/%s", repsauv, dosrel); struct stat st = {0}; // Crée chaque sous-dossier s'il n'existe pas if(stat(destfolder, &st) == -1){ char* tmp = repsauv; // /media/truc/C416-16A6 char* str = strdup(dosrel); // test/machin/truc char* token = strtok(str, "/"); while (token != NULL) { asprintf(&tmp, "%s/%s", tmp, token); // /media/truc/C416-16A6/test puis /media/truc/C416-16A6/test/machin struct stat st = {0}; //crée le dossier s'il n'existe pas if (stat(tmp, &st) == -1) mkdir(tmp, 0700); token = strtok(NULL, "/"); } free(str); } free(destfolder); // Maintenant que le dossier existe, y copie le fichier char c[8192]; FILE* stream_R = fopen(flocal, "r"); FILE* stream_W = fopen(fsauv, "w"); while (!feof(stream_R)){ size_t bytes = fread(c, 1, sizeof(c), stream_R); if (bytes) {fwrite(c, 1, bytes, stream_W);} } fclose(stream_R); fclose(stream_W); } /*==========================================================*/ void scancopie(char* dos, char* repsauv, char testSansCopie) { // Commence par scanner les dossiers uniquements DIR *dip = opendir(dos); if (dip == NULL) {printf("Impossible d'ouvrir le répertoire %s\n", dos); return;} struct dirent *dit; dossiersscannés++; while ((dit = readdir(dip)) != NULL){ if (dit->d_type == 4){ if(!strcmp(dit->d_name, ".") || !strcmp(dit->d_name, "..")) continue; dossiersscannés++; char* sousdos = NULL; asprintf(&sousdos, "%s%s%s", dos,"/",dit->d_name); //printf("dos %s/%s\n", dos, dit->d_name); scancopie(sousdos, repsauv, testSansCopie); // dossier trouvé? le scanne free(sousdos); } } closedir(dip); // puis scanne les fichiers dip = opendir(dos); if (dip == NULL) {printf("Impossible d'ouvrir le répertoire %s\n", dos); return;} while ((dit = readdir(dip)) != NULL){ if (dit->d_type == 8){ fichiersscannés++; char* flocal = NULL; asprintf(&flocal, "%s/%s", dos, dit->d_name); char* fsauv = NULL; asprintf(&fsauv, "%s/%s/%s", repsauv, dos, dit->d_name); // Regarde si ce fichier existe dans la sauvegarde if (access(fsauv, F_OK) != 0){ // il n'existe pas → le copie printf("*existe pas*\t%s\n", flocal); nbfichiersacopier++; if(testSansCopie==0) copie(repsauv, dos, flocal,fsauv); } // il existe. Ont-ils la même taille? Sinon celui en local est plus récent? else{ struct stat st; stat(flocal, &st); off_t flocalsize = st.st_size; time_t flocaldate = st.st_mtime; stat(fsauv, &st); off_t fsauvsize = st.st_size; time_t fsauvdate = st.st_mtime; if(flocalsize != fsauvsize){ // ils n'ont pas la même taille → copie //printf("*!=taille* %ld→%ld\t%s\n", flocalsize, fsauvsize, flocal); printf("*!=taille*\t%s\n", flocal); nbfichiersacopier++; if(testSansCopie==0) copie(repsauv, dos, flocal,fsauv); } else if((flocaldate -1) > fsauvdate){ // ils ont la même taille, mais le fichier source a été modifié après la sauvegarde //printf("*!=date* %ld→%ld %s\n", flocaldate, fsauvdate, flocal); printf("*!=date*\t%s\n", flocal); nbfichiersacopier++; if(testSansCopie==0) copie(repsauv, dos, flocal,fsauv); } //else printf("%s\n", flocal); // ils ont la même taille/même date de modif ne rien faire } free(fsauv); free(flocal); } } closedir(dip); } /*==========================================================*/ void scansuppr(char* dos, char* repsauv) // www/test, /media/machin/truc { char* dsauv = NULL; asprintf(&dsauv, "%s/%s", repsauv, dos); // /media/machin/truc/www/test // Commence par scanner les dossiers uniquements DIR *dip = opendir(dsauv); if (dip == NULL) {printf("Impossible d'ouvrir le répertoire %s\n", dsauv); return;} struct dirent *dit; while ((dit = readdir(dip)) != NULL){ if (dit->d_type == 4){ if(!strcmp(dit->d_name, ".") || !strcmp(dit->d_name, "..")) continue; char* sousdos = NULL; asprintf(&sousdos, "%s/%s", dos, dit->d_name); //printf("sousdos %s\n", sousdos); DIR *diq; if ((diq = opendir(sousdos)) == NULL) printf("*DOS supprimé*\t%s\n", sousdos); else closedir(diq); scansuppr(sousdos, repsauv); free(sousdos); } } closedir(dip); // puis scanne les fichiers dip = opendir(dsauv); if (dip == NULL) {printf("Impossible d'ouvrir le répertoire %s\n", dsauv); return;} while ((dit = readdir(dip)) != NULL){ if (dit->d_type == 8){ char* flocal = NULL; asprintf(&flocal, "%s/%s", dos, dit->d_name); // un fichier a été supprimé en local → le supprimer sur la sauvegarde if (access(flocal, F_OK) != 0){ // il n'existe pas → le supprime sur la sauvegarde char* fsauv = NULL; asprintf(&fsauv, "%s/%s", dsauv, dit->d_name); printf("*FIC supprimé*\t%s\n", flocal); //printf("*fic supprimé*\t\t%s\n", dit->d_name); nbfichiersasupprimer++; unlink(fsauv); free(fsauv); } free(flocal); } } closedir(dip); // Après avoir effacé ses fichiers, regarde si ce dossier existe en local pour éventuellement supprimer ce dossier sur la sauvegarde if ((dip = opendir(dos)) == NULL){ //printf("*dos supprimé*\t%s\n", dos); rmdir(dsauv); nbdossiersasupprimer++; } else closedir(dip); free(dsauv); } /*==========================================================*/ int main(int argc, char *argv[]) { if (argc < 3){ printf("\tSauvegarde d'un dossier vers un autre dossier, une autre partition, ou un disque externe\n\ Usage: %s [dossier relatif à scanner ] [chemin absolu dossier sauvegarde] options: [sanscopie OU nepassupprimer]\n\ Mettre chemins sans / final\n\ Exemple %s www/test /media/truc/C416-16A6 sanscopie\n \ Exemple %s www/test /media/truc/C416-16A6\n \ Exemple %s www/test /media/truc/C416-16A6 nepassupprimer\n\ sanscopie : indique le nombre de dossiers/fichiers à supprimer mais ne les copie/supprime pas.\n\ nepassupprimer : copie les fichiers vers la sauvegarde, mais ne fait pas le scan en sens inverse ni ne supprime.\n\ sans option : scanne local → sauvegarde et copie, puis scanne sauvegarde → local et supprime.\n", argv[0], argv[0], argv[0], argv[0]); return 0; } // Regarde si le répertoire de sauvegarde est monté DIR *dip = opendir(argv[2]); if (dip == NULL) {printf("\tLe répertoire de sauvegarde est inaccessible: partition non montée?\n"); return 1;} else closedir(dip); char testSansCopie = (argc==4 && !strcmp(argv[3],"sanscopie")) ? 1 : 0; // Regarde les fichiers à copier vers la sauvegarde scancopie(argv[1], argv[2], testSansCopie); //printf("\tJ'ai scanné %d dossiers, %d fichiers\n", dossiersscannés, fichiersscannés); //printf("\t%d fichier%s %s\n", nbfichiersacopier, (nbfichiersacopier>1)?"s":"", (testSansCopie) ? "à copier" : "ont été copiés"); // Si l'option "nepassupprimer" est présente, alors ne pas scanner ni supprimer if(argc==4 && !strcmp(argv[3],"nepassupprimer")); else //sinon scanne en sens inverse de la sauvegarde vers local, //pour regarder des fichiers ont été supprimés en local, donc à supprimer sur la sauvegarde scansuppr(argv[1], argv[2]); //printf("\t%d dossier%s, %d fichier%s %s\n", // nbdossiersasupprimer, (nbdossiersasupprimer>1)?"s":"", // nbfichiersasupprimer, (nbfichiersasupprimer>1)?"s":"", // (testSansCopie) ? "à supprimer" : "ont été supprimés"); printf("Scanné dans %s %d dossiers, %d fichiers → %d fichier%s %s. %d dossier%s, %d fichier%s %s\n", argv[1], dossiersscannés, fichiersscannés, nbfichiersacopier, (nbfichiersacopier>1)?"s":"", (testSansCopie) ? "à copier" : "ont été copiés", nbdossiersasupprimer, (nbdossiersasupprimer>1)?"s":"", nbfichiersasupprimer, (nbfichiersasupprimer>1)?"s":"", (testSansCopie) ? "à supprimer" : "ont été supprimés"); return 0; }