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, &times);
*/
}

/*引数は絶対パス*/
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, &times);

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

update-apt-xapian-indexがCPUを喰うバグに関して

408 :login:Penguin [sage] :2010/06/05(土) 16:40:41 id:txwwNgMs (1/2)
update-apt-xapian-indexというのがものすごくうざくてしょうがないんですけど
一切起動しないようにしたいんですけど、やり方教えてください。


409 :login:Penguin [sage] :2010/06/05(土) 17:22:20 id:CwGBGHOb (4/5)
>>408
これかな?
https://forums.ubuntulinux.jp/viewtopic.php?pid=54268


410 :login:Penguin [sage] :2010/06/05(土) 18:10:45 ID:Kh+m5Ogq
>>408
俺も困っていたので↓を試してみた。リソース消費量と実行時間が減る……らしい。
http://ubuntuforums.org/showpost.php?s=ad8f4de5110c0d759883cb436d3f8726&p=9304431&postcount=8

完全に無効化してしまいたいなら、実行できないようにする。
http://ubuntuforums.org/showpost.php?s=ad8f4de5110c0d759883cb436d3f8726&p=7047675&postcount=4

いちおう問題はないみたいだけど自己責任でよろしく。


411 :login:Penguin [sage] :2010/06/05(土) 19:14:16 id:txwwNgMs (2/2)
>>409-410
ありがとうございます。
sudo chmod 644 /etc/cron.weekly/apt-xapian-index
これ実行したんですけど、その場所にまだファイルが残ってるみたいなんですけど
手動でapt-xapian-indexを削除すればいいんでしょうか?


414 :login:Penguin [sage] :2010/06/05(土) 20:01:55 id:CwGBGHOb (5/5)
>>411
実行権でググれ。
結論としては削除しなくていい。

関連:update-apt-xapian-indexでCPU使用率が100%近くに

monoの文字化けは/etc/fonts/conf.d/89-ttf-thai-tlwg-synthetic.confをゴニョゴニョすればいいらしい

840 :login:Penguin [sage] :2010/06/09(水) 00:13:47 id:kn9BJ246 (1/3)
低レベル化が顕著だなあ。
まさかmonoの文字化けすら直せない奴らばっかとは。


851 :login:Penguin [sage] :2010/06/09(水) 02:36:08 id:kn9BJ246 (3/3)
池沼に答え教えるのも嫌だが、普通の奴もいるだろうから正解書いとく。
まず俺は本家版しか使ってないから、池沼に合わせて日本語版落として環境作った。
インストール直後に実行すると、たしかに一部文字化けする。
/etc/fontsを見直していくと
sudo rm /etc/fonts/conf.d/89-ttf*
すると文字化けが直った。実際にこのファイルを見ると、
MS Sans Serifに別のフォントを問答無用で当ててて、たぶんこれが悪さしてる。
池沼以外で困ってる人は試してみてください。池沼はやるな

Ubuntu 9.10で液晶ディスプレイを設定する(GeForce編)

液晶モニターiiyamaProLite B2409HDS-B1 PLB2409HDS-B1)を購入したので、設定したこととかメモ。

まず、モニター自身の設定を、

  • コントラスト:13
  • 輝度:25
  • エコモード:オフ
  • 映像設定:標準
  • ACR:オフ
  • Gamma:Mode 1
  • 色温度ユーザ(RED 100GREEN 89BLUE 78
  • 色温度ユーザ(RED 90GREEN 81BLUE 70
  • 言語:日本語

にする。次に、

システム > システム管理 > NVIDIA X Server Settings

を起動し、ディスプレイガンマの調整ガンマ補正の話簡易ガンマ値チェッカなどのページを見ながら、X Server Color CorrectionのGammaの値を変更(上記設定でgamma=2.0に調整する場合、Gammaの値は0.850ぐらいになる)し、Confirm Currentボタンを押して終了する。ログイン時に設定を反映させるためには~/.profileの末尾にnvidia-settings -lと記述しておく必要があるらしい。

※参考:NVIDIA製GPU/ドライバ使用時の設定ツールと明るさ/コントラスト/ガンマ調整について

ついでにDPIも変更しておく。

システム > 設定 > 外観の設定 > フォント > 詳細

で、解像度を96から120 ドット/インチに変更。

今まで使っていたiiyama ProLite E431S-3と比べて目が疲れにくいような気がする。Full HDの世界を味わってしまうと、もうSXGAには戻れんなぁと思ったり。

コントラスト輝度の初期値はそれぞれ50と100辺りになっていたと思うが、そのままでは眩しすぎるので下げる。輝度は40だと明るく20だと暗すぎる気がしたので25辺りに、コントラストはそのおよそ半分に。

色温度は、ググると5000Kや6500Kぐらいが良いと書かれたページが見つかるが、このディスプレイにはそういった数値を指定する機能はなく、rgbで設定するようになっている。予め、

  • ウォーム(RED 90GREEN 89BLUE 73) → 緑っぽい
  • ノーマル(RED 86GREEN 85BLUE 90) → 黄っぽい
  • クール(RED 73GREEN 75BLUE 90) → 青っぽい

の三つの設定が用意されているが(sRGBは置いといて)、液晶モニターの色温度というページによると5000Kというのは赤っぽい画面らしいので、自分でそれっぽく設定。

Ubuntu 9.10でext4を使用する場合は注意が必要?

307 :login:Penguin [sage] :2009/10/29(木) 22:53:35 id:ZN9lU4Hj
リリースノートに、ext4だと512MB以上のファイルが壊れるっていうバグが
Known issue になってるから、インストールするときにはext3を使って
あとでupgradeすべし。


311 :login:Penguin [sage] :2009/10/29(木) 23:14:40 id:dT8wLN/a
>>307
リンク先に、このバグが取り除けなければ、デフォルトのfile systemをext3
ロールバックするべきだろうってなことがかかれてるけど、
結局ex4デフォでリリースなのかな?


320 :login:Penguin [sage] :2009/10/30(金) 01:14:18 id:vlr+TbD2
512MB以上のファイル壊れまくり祭り?


321 :login:Penguin [] :2009/10/30(金) 01:34:23 id:Z3mN1rLr
ファイルシステムの不具合は不味いよなぁ。
ext3使えば良いんだけど、せっかくだしfixするまで待つわ。
しかし、リリースノートの和訳でどうしてこの事に触れないんだろ?

KarmicKoala/ReleaseNotes/ja

関連:Ubuntu 9.04でext4を使用する場合は注意が必要

Ubuntu 9.10 betaの印刷機能でテキストファイルやHTMLをPDF化した場合、一部の文字コードが変わる?

Ubuntu 9.04の時にあった、PDF出力時に康煕部首を含むフォントを使用すると一部の文字の文字コードが変わるバグは直っているっぽいが、代わりに一部の文字がCJK部首補助に変わってしまうバグに気づいた。鬼(U+9B3C)が⻤(U+2EE4)になっちゃったりするっぽい。

Ubuntuを家族で共有しているパソコンで使う場合の注意点

872 :login:Penguin [sage] :2008/05/16(金) 00:43:00 id:aIjgqy8s
デフォルトで /home/ユーザ名 のアクセス権が755ってのと、
updatedbが有効なのは、デスクトップ用途では欠点だな。
他のユーザの~/Documentsが見れたり、locateで検索できたりすると、
あんなファイルやこんなファイルが身内にバレちゃってさぁ大変なことに。

873 :login:Penguin [sage] :2008/05/16(金) 00:55:24 id:MWAuQhQt
>>872
linuxでは基本的にユーザ同士で情報を共有しようというのもあったんだよね。
~/.bashrcや~/.emacsを観れるようにしてノウハウを公開する意図があったりして。
でもまあ、これからのデスクトップユーザ、Windowsから流れてくる人たちにとっては、
デフォルトで見えないほうがいいのかもねえ・・・

874 :login:Penguin [] :2008/05/16(金) 01:42:48 id:kgd04gO/
>>872
最初は755でいいんだよ、そこでアクセス拒否られると、
「ああ、何かかくしてる」
ってことになるんだよ。
あんなのやこんなのをかくしたいなら、その方法を探ればOK
.systemほにゃららとかにしてアクセス権変えといたらいいんだよ。

Windowsだと制限ユーザは他のユーザのマイドキュメントを見たり出来ないが、Ubuntuだと見放題なので複数のユーザで使用する場合は注意が必要と。