カテゴリー別アーカイブ: unix_programming

unix_programming4章_1

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

4章課題について進んでいきます。

4章はファイルシステムに関する章になります。
inodeだとかデータ領域だとかその辺のお話がメインです。
が、実際中身は3章とそんなに大差ないです。

1つめ。mkdir -pを実装せよ。
今まで読んでいる方は気づいているかもしれないですが、今オプションの処理をしてません。
それはそれで別の機会に実装すればよいのでは今は機能それ自体に集中してます。

さて実装ですが、下記のような感じに仕上げました。
mkdirはディレクトリを作成できますから、再帰的に処理するのが一番しっくり来るのではないかと想定して、再帰関数にしてます。
遡っていって、ディレクトリが存在した時点で探索を終了し、あとは巻き戻っていくと同時にディレクトリを逐次作成していきます。

この手の関数を作ってて思うのは・・・特にやっていることは上級言語と変わらないですね。
unixプログラミングといえど、もはやOS自体は洗礼されていますから、私達プログラマがやるのは適切なシステムコールやライブラリ関数をパズルのようにつないでいくことだけです。

ちょいと文字列やメモリの扱いがシビア(というか感覚が違うだけ)ですがそれにさえ慣れてしまえば、そんなに大したことはないのかな。
早く実務に耐えうるレベルまで学習していきたいと思います。

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

int recursive_mkdir(const char *path, mode_t mode);

//
// 4.15
// implements mkdir -r
//
int main(int argc, char *argv[])
{
	if (argc < 2) {
		printf("usage -- ./a.out [dir_path]\n");
		exit(-1);
	}

	recursive_mkdir(argv[1], 0644);
}

int recursive_mkdir(const char *path, mode_t mode)
{
	struct stat dir;
	char *ppath;
	int i=0, idx=0, len=0;

	// check existence.
	if (!stat(path, &dir)) {
		if (S_ISDIR(dir.st_mode)) {
			return 1;
		}
	}

	if (strcmp(path, "/") == 0) {
		return 1;
	}

	// trim last segment.
	len = strlen(path);
	for (i=0; i<len; i++) {
		if (path[i] == '/') {
			idx = i;
		}
	}

	if (idx > 0) {
		ppath = malloc(sizeof(char) * (idx+1));
		ppath = strncpy(ppath, path, idx);
		ppath[idx] = '\0';
		if (recursive_mkdir(ppath, mode) == -1) {
			free(ppath);
			return -1;
		}
	}

	// mkdir
	if (mkdir(path, mode) == -1) {
		perror("failed to mkdir.");
		exit(-1);
	}

	return 1;
}

unix_programming3章_3

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

引き続き3章課題について進んでいきます。

3つ目。lsを拡張して、ディレクトリを再帰的に読み込んでいくような機能(ls -R)を実装せよ。
一つ前の記事で拡張したlsを更に拡張して、下記のように実装しました。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/dirent.h>
#include <unistd.h>

#include <pwd.h>
#include <grp.h>
#include <uuid/uuid.h>

#include <time.h>

#define BUFF_SIZE 4096

//
// 3.18
// implement recursive ls
//

struct sline {
	char *mode;
	int link;
	char *user;
	char *group;
	int size;
	char *datetime;
	char *file;
};

struct queue {
	char *dir;
	struct queue *next;
};

int create_stat_info(struct dirent *entry, struct sline *lentry, const char *root);
int get_digit(int num);
int enqueue(const char *dir, const char *root);
struct queue dequeue(struct queue qp);

static int luser=0, llink=0, lgroup=0, lsize=0;
static struct queue *qp;

int main(int argc, char* argv[])
{
	DIR *dp;
	struct dirent *dent;
	struct sline *lines;
	struct queue *tmp;
	int i=0, csize=1, cline=0;
	char *root;

	if (1 < argc) {
		root = argv[1];
	} else {
		root = malloc(sizeof(char) * 2);
		strcpy(root, ".");
	}


	qp = malloc(sizeof(struct queue));
	qp->dir = root;
	qp->next = NULL;

	while(qp != NULL) {
		luser=0; llink=0; lgroup=0; lsize=0;
		i=0; csize=1; cline=0;

		if ((dp=opendir(qp->dir)) == NULL) {
			printf("failed to opendir.");
			exit(-1);
		}

		// initialize malloc.
		lines = malloc(sizeof(struct sline) * csize);

		while ((dent=readdir(dp)) != NULL) {
			if (cline+1 > csize) {
				csize *= 2;
				lines = realloc(lines, sizeof(struct sline) * csize);
			}

			if (create_stat_info(dent, &lines[cline], qp->dir) != -1) {
				cline++;
			}
		}

		// print out
		printf("--- stat info of dir '%s' ---\n", qp->dir);
		for(i=0; i<cline; i++) {
			printf("%s ", lines[i].mode);
			printf("%*d ", llink+1, lines[i].link);
			printf("%*s ", luser+1, lines[i].user);
			printf("%*s ", lgroup+1, lines[i].group);
			printf("%*d ", lsize+1, lines[i].size);
			printf("%s ", lines[i].datetime);
			printf("%s \n", lines[i].file);
		}
		printf("\n");

		tmp = qp;
		qp = qp->next;
		free(tmp);
		free(lines);
		closedir(dp);
	}

	return 1;
}

int create_stat_info(struct dirent *entry, struct sline *lentry, char const *root)
{
	// init
	struct stat info;
	int len;
	char *smode, *suser, *sgroup, *sfile, *stime;
	struct sline line;
	char *filepath;

	filepath = malloc(sizeof(char) * (strlen(entry->d_name) + strlen(root) + 1));
	strcpy(filepath, root);
	len = strlen(root);
	filepath[len] = '/';
	strcpy(&filepath[len+1], entry->d_name);

	// validation.
	if (stat(filepath, &info) == -1) {
		return -1;
	}

	if (S_ISDIR(info.st_mode)) {
		if (strcmp(".", entry->d_name) != 0 && strcmp("..", entry->d_name) != 0) {
			// enqueue
			// printf("%s\n", entry->d_name);
			if (enqueue(entry->d_name, root) == -1) {
				printf("failed to enqueu.");
				exit(-1);
			}
		}
	}

	// create mode.
	smode = malloc(sizeof(char) * 11);
	strcpy(smode, "----------");

	// inode type.
	if (S_ISDIR(info.st_mode)) { smode[0] = 'd'; }
	if (S_ISCHR(info.st_mode)) { smode[0] = 'c'; }
	if (S_ISBLK(info.st_mode)) { smode[0] = 'b'; }

	// permission.
	if (info.st_mode & S_IRUSR) { smode[1] = 'r'; }
	if (info.st_mode & S_ISUID) { smode[1] = 's'; }
	if (info.st_mode & S_IWUSR) { smode[2] = 'w'; }
	if (info.st_mode & S_IXUSR) { smode[3] = 'x'; }
	if (info.st_mode & S_IRGRP) { smode[4] = 'r'; }
	if (info.st_mode & S_ISGID) { smode[4] = 's'; }
	if (info.st_mode & S_IWGRP) { smode[5] = 'w'; }
	if (info.st_mode & S_IXGRP) { smode[6] = 'x'; }
	if (info.st_mode & S_IROTH) { smode[7] = 'r'; }
	if (info.st_mode & S_IWOTH) { smode[8] = 'w'; }
	if (info.st_mode & S_IXOTH) { smode[9] = 'x'; }
	if (info.st_mode & S_ISVTX) { smode[9] = 't'; }

	// user
	struct passwd* user = getpwuid(info.st_uid);
	len = strlen(user->pw_name);
	suser = malloc(sizeof(char) * len);
	strcpy(suser, user->pw_name);

	if (len > luser) {
		luser = len;
	}

	// group
	struct group* group = getgrgid(info.st_gid);
	len = strlen(group->gr_name);
	sgroup = malloc(sizeof(char) * len);
	strcpy(sgroup, group->gr_name);

	if (len > lgroup) {
		lgroup = len;
	}

	// file
	len = strlen(entry->d_name);
	sfile = malloc(sizeof(char) * len);
	strcpy(sfile, entry->d_name);

	// time
	struct tm* stm = localtime(&info.st_atimespec.tv_sec);
	stime = malloc(sizeof(char) * 17);
	strcpy(stime, "                ");
	sprintf(stime, "%d %2d %2d %02d:%02d", stm->tm_year+1900, stm->tm_mon+1, stm->tm_mday, stm->tm_hour, stm->tm_min);

	// link
	len = get_digit(info.st_nlink);
	if (len > llink) {
		llink = len;
	}

	// size
	len = get_digit(info.st_size);
	if (len > lsize) {
		lsize = len;
	}

	line.mode = smode;
	line.user = suser;
	line.group = sgroup;
	line.file = sfile;
	line.link = info.st_nlink;
	line.size = info.st_size;
	line.datetime = stime;

	*lentry = line;

	return 1;
}

int get_digit(int num)
{
	int count = 0;

	while(1) {
		count++;
		if (num < 10) {
			return count;
		}
		num = (int)(num / 10);
	}

	return count;
}

int enqueue(const char *dir, const char *root)
{
	char *dirname;
	char buff[BUFF_SIZE];
	int idx, len;
	struct queue *q, *p;

	q = malloc(sizeof(struct queue));

	strcpy(buff, root);
	idx = strlen(root);
	buff[idx] = '/';
	strcpy(&buff[idx+1], dir);

	len = strlen(buff);
	dirname = malloc(sizeof(char) * len);
	strcpy(dirname, buff);
	q->dir = dirname;
	q->next = NULL;
	p = qp;
	while (p->next != NULL) {
		p = p->next;
	}
	p->next = q;

	return 1;
}

実行すると下記のように表示される。
一応機能は満たされている。

$ ./a.out   
--- stat info of dir '.' ---
drwxr-xr-x  16  user  staff    544 2015  8 12 00:40 . 
drwxr-xr-x  13  user  staff    442 2015  8 12 00:40 .. 
-rwxr-xr-x   1  user  staff  13812 2015  8 12 00:40 a.out 
drwxr-xr-x   3  user  staff    102 2015  8 12 00:39 a.out.dSYM 
-rw-r--r--   1  user  staff   1748 2015  8 11 09:41 cp03.c 
-rw-r--r--   1  user  staff   3040 2015  8 11 09:41 cp04.c 
drwxr-xr-x   4  user  staff    136 2015  8 12 00:39 dir 
drwxr-xr-x   4  user  staff    136 2015  8 12 00:39 dir2 
-rw-r--r--   1  user  staff    295 2015  8 11 09:21 hoge 
-rw-r--r--   1  user  staff    490 2015  8 11 09:21 ls01.c 
-rw-r--r--   1  user  staff   2059 2015  8 11 21:28 ls02.c 
-rw-r--r--   1  user  staff   3941 2015  8 11 23:03 ls03.c 
-rw-r--r--   1  user  staff   5419 2015  8 12 00:40 ls04.c 
-rw-r--r--   1  user  staff    839 2015  8 11 09:31 readtest.c 
-rw-r--r--   1  user  staff    647 2015  8 11 09:21 statinfo.c 
-rw-r--r--   1  user  staff    295 2015  8 11 09:32 test.c 

--- stat info of dir './a.out.dSYM' ---
drwxr-xr-x   3  user  staff  102 2015  8 12 00:40 . 
drwxr-xr-x  16  user  staff  544 2015  8 12 00:40 .. 
drwxr-xr-x   4  user  staff  136 2015  8 12 00:11 Contents 

--- stat info of dir './dir' ---
drwxr-xr-x   4  user  staff  136 2015  8 12 00:40 . 
drwxr-xr-x  16  user  staff  544 2015  8 12 00:40 .. 
-rw-r--r--   1  user  staff  295 2015  8 10 23:44 fuga 
-rw-r--r--   1  user  staff  295 2015  8 10 23:44 test.c 

--- stat info of dir './dir2' ---
drwxr-xr-x   4  user  staff  136 2015  8 12 00:40 . 
drwxr-xr-x  16  user  staff  544 2015  8 12 00:40 .. 
-rw-r--r--   1  user  staff  295 2015  8 10 23:46 fuga 
-rw-r--r--   1  user  staff  295 2015  8 12 00:32 test.c 

--- stat info of dir './a.out.dSYM/Contents' ---
drwxr-xr-x  4  user  staff  136 2015  8 12 00:40 . 
drwxr-xr-x  3  user  staff  102 2015  8 12 00:40 .. 
-rw-r--r--  1  user  staff  634 2015  8 11 23:57 Info.plist 
drwxr-xr-x  3  user  staff  102 2015  8 12 00:11 Resources 

--- stat info of dir './a.out.dSYM/Contents/Resources' ---
drwxr-xr-x  3  user  staff  102 2015  8 12 00:40 . 
drwxr-xr-x  4  user  staff  136 2015  8 12 00:40 .. 
drwxr-xr-x  3  user  staff  102 2015  8 12 00:39 DWARF 

--- stat info of dir './a.out.dSYM/Contents/Resources/DWARF' ---
drwxr-xr-x  3  user  staff    102 2015  8 12 00:40 . 
drwxr-xr-x  3  user  staff    102 2015  8 12 00:40 .. 
-rw-r--r--  1  user  staff  15830 2015  8 12 00:39 a.out 

今回の実装のポイントとしてはディレクトリをどのように再帰的に読み込むか。です。
アプローチとしては2つ考えられました。深さ優先探索と、幅優先探索です。
上記の実装は幅優先探索になります。

深さ優先の場合はディレクトリを発見した瞬間に再帰的にどんどん掘り下げていきます。
直感的でわかりやすい。実際できることならばこちらを選びたかったところがなくは無いです。

なんですがいくつかの問題点があったのですぐに辞めました。
一つ目に先に実装したアライメントについて、これはある一定のグループ(例えばディレクトリ単位)でその表示幅を最大長のものに合わせるような仕組みとして実装しました。
深さ優先にするとこの値が決定するまでにすごく時間がかかる。更に非常にバラける可能性がある。
と言うかそもそも、探索したディレクトリも含めて表示する必要があるので、表示ファイル名が長くなってしまうので全然実用的ではないです。

というわけでシンプルに幅優先で実装しました。
ディレクトリを再帰的に発見した場合は、いったんそのパス情報をキューに入れるようにしました。
それによって非常に効率的に処理できます。
いったん見つけたディレクトリはキューに入れてしまえば現在のプロセスでは考慮しなくてよいです。

プロセスはキューがからになるまで再帰的に処理を続けますので、それらは独立して動いているようになります。


unix_programming3章_2

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

引き続き3章課題について進んでいきます。

二つ目。lsを拡張して、アラインメントを揃えるように実装しなさい。という課題。
下記のように実装しました。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/dirent.h>
#include <unistd.h>

#include <pwd.h>
#include <grp.h>
#include <uuid/uuid.h>

#include <time.h>

//
// 3.10
// display to be alligned.
// to implement this function, make sline buffer for result.
//

struct sline {
	char *mode;
	int link;
	char *user;
	char *group;
	int size;
	char *datetime;
	char *file;
};

int create_stat_info(struct dirent *entry, struct sline *lentry);
int get_digit(int num);

static int luser=0, llink=0, lgroup=0, lsize=0;

int main(int argc, char* argv[])
{
	DIR *dp;
	struct dirent *dent;
	struct sline *lines;
	int i=0, csize=1, cline=0;

	if (1 < argc) {
		chdir(argv[1]);
	}

	if ((dp=opendir(".")) == NULL) {
		printf("failed to opendir.");
		exit(-1);
	}

	// initialize malloc.
	lines = malloc(sizeof(struct sline) * csize);

	while ((dent=readdir(dp)) != NULL) {
		if (cline+1 > csize) {
			csize *= 2;
			lines = realloc(lines, sizeof(struct sline) * csize);
		}

		if (create_stat_info(dent, &lines[cline]) != -1) {
			cline++;
		}
	}

	for(i=0; i<cline; i++) {
		printf("%s ", lines[i].mode);
		printf("%*d ", llink+1, lines[i].link);
		printf("%*s ", luser+1, lines[i].user);
		printf("%*s ", lgroup+1, lines[i].group);
		printf("%*d ", lsize+1, lines[i].size);
		printf("%s ", lines[i].datetime);
		printf("%s \n", lines[i].file);
	}

	closedir(dp);
}

int create_stat_info(struct dirent *entry, struct sline *lentry)
{
	// init
	struct stat info;
	int len;
	char *smode, *suser, *sgroup, *sfile, *stime;
	struct sline line;

	// validation.
	if (stat(entry->d_name, &info) == -1) {
		// i dont know yet that how to get symbolic link from 'stat' library function.
		return -1;
		// perror("failed");
		// exit(-1);
	}

	// create mode.
	smode = malloc(sizeof(char) * 11);
	strcpy(smode, "----------");

	// inode type.
	if (S_ISDIR(info.st_mode)) { smode[0] = 'd'; }
	if (S_ISCHR(info.st_mode)) { smode[0] = 'c'; }
	if (S_ISBLK(info.st_mode)) { smode[0] = 'b'; }

	// permission.
	if (info.st_mode & S_IRUSR) { smode[1] = 'r'; }
	if (info.st_mode & S_ISUID) { smode[1] = 's'; }
	if (info.st_mode & S_IWUSR) { smode[2] = 'w'; }
	if (info.st_mode & S_IXUSR) { smode[3] = 'x'; }
	if (info.st_mode & S_IRGRP) { smode[4] = 'r'; }
	if (info.st_mode & S_ISGID) { smode[4] = 's'; }
	if (info.st_mode & S_IWGRP) { smode[5] = 'w'; }
	if (info.st_mode & S_IXGRP) { smode[6] = 'x'; }
	if (info.st_mode & S_IROTH) { smode[7] = 'r'; }
	if (info.st_mode & S_IWOTH) { smode[8] = 'w'; }
	if (info.st_mode & S_IXOTH) { smode[9] = 'x'; }
	if (info.st_mode & S_ISVTX) { smode[9] = 't'; }

	// user
	struct passwd* user = getpwuid(info.st_uid);
	len = strlen(user->pw_name);
	suser = malloc(sizeof(char) * len);
	strcpy(suser, user->pw_name);

	if (len > luser) {
		luser = len;
	}

	// group
	struct group* group = getgrgid(info.st_gid);
	len = strlen(group->gr_name);
	sgroup = malloc(sizeof(char) * len);
	strcpy(sgroup, group->gr_name);

	if (len > lgroup) {
		lgroup = len;
	}

	// file
	len = strlen(entry->d_name);
	sfile = malloc(sizeof(char) * len);
	strcpy(sfile, entry->d_name);

	// time
	struct tm* stm = localtime(&info.st_atimespec.tv_sec);
	stime = malloc(sizeof(char) * 17);
	strcpy(stime, "                ");
	sprintf(stime, "%d %2d %2d %02d:%02d", stm->tm_year+1900, stm->tm_mon+1, stm->tm_mday, stm->tm_hour, stm->tm_min);

	// link
	len = get_digit(info.st_nlink);
	if (len > llink) {
		llink = len;
	}

	// size
	len = get_digit(info.st_size);
	if (len > lsize) {
		lsize = len;
	}

	line.mode = smode;
	line.user = suser;
	line.group = sgroup;
	line.file = sfile;
	line.link = info.st_nlink;
	line.size = info.st_size;
	line.datetime = stime;

	*lentry = line;

	return 1;
}

int get_digit(int num)
{
	int count = 0;

	while(1) {
		count++;
		if (num < 10) {
			return count;
		}
		num = (int)(num / 10);
	}

	return count;
}

実行すると下記のように表示される。
目的のアラインメントはなかなかいい感じに表示されている。

$ ./a.out
drwxr-xr-x  14  user  staff   476 2015  8 11 21:45 . 
drwxr-xr-x  13  user  staff   442 2015  8 11 21:39 .. 
-rwxr-xr-x   1  user  staff  9472 2015  8 11 21:45 a.out 
-rw-r--r--   1  user  staff  1748 2015  8 11 09:41 cp03.c 
-rw-r--r--   1  user  staff  3040 2015  8 11 09:41 cp04.c 
drwxr-xr-x   4  user  staff   136 2015  8 10 23:41 dir 
drwxr-xr-x   4  user  staff   136 2015  8 10 23:46 dir2 
-rw-r--r--   1  user  staff   295 2015  8 11 09:21 hoge 
-rw-r--r--   1  user  staff   490 2015  8 11 09:21 ls01.c 
-rw-r--r--   1  user  staff  2059 2015  8 11 21:28 ls02.c 
-rw-r--r--   1  user  staff  3941 2015  8 11 21:44 ls03.c 
-rw-r--r--   1  user  staff   839 2015  8 11 09:31 readtest.c 
-rw-r--r--   1  user  staff   647 2015  8 11 09:21 statinfo.c 
-rw-r--r--   1  user  staff   295 2015  8 11 09:32 test.c 

今回の実装のポイントとしては各要素のアライメントをどのように実装するか。それにつきる。
はじめはprintfのオプションとしてうまく表示できないか、と考えたが逐次表示していくと、すでに表示したものよりも大きな幅が必要になった場合もはや取り返しがつかない。
例えば1行目に表示したファイルのユーザ名は6文字であったが5行目のユーザ名が10文字であった場合などだ。

ここから言えるのは一点であり、一度すべてのファイル情報をバッファリングする必要がある。
その中で一番大きな幅を持つ要素を各列について計算する。結果としてはとてもシンプルである。

ただこのモデルの欠点としては含めるファイル数が大きくなりすぎるとメモリ上に保持できなくなるということだ。
(最もただの文字列だけでそこまで大きなものはすでに見るきがしないが)

実装の側面からは一行分のデータをそれぞれ各業で分割して捉え、それを構造体として表現してみた。


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では最も大事だということを実感した。


unix programming 2章_2

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

2章の最後の課題。自分でtailコマンドを実装せよ。
下記のように仕上げた。ファイルをオープンしてからfseekでいったん末尾に移動、その後後ろから改行コードを任意の数だけ検索する。
効率よく後ろ方向に検索する方法はもっと効率のよい方法がありそうだ。

今の知識ではlseekで一つ一つ戻っていく方法を思いついて、それで実装した。それなりに上手く動いている。
ただパフォーマンスとしては改善の余地がありそうである。

FILE*構造体を介しているから、アプリケーション側である程度バッファが利用される。
これはlseekで後ろ方向に移動するときも有効なのか?調べる必要がある。

また探索を行う際には一度getcでストリームから取得したものはungetcで戻しておく必要がある。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define TAIL_LINE 10
#define BUF_SIZE 4096

int main(int argc, char * argv[])
{
	FILE * fp;
	int c;
	int from_bottom = 0;
	char buff[BUF_SIZE];

	if (argc < 2) {
		printf("usage. mytail [file_name]\n");
		exit(-1);
	}

	if ((fp = fopen(argv[1], "r")) == NULL) {
		perror("failed to open file.");
		exit(-1);
	}

	if ((fseek(fp, 0, SEEK_END) == -1) || (fseek(fp, -1, SEEK_CUR) == -1)) {
		printf("failed to seek.");
		exit(-1);
	}


	while(1) {
		if ((c = fgetc(fp)) == '\n') {
			from_bottom++;
		}
		if (c == EOF) {
			break;
		}

		if (TAIL_LINE <= from_bottom) {
			break;
		}

		if (ungetc(c, fp) == EOF){
			printf("failed to ungetc.");
			exit(-1);
		}
		fseek(fp, -1, SEEK_CUR);
	}

	while (fgets(buff, BUF_SIZE, fp) != NULL) {
		printf("%s", buff);
	}
	printf("\n");
}

実行すると下記のようになる

$ ./a.out mytail.c
			exit(-1);
		}
		fseek(fp, -1, SEEK_CUR);
	}

	while (fgets(buff, BUF_SIZE, fp) != NULL) {
		printf("%s", buff);
	}
	printf("\n");
}

tailの実装と比較してみよう。
freebsdのリポジトリから実装を参照してみる。
下記に一部llinesという関数を抜粋する。

int
lines(FILE *fp, const char *fn, off_t off)
{
	struct {
		int blen;
		u_int len;
		char *l;
	} *llines;
	int ch, rc;
	char *p, *sp;
	int blen, cnt, recno, wrap;

	if ((llines = calloc(off, sizeof(*llines))) == NULL)
		err(1, "calloc");
	p = sp = NULL;
	blen = cnt = recno = wrap = 0;
	rc = 0;

	while ((ch = getc(fp)) != EOF) {
		if (++cnt > blen) {
			if ((sp = realloc(sp, blen += 1024)) == NULL)
				err(1, "realloc");
			p = sp + cnt - 1;
		}
		*p++ = ch;
		if (ch == '\n') {
			if ((int)llines[recno].blen < cnt) {
				llines[recno].blen = cnt + 256;
				if ((llines[recno].l = realloc(llines[recno].l,
				    llines[recno].blen)) == NULL)
					err(1, "realloc");
			}
			bcopy(sp, llines[recno].l, llines[recno].len = cnt);
			cnt = 0;
			p = sp;
			if (++recno == off) {
				wrap = 1;
				recno = 0;
			}
		}
	}
	if (ferror(fp)) {
		ierr(fn);
		rc = 1;
		goto done;
	}
	if (cnt) {
		llines[recno].l = sp;
		sp = NULL;
		llines[recno].len = cnt;
		if (++recno == off) {
			wrap = 1;
			recno = 0;
		}
	}

	if (rflag) {
		for (cnt = recno - 1; cnt >= 0; --cnt)
			WR(llines[cnt].l, llines[cnt].len);
		if (wrap)
			for (cnt = off - 1; cnt >= recno; --cnt)
				WR(llines[cnt].l, llines[cnt].len);
	} else {
		if (wrap)
			for (cnt = recno; cnt < off; ++cnt)
				WR(llines[cnt].l, llines[cnt].len);
		for (cnt = 0; cnt < recno; ++cnt)
			WR(llines[cnt].l, llines[cnt].len);
	}
done:
	for (cnt = 0; cnt < off; cnt++)
		free(llines[cnt].l);
	free(sp);
	free(llines);
	return (rc);
}

これを見ると引数として指定したファイルを最初から最後まで走査しているようである。
予め各行を表す構造体を定義し、tailで表示する行数だけメモリを確保する。
この構造体は循環リストのようになっており、例えば10行tailする際には11行目を読み込んだ時にははじめの1行目のデータを上書きしてメモリに保存する。
つまり指定行しか保持しないで、ファイルの末尾に達した時には自動的に最後のn行がメモリに残るような作りになっている。

あとは最後にデータを表示するだけである。
後ろからデータをseekするようなことはやっていなかった。


unix programming 2章_1

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

2章の課題を2つほど進めます。

(課題2.10)
whoコマンドを拡張してwho am iおよびwhoamiというシンタックスもサポートするように実装する。

who am iとwhoamiは何が違うのか。

whoamiのmanを引いてみよう

man whoami

説明
whoami は現在の実効ユーザー id に対応するユーザー名を表示する。 whoami は、コマンド ‘id -un’ と等価である。

実効ユーザidに対応するユーザ名を表示する点がことなりそうだ。
whoは実ユーザidに対応するユーザ名を表示する。

次にwhoを引いてみる

man who

名前

オプション以外の引数を 2 つ与えた場合、 who は who を起動したユーザ (標準入力から決定される) についての情報だけを表示する。このとき名前の前にホスト名が置かれる。普通は 2 つの引数には、‘am i’
が使われる。

要するにwho am iだろうがwho you areだろうがなんだって良いのである。
引数が2つ入力された場合は自分の情報だけ表示するようになる。

* who am iを実装する

起動したユーザの情報を比較するにはどうすればよいか。
実装としては、utmp->ut_userを現在のプロセスの実ユーザidを比較して一緒であれば表示するようにする。

* whoamiを実装する

対してwhoamiではut_pidからプロセスの情報を引っ張ってきて、そのユーザidと現在のプロセスの実効ユーザidを比較する。
調べたところuid -> user名への変換は難しい模様。代わりにgetloginやcuseridというライブラリ関数があるのでそれを用いる。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <utmp.h>
#include <fcntl.h>
#include <time.h>
#include <string.h>

void showtime(long);
void show_info(struct utmp *, int ami);

int main(int argc, char * argv[])
{
  struct utmp utbuf;
  int utmpfd;
  int ami = 0;

  if (3 <= argc) {     ami = 1;   }   if ((utmpfd = open(UTMP_FILE, O_RDONLY)) == -1) {     perror(UTMP_FILE);     exit(1);   }   while(read(utmpfd, &utbuf, sizeof(utbuf)) == sizeof(utbuf))     show_info(&utbuf, ami);   close(utmpfd);   return 0; } void show_info(struct utmp *utbufp, int ami) {   if (utbufp->ut_type != USER_PROCESS)
    return;

  if ((ami == 1) && (strcmp(utbufp->ut_name, getlogin()) != 0)) {
    return;
  }
  printf("%-8.8s", utbufp->ut_name);
  printf(" ");
  printf("%-8.8s", utbufp->ut_line);
  printf(" ");
  showtime( utbufp->ut_time );
#ifdef SHOWHOST
  if (utbufp->ut_host[0] != '\0')
    printf(" (%s)", utbufp->ut_host);
#endif
  printf("\n");
}

void showtime(long timeval)
{
  char *cp;
  cp = ctime(&timeval);
  printf("%12.12s", cp+4);
}

(課題2.11)
自作したcp01.cは入力、出力を同じファイルに設定した時にどのように振る舞うか?
また本来はどのようにあるべきか、検討実装せよ。

cp01.cはファイル名の検証については全く実装されていない。
出力ファイルに関してはcreatシステムコールを利用することでファイルを作成している。
入力ファイルと出力ファイルの引数が同じに設定されていた際にはcreatがコールされた段階でファイルデータが削除される。
そのためファイルコンテンツが削除されるような挙動となる。

またこれに関しては検証を実装すべきだと考える。
入力ファイル名と出力ファイル名が同じファイルであればエラーとみなして動作を中止するようにする

そういうわけで下記のように仕上がった

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

#define BUFFERSIZE 4096
#define COPYMODE 0644

int main(int ac, char *av[])
{
  int in_fd, out_fd, len;
  char buf[BUFFERSIZE];

  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 || stat(av[2], out_st) == -1) {
    printf("error. failed to get stat info.");
    exit(-1);
  }

  if (in_st->st_ino == out_st->st_ino) {
    printf("error. input file and output file must be different.");
    exit(-1);
  }

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

  if ((out_fd = creat(av[2], 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);
    }
  }
}

unix programming

webエンジニアが持つスキルセットとして、どこまで保持してれば十二分な対応ができるか。
それについて私の中ではunixに関する知識を持つことが必須、だと思います。

LLでアプリケーション書いてシステムが構築できればアプリケーションエンジニアとしては十分かもしれませんが
やはり言語の性能の限界や、トラブルシューティングなど考えたらunixは基礎として身につけておくべきでしょう。
できればかなりソースに近いレベルで。

でも哀しいかな。ここ5年くらいの動向を見ていると、しばらくはunixなんぞしらなくてもそこそこ食っていけるような業界となってきているようです。
むしろどちらかと言うと低レイヤなところをできるよりも、アプリケーションを品質高く実装できるエンジニアがかなり需要があるように感じます。
もちろんそういった人材がいなければいないのですが

というわけで現場としてもそれらが必要となる現場は(webにおいては)少ないです。
よってそういったスキルセットを持つエンジニア母数も減少すると。

でもアプリケーションを支えているのは当然低レイヤな技術なわけですから、そういったところを補っていきたい。
ビジネスレイヤには見えてこないだけで世界的には多くのコミッタがいることでしょう。

と、私の立場的にはそういった技術を身に着けたいけれども現場がない。ということで独学で勉強するしかないなと思い立って勉強中です。

いったん下記の書籍で勉強中です。

まずはこちらで理論を補います。
unixっていきなり全部立ち向かうのは無謀です。大きすぎる壁なので。
まず取っ掛かりとして挑むにはちょうどよいサイズです。

続いてこちらで実装を補います。
理論だけではもったいないですから、実践して自分のものにします。

現在一読したところですが、かなりよいです。
なお後者の書籍に関して課題などが挙げられているので、このブログでもピックアップしながら解説していきたいと思います。