C言語でフォルダ(ディレクトリ)を丸ごとコピーする
※2012年10月1日、Ubuntu 12.10でコンパイルできるように修正。
※より複雑なコードを見たい場合はsnowcpのソースを見てやって下さい。
Linuxで動作する良さ気なサンプルコードを見つけられなかったので自分で書いてみた。実用性は皆無。
コンパイルにはGLibが必要なのでインストールしておく。
sudo apt-get install build-essential libgtk-3-dev
コンパイルするときは以下のように実行する。
gcc *.c -Wall -Wextra -O2 `pkg-config --cflags --libs gtk+-3.0` -std=c99 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
以下、ソース。4つのファイルに分割してある。
/* a.c */ #include <stdio.h> #include <limits.h> #include <stdlib.h> #include <linux/limits.h> #include <unistd.h> #include <glib.h> void check_dir(char *src); int main(int argc, char *argv[]) { char src[PATH_MAX]; char dst[PATH_MAX]; if(argc == 3) { if(g_file_test(argv[1], G_FILE_TEST_IS_DIR) && g_file_test(argv[2], G_FILE_TEST_IS_DIR)) { if((realpath(argv[1], src) == NULL) || (realpath(argv[2], dst) == NULL)) { fprintf(stderr, "絶対パスの作成に失敗しました\n"); } else if(chdir(dst) == -1) { fprintf(stderr, "コピー先にアクセスできません\n"); } else { check_dir(src); } } else { fprintf(stderr, "引数はフォルダである必要があります\n"); } } else { puts("引数が正しくありません"); } return EXIT_SUCCESS; }
/* b.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include <dirent.h> #include <glib.h> void cp_file(char *src, char *dst); void check_dir(char *src) { DIR *dp; char *src_basename = g_path_get_basename(src); if(mkdir(src_basename, 0755)) { fprintf(stderr, "フォルダの作成に失敗しました\n"); return; } else if(chdir(src_basename) == -1) { fprintf(stderr, "コピー先にアクセスできません\n"); return; } else if((dp = opendir(src)) == NULL) { fprintf(stderr,"%sを開けません\n", src); if(chdir("..") == -1) { fprintf(stderr, "コピー先にアクセスできません\n"); exit(EXIT_FAILURE); } return; } struct dirent *entry; while((entry = readdir(dp)) != NULL) { /* * 引数の最後のNULLを忘れると、missing sentinel in function call、という警告が出る。 * g_build_pathを使わない場合はstrcatやstrncatを使うことになるか。 * strlcatはglibcには無いらしいのでg_strlcatを使う手も。 * いや、sprintf使った方が楽か。 */ char *next_file = g_build_path(G_DIR_SEPARATOR_S, src, entry->d_name, NULL); if(g_file_test(next_file, G_FILE_TEST_IS_DIR)) { if(strcmp(".", entry->d_name) == 0 || strcmp("..", entry->d_name) == 0) { continue; } check_dir(next_file); } else if(g_file_test(next_file, G_FILE_TEST_IS_REGULAR)) { cp_file(next_file, entry->d_name); } if(next_file != NULL) { free(next_file); next_file = NULL; } } if(closedir(dp) == -1) { fprintf(stderr, "コピー先でエラーが発生しました\n"); } if(chdir("..") == -1) { fprintf(stderr, "コピー先にアクセスできません\n"); exit(EXIT_FAILURE); } }
/* c.c */ #define FILE_BUF 8192000 #include <stdio.h> char buf[FILE_BUF]; void ch_time_and_mod(char *src, char *dst); void cp_file(char *src, char *dst) { FILE *src_f; FILE *dst_f; int rcount = 0; int wcount = 0; printf("%s\n", dst); if((src_f = fopen(src, "rb")) == NULL) { fprintf(stderr, "%sを開けません\n", src); return; } setvbuf(src_f, NULL, _IOFBF, FILE_BUF); if((dst_f = fopen(dst, "wb")) == NULL) { fprintf(stderr, "%sを開けません\n", dst); fclose(src_f); return; } setvbuf(dst_f, NULL, _IOFBF, FILE_BUF); while((rcount = fread(buf, 1, FILE_BUF, src_f)) > 0) { if((wcount = fwrite(buf, 1, rcount, dst_f)) != rcount) { fprintf(stderr, "%sに書き込む際にエラーが発生しました\n", dst); break; } } if(fclose(src_f) == EOF) { fprintf(stderr, "コピー元ファイルを閉じる際のエラー\n"); } if(fclose(dst_f) == EOF) { fprintf(stderr, "コピー先ファイルを閉じる際のエラー\n"); } ch_time_and_mod(src, dst); }
/* d.c */ #define _GNU_SOURCE #include <stdio.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/types.h> void ch_time_and_mod(char *src, char *dst) { struct stat stat_buf; if(stat(src, &stat_buf) == -1) { fprintf(stderr, "%s : ファイル情報取得エラー\n", dst); return; } else { if(chmod(dst, stat_buf.st_mode) == -1) { fprintf(stderr, "%s : ファイル情報変更エラー\n", dst); } else { struct timespec time_s[2]; time_s[0].tv_sec = stat_buf.st_atime; time_s[1].tv_sec = stat_buf.st_mtime; time_s[0].tv_nsec = stat_buf.st_atim.tv_nsec; time_s[1].tv_nsec = stat_buf.st_mtim.tv_nsec; /* 第四引数は0 or AT_SYMLINK_NOFOLLOW */ if(utimensat(AT_FDCWD, dst, time_s, AT_SYMLINK_NOFOLLOW) == -1) { fprintf(stderr, "%s : ファイル情報変更エラー\n", dst); } } } }
関連:C言語でファイルをコピーする(マルチスレッド&ダイレクトI/O編)
以下は修正前の古いコード。
※2010年9月14日。ちょっとだけ修正。
ググってみたけどLinuxで動作する良さ気なサンプルコードを見つけられなかったので自分で書いてみた。実用性は皆無。2GB以上のファイルを扱うには#define _FILE_OFFSET_BITS 64が必要とか、lstat()じゃなくてlstat64()を使うとか、()utime()は廃止予定とか、勉強になった。
/* gcc -Wall -std=c99 -D_FILE_OFFSET_BITS=64 `pkg-config --cflags --libs glib-2.0` -o dir_copy dir_copy.c コンパイル時のオプションではなくdefineを使用した場合は gcc -Wall -std=c99 `pkg-config --cflags --libs glib-2.0` -o dir_copy dir_copy.c gcc -Wall -std=gnu99 `pkg-config --cflags --libs glib-2.0` -o dir_copy dir_copy.c とした場合は、#define __USE_ATFILE が要らない */ #define _FILE_OFFSET_BITS 64 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <dirent.h> #include <limits.h> #define __USE_ATFILE #include <fcntl.h> #undef __USE_MISC #include <sys/stat.h> #include <sys/time.h> #include <unistd.h> #include <utime.h> #include <glib.h> #define IO_BUF 8192000 #define FILE_BUF 8192000 #define PATH_LEN 8192 /* ヘッダファイルをincludeし忘れたり、プロトタイプ宣言が無い外部関数を呼び出した場合、 引数や戻り値の型がintになるらしい(C99の場合?古いCではプロトタイプ宣言は無いことも多いらしい)。 ↓することでwarningが出なくなるが、悪手。ちゃんとヘッダをincludeすればいいだけの話。 */ char *realpath(const char *path, char *resolved_path); void file_cp(char *from, char *to); void check_dir(char *from); char buf[IO_BUF]; /*ファイルを読み書きする際のバッファ*/ void file_cp(char *from, char *to) { struct stat stat_buf; /*ファイルの状態を入れる構造体*/ /* struct utimbuf times;*/ /*タイムスタンプ変更用構造体。utime用*/ struct timespec time_s[2]; /*タイムスタンプ変更用構造体。utimensat用*/ FILE *ffrom; FILE *tto; int rcount; int wcount; printf("%sのコピーを開始します\n", to); if((ffrom = fopen(from, "rb")) == NULL) { fprintf(stderr, "\n"); printf("コピー元ファイルを開けません\n"); return; } setvbuf(ffrom, NULL, _IOFBF, FILE_BUF); if((tto = fopen(to, "wb")) == NULL) { fprintf(stderr, "\n"); printf("コピー先ファイルを開けません\n"); return; } setvbuf(tto, NULL, _IOFBF, FILE_BUF); while((rcount = fread(buf, 1, IO_BUF, ffrom)) > 0) { if((wcount = fwrite(buf, 1, rcount, tto)) != rcount) { fprintf(stderr, "\n"); printf("ファイル書き込み時にエラーが発生しました\n"); exit(1); } } if(fclose(ffrom) == EOF) { printf("コピー元ファイルを閉じる際のエラー\n"); exit(1); } if(fclose(tto) == EOF) { printf("コピー先ファイルを閉じる際のエラー\n"); exit(1); } /*元ファイルの状態を取得*/ lstat64(from, &stat_buf); chmod(to, stat_buf.st_mode); time_s[0].tv_sec = stat_buf.st_atime; time_s[0].tv_nsec = stat_buf.st_atimensec; time_s[1].tv_sec = stat_buf.st_mtime; time_s[1].tv_nsec = stat_buf.st_mtimensec; /*第四引数は0かAT_SYMLINK_NOFOLLOW*/ utimensat(AT_FDCWD, to, time_s, AT_SYMLINK_NOFOLLOW); /* times.actime = stat_buf.st_atime; times.modtime = stat_buf.st_mtime; utime(to, ×); */ } /*引数は絶対パス*/ void check_dir(char *from) { char current_dir[PATH_LEN]; /*CWD*/ char *separator = "/"; /*パスの区切り文字. Windowsなら\*/ char *from_basename; /*コピー元ファイル名・フォルダ名*/ char *next_file; DIR *dp; struct dirent *entry; struct stat stat_buf; struct utimbuf times; /*タイムスタンプ変更用構造体*/ gboolean gb; from_basename = g_path_get_basename(from); if(mkdir(from_basename, 0755)) { puts("mkdirに失敗しました"); return; } chdir(from_basename); getcwd(current_dir, sizeof(current_dir)); printf("cwd is : %s \n", current_dir); if((dp = opendir(from)) == NULL) { fprintf(stderr,"ディレクトリを開けません : %s\n", from); return; } while((entry = readdir(dp)) != NULL) { if(next_file != NULL) { free(next_file); next_file = NULL; } /* 引数の最後のNULLを忘れると、missing sentinel in function call、という警告が出る。 g_build_pathを使わない場合はstrcatやstrncatを使うことになるか。 strlcatはglibcには無いらしいのでg_strlcatを使う手も。 いや、sprintf使った方が楽か。 */ next_file = g_build_path(separator, from, entry->d_name, NULL); gb = g_file_test(next_file, G_FILE_TEST_IS_DIR); if(gb) { if(strcmp(".", entry->d_name) == 0 || strcmp("..", entry->d_name) == 0) { continue; } check_dir(next_file); } else { file_cp(next_file, entry->d_name); } } chdir(".."); lstat(from, &stat_buf); times.actime = stat_buf.st_atime; times.modtime = stat_buf.st_mtime; utime(from_basename, ×); if(next_file != NULL) { free(next_file); next_file = NULL; } closedir(dp); } int main(int argc, char *argv[]) { gboolean gb; char current_dir[PATH_LEN]; char from[PATH_LEN]; char to[PATH_LEN]; if(argc == 3) { gb = g_file_test(argv[1], G_FILE_TEST_IS_DIR); if(gb) { gb = g_file_test(argv[2], G_FILE_TEST_IS_DIR); if(gb) { if((realpath(argv[1], from) == NULL) || (realpath(argv[2], to) == NULL)) { puts("絶対パスの作成に失敗しました"); exit(1); } getcwd(current_dir, sizeof(current_dir)); printf("cwd is : %s \n", current_dir); printf("from : %s\n", from); printf("to : %s\n", to); chdir(to); check_dir(from); } } } else { puts("引数が正しくありません"); } return 0; }