unix_programming 3章_1

「Unix/Linuxプログラミング 理論と実践」読み進めています。

3章に進みました。3章では主にstat情報を取り扱うようになりました。
いくつかの課題をかいつまんで説明します。

一つ目、2章で自作したcpを拡張してコピー先をディレクトリに指定した場合や、ディレクトリからディレクトリへの再起コピーなどを実装する。
下記のように実装した。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

#include <dirent.h>

#define BUFFERSIZE 4096
#define COPYMODE 0644

//
// 3.14
// extend cp.
// if src and dst are dir. cp all files under src dir to dest.
//
int cp_file_to_file(char *src, char *dst);
int cp_file_to_dir(char *src, char *dst);
int cp_dir_to_dir(char *src, char *dst);

int main(int ac, char *av[])
{
	char *filename;
	char *out_file;

	struct stat in_st;
	struct stat out_st;

	if (ac < 3) {
		printf("usage -- cp01 <source> <destination>\n");
		exit(-1);
	}

	if (stat(av[1], &in_st) == -1) {
		printf("error. failed to get stat info.");
		exit(-1);
	}

	if (stat(av[2], &out_st) != -1) {
		// check mode.
		if (S_ISCHR(in_st.st_mode) || S_ISCHR(out_st.st_mode)) {
			printf("cannot cp character device.");
			exit(-1);
		}
		if (S_ISBLK(in_st.st_mode) || S_ISBLK(out_st.st_mode)) {
			printf("cannot cp block device.");
			exit(-1);
		}

		// validate dstination. whether if its dir or not.
		if (S_ISDIR(in_st.st_mode) && S_ISDIR(out_st.st_mode)) {
			// both of in and out are dir.
			return cp_dir_to_dir(av[1], av[2]);

		} else if (S_ISDIR(out_st.st_mode)) {
			// only out is dir.
			return cp_file_to_dir(av[1], av[2]);

		} else if (S_ISDIR(in_st.st_mode)) {
			// only in is dir.
			printf("error. cannot copy dir to file");
			exit(-1);
		} else {
			if (in_st.st_ino == out_st.st_ino) {
				printf("error. input file and output file must be different.");
				exit(-1);
			}
		}
	}

	return cp_file_to_file(av[1], av[2]);
}

int cp_file_to_file(char *src, char *dst)
{
	int in_fd, out_fd, len;
	char buf[BUFFERSIZE];

	// printf("copy from %s to %s\n", src, dst);

	if ((in_fd = open(src, O_RDONLY)) == -1) {
		perror("failed to open source file.");
		exit(-1);
	}

	if ((out_fd = creat(dst, COPYMODE)) == -1) {
		perror("failed to open destination file.");
		exit(-1);
	}

	while ((len = read(in_fd, buf, BUFFERSIZE)) > 0) {
		if (write(out_fd, buf, len) == -1) {
			perror("failed to write output file.");
			exit(-1);
		}
	}

	return 1;
}

int cp_file_to_dir(char *src, char *dst)
{
	char *filename, *out_file;
	int ret, len, i, idx;

	out_file = malloc(sizeof(char) * BUFFERSIZE);

	len = strlen(src);
	for (i=0; i<len; i++) { 		if (src[i] == '/') { 			idx = i+1; 		} 	} 	out_file = strcpy(out_file, dst); 	len = strlen(out_file); 	out_file[len] = '/'; 	strcpy(&out_file[len+1], &src[idx]); 	ret = cp_file_to_file(src, out_file); 	free(out_file); 	return ret; } int cp_dir_to_dir(char *src, char *dst) { 	DIR * dp; 	struct dirent *entry; 	char *in_file; 	int len; 	dp = opendir(src); 	while ((entry = readdir(dp)) != NULL) { 		if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
			continue;
		}
		in_file = malloc(sizeof(char) * BUFFERSIZE);
		in_file = strcpy(in_file, src);
		len = strlen(in_file);
		in_file[len] = '/';
		strcpy(&in_file[len+1], entry->d_name);

		if (cp_file_to_dir(in_file, dst) == -1) {
			return -1;
		}
		free(in_file);
	}

	closedir(dp);
	return 1;
}

opendir, readdir, statを用いてファイルやディレクトリの属性を取得している。
src, dstがそれぞれファイルの場合ディレクトリの場合などをパターン分けして別々に切り出したメソッド実装を呼び出しているシンプルな構成である。

高級言語に慣れている身として、直感的に一番馴染みがなかったのが、文字列操作である。
はじめスラッシュで区切られた文字列から末尾のセグメントだけ取得してくるような処理を書いた時(要するにファイル名だけを取得したかった)
strtokというライブラリ関数を利用し、一見うまく動作しているように見えたが、実際操作対象の文字列を破壊していた。
また文字列結合などを行うときには、ちゃんと文字列用のヒープ領域を確保してあげないと、周りの領域のデータが破壊される。

Cでは馴染みのバッファオーバーフローなのだが、実際にここまでと実感したのははじめてである。
警告の一つも出さずにしれっと破壊活動を行っているのだから恐ろしい。
このトピックではstatが動向というよりも、きちんとヒープの確保と開放を行ってあげることがCでは最も大事だということを実感した。


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です