2014-05-24 :-)
_ 午前
1030 起床 && 部屋掃除
_ 生田緑地ばら苑
行ってきた
開園から 2 週間目だし、ブログを見たら「見頃を迎えています」とあったので( 小川宏氏のばら: 生田緑地ばら苑 公式ブログ ) 駐車場が超絶混雑してそうなのでリスクを回避するために自転車で行ってきた。片道 30 分くらい。
入場口についたら入場待ちの車が 20 台くらい待機していた。自転車で来たのは正解だった。(または電車でもどうぞ)
芝生に座ってヨタヨタする。ばら苑の入り口付近の売店で買ったものを食べるなど。
_ [NetBSD][/bin/date][コードリーディング]NetBSD /bin/date を読む
ソース src/bin/date/date.c
マニュアル date - NetBSD Manual Pages
日付を表示するときの流れ
main
日付を設定するときの流れ
main setthetime netsettime (?) settimeofday
main から読んでいく。
お馴染みの処理。
setprogname(argv[0]); (void)setlocale(LC_ALL, "");
ここでいろいろ分岐している。
// -r されてなかったらまず time で tval を初期化。 if (!rflag && time(&tval) == -1) err(EXIT_FAILURE, "time"); // date "+%Y/%m/%d%n %H:%M:%S" などと使われた場合 // format に argv ( +%Y/%m/%d%n %H:%M:%S ) が入る /* allow the operands in any order */ if (*argv && **argv == '+') { format = *argv; ++argv; } else // デフォルトがこのフォーマット format = "+%a %b %e %H:%M:%S %Z %Y"; // date 20140524 などと使われた場合は↑は else を通るので // ここで argv は 20140524 が入っている // それを使って setthetime を呼ぶ if (*argv) { setthetime(*argv); ++argv; } // これはなんだ.... if (*argv && **argv == '+') format = *argv; // 時間の文字列を格納するための配列。とりあえず 1024 としておく。 // あとで strftime したときのために malloc で作っておく。 if ((buf = malloc(bufsiz = 1024)) == NULL) goto bad; // localtime してる if ((tm = localtime(&tval)) == NULL) err(EXIT_FAILURE, "localtime %lld failed", (long long)tval); // strftime する // 0 (たぶんエラー) が返ってきたらとりあえず配列サイズを 2 倍にして再確保してリトライ while (strftime(buf, bufsiz, format, tm) == 0) if ((buf = realloc(buf, bufsiz <<= 1)) == NULL) goto bad; // 最後に印字して終了 (void)printf("%s\n", buf + 1); free(buf); return 0;
strftime は結局 非 0 なら正常終了、0 ならエラーと言ってるようだ。
strftime(3) - NetBSD Manual Pages
No more than maxsize characters will be placed into the array. If the total number of resulting characters, including the terminating null character, is not more than maxsize, strftime() returns the number of characters in the array, not counting the terminating null. Otherwise, zero is returned and the contents of the array are undefined.
ところで
Upon successful completion, time() returns the value of time. Otherwise a value of ((time_t) -1) is returned and the global variable errno is set to indicate the error.
おかしいときは -1 を返すよ。errno に値を設定するよ
ERRORS No errors are defined.
でもエラーが無い。
さて
日付を設定するときの処理 setthetime を読む。
for (t = p, dot = NULL; *t; ++t) { if (isdigit((unsigned char)*t)) continue; if (*t == '.' && dot == NULL) { dot = t; continue; } badformat(); }
if (dot != NULL) { /* .ss */ len = strlen(dot); if (len != 3) badformat(); ++dot; lt->tm_sec = ATOI2(dot); if (lt->tm_sec > 61) badvalue("seconds"); } else { len = 0; lt->tm_sec = 0; }
ここでは
[[[[[[CC]yy]mm]dd]HH]MM[.SS]]
というフォーマットの最後の .SS が含まれているかどうかをチェックしている。↑の dot は .SS の . を意味する。
ここに出てきた ATOI2 は setthetime のすぐ上で定義されている。
#define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0'))
ようするに文字列ポインタを 2 つ進めて、2 つ通り過ぎた 2 文字を 10 進法数値として扱う。
たとえば 20140524 という文字列の場合を考えてみる。
最初に呼び出すとき
ATOI2("20140524") ~~
"20" が取り出されて 20(10進法) となる。
次に呼び出すとき
ATOI2("20140524") ~~
"14" が取り出されて 14(10進法) となる。
以下同様。
switch ではこの仕組を利用して渡されたフォーマットを解析している。
yearset = 0; switch (strlen(p) - len) { case 12: /* cc */ lt->tm_year = ATOI2(p) * 100 - TM_YEAR_BASE; if (lt->tm_year < 0) badtime(); yearset = 1; /* FALLTHROUGH */ case 10: /* yy */ if (yearset) { lt->tm_year += ATOI2(p); } else { yearset = ATOI2(p); if (yearset < 69) lt->tm_year = yearset + 2000 - TM_YEAR_BASE; else lt->tm_year = yearset + 1900 - TM_YEAR_BASE; } /* FALLTHROUGH */ case 8: /* mm */ lt->tm_mon = ATOI2(p); if (lt->tm_mon > 12 || lt->tm_mon == 0) badvalue("month"); --lt->tm_mon; /* time struct is 0 - 11 */ /* FALLTHROUGH */ case 6: /* dd */ lt->tm_mday = ATOI2(p); switch (lt->tm_mon) { case 0: case 2: case 4: case 6: case 7: case 9: case 11: if (lt->tm_mday > 31 || lt->tm_mday == 0) badvalue("day of month"); break; case 3: case 5: case 8: case 10: if (lt->tm_mday > 30 || lt->tm_mday == 0) badvalue("day of month"); break; case 1: if (lt->tm_mday > 29 || lt->tm_mday == 0 || (lt->tm_mday == 29 && !isleap(lt->tm_year + TM_YEAR_BASE))) badvalue("day of month"); break; default: badvalue("month"); break; } /* FALLTHROUGH */ case 4: /* hh */ lt->tm_hour = ATOI2(p); if (lt->tm_hour > 23) badvalue("hour"); /* FALLTHROUGH */ case 2: /* mm */ lt->tm_min = ATOI2(p); if (lt->tm_min > 59) badvalue("minute"); break; case 0: /* was just .sss */ if (len != 0) break; /* FALLTHROUGH */ default: badformat(); }
ここに出てくる TM_YEAR_BASE は src/lib/libc/time/tzfile.h で定義されてるものかな。以下の値となっている。2000 年問題ェ...
#define TM_YEAR_BASE 1900
FALLTHROUGH とあるように渡されたフォーマットを五月雨で処理していく。プログラミング言語 C には伝統として「switch case で break し忘れる」というバグがよく話題になるので、コメントとして書いておかないと break を意図して省いているのかどうか分からないのである。
実際に日付を設定している処理がここ。だと思う。
// or で繋がっているので nflag がどのような値であってもこの if は通る。 // つまり必ず netsettime が呼ばれる。はず /* set the time */ if (nflag || netsettime(new_time)) { logwtmp("|", "date", ""); if (aflag) { tv.tv_sec = new_time - tval; tv.tv_usec = 0; if (adjtime(&tv, NULL)) err(EXIT_FAILURE, "adjtime"); } else { tval = new_time; tv.tv_sec = tval; tv.tv_usec = 0; // ここで日付を設定 if (settimeofday(&tv, NULL)) err(EXIT_FAILURE, "settimeofday"); } logwtmp("{", "date", ""); }
nflag がなんのためにあるのか分からんのだが、netsettime を覗いてみる。
ソースは src/bin/date/netdate.c にある。
冒頭のコメント
/* * Set the date in the machines controlled by timedaemons by communicating the * new date to the local timedaemon. If the timedaemon is in the master state, * it performs the correction on all slaves. If it is in the slave state, it * notifies the master that a correction is needed. * Returns 0 on success. Returns > 0 on failure. */
結局 timed デーモンが起動していない場合はエラーになるけど、返り値は 0 超の値を返すらしい。
そして netsettime から返って ↑ の処理に戻る。ようだ。
netsettime の呼び出しが無駄なような...