ファイルシステムが壊れたらDHCPクライアントも暴走した話

はじめに

職場で一部の人から「ネットワークにつながらない」という申告があり、調べたら自分が原因でした、というお話です。

朝のことです

一部の職員から「ネットワークにつながりません」という申告がありました。朝で担当の職員がいなかったこともあり、自分が代わって対応しました。

確認できたのは以下のような事象でした。

  • 会社の無線LANのうち、あるSSIDではつながらない
  • 別のSSIDなら大丈夫
  • ipconfigしてもらうと、つながらないというSSIDはIPを取得できていない
  • Wifiはつかめているっぽい

PCのIPアドレスはDHCPなので、DHCPサーバになにか問題が起きたかなぁというのが私の見立て。

しかし、この時点でいくつか疑問が。

  • DHCPサーバの不具合なら、別のSSID(別セグメント)では問題起きないのは不思議
  • DHCPリレーの不具合でも同様
  • 問題のあるSSIDも、大丈夫な人と大丈夫じゃない人がいる。DHCPサーバの問題なら大丈夫な人がいるのも不思議。
  • 大丈夫じゃない人も複数いる。PC再起動してもダメ。PC起因が複数台で同時発生というのもありえなくないけど、確率低い

そんなこんな考えると、IPアドレス枯渇が一番ありえそうな気がする一方で、つながらない人が全員、後から接続しようとしたわけじゃなく、大丈夫な人よりも先につなごうとしていたので、はっきりと断言できるわけでもなし。

調査結果を聞く

ネットワークの担当者が来たので、そちらに任せていると「DHCPのアドレス枯渇が原因です。特定のクライアントから10秒毎に新しいIPを払い出すよう依頼が来ている」という連絡が。しかも、私が実験用に使っているPCから。

そのPCはMinIO の実験用に職員用セグメントに立てていたDebianマシン。そういえば、昨日の午後にファイルの送受信試したな、と。
マシンを見てみると、IOエラーが大量に出ているようですが、sshも通るしネットワーク的にはおかしくはない感じ。IPアドレス確認しようとコマンド打ったらびっくり!確かに有線のinterfaceにIPアドレスが大量に割り当ててられているではないですかー。とりあえず迷惑かけ続けるのもアレなので、シャットダウンしました。

問題のマシンを調査してみる

IOエラーがDHCPの暴走になるのか?というのが疑問で、所定の用事を済ませたた後に、問題のマシンを調査してみました。

問題起こすわけにもいかないので、いったん、有線ケーブルは抜いて起動して、syslogとかを見てみようとすると、ガーン!起動しないじゃないの。手動でfsckしてね、と言われるので、言われるがまま作業してなんとか起動。

で、syslog見てみると、どうも前日の18時前くらいからログがない様子。その少し前のログを見るとIOエラーでファイルシステムをReadOnlyに、と書かれているので、syslogも書き込めなくなったのかも、という結論に。

しかし、それでなぜDHCPが?syslogを見ると前日の12時くらいにリース更新の記録があり、残りが12時間弱くらいになっていたのです。そういえば、前日の23時台から問題の挙動が起き始めている、とネットワーク担当者が言っていたな…

dhclient-enp2s0.leasesを見ると、正常に取得していたIPだけが書かれていて、それがsyslogの内容とも概ね合致している感じ。その後IPを大量に取得しているにもかかわらずそれがleasesに反映されていない。

ということは、こんな感じってことですかね。

DHCPのリース更新をした
→ちゃんと更新できているにもかかわらず、DBを更新できないためにリース期限を更新できなかったと思いこむ
→リース切れだと思い、IP取得に行く
→取得できているのにDB更新できないから取得できなかったと思い込み、また取得に行く(以下これを繰り返す)

まとめ

問題のマシンは、以前もファイルシステムエラーを起こしていたので、ディスクが壊れているのか、MinIOとext4の組み合わせが良くないのか、ちゃんと調べてから使うべきでした。

きちんと調査せずに、安易にOS再インストールして、かつサーバのように起動させっぱなしにしていたのが良くなかったです。

psコマンドとかで見た限り怪しいプロセスもなかったし、おそらくセキュリティインシデント(マルウェア感染とか)ではなかったと思われるので、それは良かったです。

参考にしたサイトなど

あと、dhclientのソースコードもちょっとだけ目を通しました。

Pull Requestしてみた

はじめに

4連休を使って、ntc-templatesというOSSへPull Requestしてみたメモです。

きっかけ

職場でFWポリシーを常に最新に保ちたいというニーズがあります。でも、手作業でエクセルのポリシー表をぽちぽち修正するのってツライし、ミスる原因にもなります。そのため、FWにACLを投入した内容が自動で反映できるようになるといいな、と思ったのがきっかけです。

いろいろ試行錯誤してみました。最初に試したのはこれでした。

自由度が高くて使いやすいのですが、投入したConfigの中身を自分で解釈していく必要があってやめました。

なにか良いツールないかなと思って見つけたのがTextFSMでした。このあたりの記事などを参考にしました。

使ってみたところ

TextFSMだけだとテンプレートを自分で書かないといけないようですが、そこはやっぱり先人がいて、ntc-templatesというOSSがありました。

そこで使ってみると、これは便利だ!ということで早速、職場のASAから出力したACLでもやってみることにしました。

ところが、”Did not match any rules” エラーが出てしまいます。微妙にテンプレートの構文とコマンドの出力が合っていないようでした。

じゃあ自分で修正してみよう

自分で修正したら良いね、ということでCiscoのコマンドリファレンスなどとにらめっこして、修正しました。修正したのは主に3点。

  • ENTRY_PORTが”ftp-data”のようなハイフン付きにも対応できるようにした
  • inactive (hitcnt=0) (inactive) のような出力に対応できるようにした
  • log disableやlog default に対応できるようにした

Pull Requestの内容がこれです。
https://github.com/networktocode/ntc-templates/pull/783

英語でのやりとりになるので、そこにも注意しました。英語苦手なので。

おわりに

マージしてもらえたら嬉しいですし、されなくても指摘とか貰えればそれはそれでありがたいです。結構手間暇かかって大変でしたけど、おもしろかったです。

df コマンドを調べてみた

はじめに

職場で、mount で表示すると出てくるのに、dfだと表示されないものがあるというので、ちょっと勉強してみたのでそのメモです。

手がかり

職場での出来事なので、あまり詳細について書くことができません。

NFSサーバの3つのディレクトリをそれぞれ、別のマウントポイントでマウントしているそうです。そのうちの1つだけが表示されない状況。
そうすると、NFSサーバ側の問題は考えにくいと思います。

複数のマシンからマウントしているみたいですが、特定の1つだけdfで表示できないっぽい感じ。(このあたり詳細を聞いていないのでなんとも。)

mountコマンドで表示すると出てくるのに、dfだと出てこない。
あと、マウントポイントの先のデータも参照等できている。(なのでマウント自体はできていると思われる。)

仮説: dfを疑おう

dfとmountでおそらく情報源となっているものが違っていて、それが何らかの原因で差異が生じているのでは、と仮説を立ててみた。

mount コマンドの情報源

マニュアル見ると書いてあった。

mount および umount プログラムによって現在マウントされているファイルシステムの一覧は /etc/mtab ファイル中に記述されている。 mount が
引き数なしで実行された場合には、 このリストが表示される。

man 8 mount より

自分の手元のPC(Linux Mint 19.3)だと /etc/mtab は/proc/self/mounts へのシンボリックリンクでした。職場のはRedHatEnterpriseLinuxの7系だった気がするので、また違うかもしれない。

df コマンドの情報源

df のマニュアル見たけど、特に書いていない。うーん、しょうがない。ソースコードを読もう。でもバージョンわかんない。まぁいいや。とりあえず割と新しいやつを読んでみておこう、というわけで GNU coreutilsの8.32を読むことに。

特に手がかりとかないので、まずはmainから。

int
main (int argc, char **argv)
{
  struct stat *stats IF_LINT ( = 0);
  initialize_main (&argc, &argv);
  set_program_name (argv[0]);
  setlocale (LC_ALL, "");
  bindtextdomain (PACKAGE, LOCALEDIR);
  textdomain (PACKAGE);
  atexit (close_stdout);
  fs_select_list = NULL;
  fs_exclude_list = NULL;
  show_all_fs = false;
  show_listed_fs = false;
  human_output_opts = -1;
  print_type = false;
  file_systems_processed = false;
  exit_status = EXIT_SUCCESS;
  print_grand_total = false;
  grand_fsu.fsu_blocksize = 1;
  /* If true, use the POSIX output format. */
  bool posix_format = false;
  const char *msg_mut_excl = _("options %s and %s are mutually exclusive");

  while (true)
    {
      int oi = -1;
      int c = getopt_long (argc, argv, "aB:iF:hHklmPTt:vx:", long_options, &oi);

最初の方のやつは、GNUのコマンドでよく出てくるやつですね。(前にls を読んだときにもあった。)その後の変数は名前から推測すると、コマンドのオプション値を保存するためのものかなぁと思います。while文はコマンドオプションの処理だと思います。

というわけでこの辺をずーと飛ばしてみると、1780行目にマウントしているもののリストを取り出しているっぽいところを発見。

mount_list =
  read_file_system_list ((fs_select_list != NULL
                          || fs_exclude_list != NULL
                          || print_type
                          || field_data[FSTYPE_FIELD].used
                          || show_local_fs));

read_file_system_list は lib/mountlist.c にありました。

struct mount_entry *
read_file_system_list (bool need_fs_type)
{
  struct mount_entry *mount_list;
  (省略)
#ifdef MOUNTED_GETMNTENT1 /* glibc, HP-UX, IRIX, Cygwin, Android,
                             also (obsolete) 4.3BSD, SunOS */
  {
    FILE *fp;
#if defined linux || defined ANDROID
   /* Try parsing mountinfo first, as that make device IDs available.   
      Note we could use libmount routines to simplify this parsing a 
      little (and that code is in previous versions of this function), 
      however libmount depends on libselinux which pulls in many 
      dependencies. */ 
    char const *mountinfo = "/proc/self/mountinfo";
    fp = fopen (mountinfo, "r");
    if (fp != NULL) 
      {
        (省略) 
      } else /* fallback to /proc/self/mounts (/etc/mtab). */
#endif /* linux || ANDROID */
      {
        struct mntent *mnt;
        char const *table = MOUNTED;
        fp = setmntent (table, "r");
         (省略)

どうやら、最初に/proc/self/mountinfo を読もうとしていて、それができないときに/etc/mtab にフォールダウンするみたいですね。

ちなみに、MOUNTEDは、マクロで_PATH_MOUNTEDと定義されていました。_PATH_MOUNTEDは、glibcのsysdeps/unix/sysv/linux/paths.hによると/etc/mtab なので、上記の記載とも一致しています。

結論: まだわからないです

出勤したら、また調べてみようと思います。
とりあえず、/proc/self/mountinfo と/etc/mtab の差異を調べて見る感じですね。でも、自分がログインして調べるわけじゃないのが、ちょっと面倒です。

サーバの運用保守を委託してやっているので、あんまり自分がログインして、とかすると責任分解が、とか言われてしまうのです。とほほ。

2020/07/06 追記

原因は、このChangeLogにある内容でした。

df: prioritize mounts nearer the device root
In the presence of bind mounts of a device, the 4th “mount root” field
from /proc/self/mountinfo is now considered, so as to prefer mount
points closer to the root of the device. Note on older systems with
an /etc/mtab file, the source device was listed as the originating
directory, and so this was not an issue.
Details at http://pad.lv/1432871

coreutils/ChangeLog

ソースコードの続きの箇所で、filter_mount_list() という関数があって、そこで、bind mount の場合に重複して表示させないように処理しつつ、NFSの場合は意図したものとして、ちゃんと表示するように書いてありました。

bool target_nearer_root = strlen (seen_dev->me->me_mountdir) 
                          > strlen (me->me_mountdir);
/* With bind mounts, prefer items nearer the root of the source */bool source_below_root = seen_dev->me->me_mntroot != NULL
                    && me->me_mntroot != NULL
                    && (strlen (seen_dev->me->me_mntroot)
                        < strlen (me->me_mntroot));
if (! print_grand_total
    && me->me_remote && seen_dev->me->me_remote
    && ! STREQ (seen_dev->me->me_devname, me->me_devname))
  {
    /* Don't discard remote entries with different locations,
       as these are more likely to be explicitly mounted.
       However avoid this when producing a total to give
       a more accurate value in that case.  */
  }

無駄といえば無駄でしたが、いろいろ勉強になったのでラッキーでした。

読書通帳⑨

読んだ本

・たのしいムーミン一家(講談社文庫)
 
https://bookclub.kodansha.co.jp/product?item=0000139838

内容と感想

以前に読んだムーミン谷の彗星の続きです。妻がだいぶ以前に購入していたものがあるというので借りて読みました。今は娘の本になっているみたいです。

ムーミン谷の彗星がどことなく暗い感じがある作品でしたが、今回は全体に明るくタイトル通りに「たのしい」ストーリーでした。

飛行おにの帽子(黒いシルクハット)を入手したことでいろんな不思議なことが起きて、ゆかいなキャラクターたちの不思議な世界観と合わさって、どんどん読み進めることができます。

最後に飛行おにがでてきて、すごい怖いキャラクタなのかと思っていたら、そんなことはなく、全体にハッピーな気分で読み終わることができました。ちなみにうちの娘は、ムーミンバレーパークの飛行おにのジップアドベンチャーが大好きです。ずっと「飛行おにってなんだろう」と思っていたので、それがわかってよかったです。
※私は高いところが苦手なので、ジップアドベンチャーはやりません。

本の中で、じゃこうねずみの入れ歯が怪物に変わってしまうシーンがあって、作者注として「なんに変わったか知りたかったら、お母さんに聞いてみて」と書いてあります。北欧にはなにかそういう言い伝えとかがあるのかな、と思って調べてみました。でも、これって家族でいろんな想像を膨らませてね、というメッセージだったみたいです。うーん、お父さんは想像力が欠けているなぁ。

まとめ

続きを読みたいので、続編を買って読もうかな。最近は電子書籍で読むことが多いです。

Amazon Connect でなんちゃって監視センター作ってみる

はじめに

システムを運用していると、どうしても夜間に電話での呼び出しが必要になる場面があります。そのためにオペレーションセンターを利用することもあると思いますが、電話かけるだけなら、なんとかできないかなと考えてみました。

まぁPagerDutyが良いと思いますが、AWS使っているんなら、Amazon Connectでも良いんじゃないかと思ってやってみた、というわけです。

処理フロー

なんらかの形でS3にファイルをアップロードする
→S3へのPUTを検知してLambdaを起動
→LambdaがAmazon Connectを呼び出して、電話をかける

S3にファイルをアップロードするスクリプト

from datetime import datetime
import boto3
AWS_S3_BUCKET_NAME = 'myalertcall'
PUT_OBJECT_KEY_NAME = datetime.now().strftime('%Y%m%d%H%M%S')

s3 = boto3.resource('s3')
bucket = s3.Bucket(AWS_S3_BUCKET_NAME)
obj = bucket.Object(PUT_OBJECT_KEY_NAME)
body = """ これはテストアラートです。
test03
"""
response = obj.put(
    Body = body.encode('utf-8'),
    ContentEncoding = 'utf-8',
    ContentType = 'text/plain'
)

Lambdaのスクリプト

from urllib.parse import unquote_plus
import boto3

def lambda_handler(event, context):
    alert_bucket_name = event['Records'][0]['s3']['bucket']['name']
    alert_object_key = unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
    service_name = '適当なサービス'
    connect = boto3.client('connect')
    common_message = 'もうほんとやばいっち、いいよろうもん。なんとかしてっちゃ!'
    message = '{}が大変ばい。{}'.format(service_name,common_message) connect.start_outbound_voice_contact(
        DestinationPhoneNumber='+81xxxxxxxxxx',
        ContactFlowId='yyyyyyyyyyy',
        InstanceId='zzzzzzzzzzzzzz',
        SourcePhoneNumber='+81wwwwwwwwww',
        Attributes={ 'message': message }
    )

問い合わせフロー

参考にした記事と同じフローを作りました。

まとめ

Amazon Connectが何なのか全くわかっていない状態からでも、1時間もせずに電話発信ができるようになりました。Amazonすごい。

あとは、発信先が電話を取らない場合は次の発信先にかけるとかのルールを作れるようになろうと思います。もうちょっとAmazon Connectを勉強しないと。

現状のスクリプトでもS3からオブジェクト名を取れるので、それで処理を分けたり、ファイルの中身をみて処理を変えたりとかできるとより便利そうだな〜とか考えています。

参考にしたサイト

・Amazon Connectで月4ドルで電話発信する仕組みを構築する
 https://dev.classmethod.jp/articles/amazon-connect-system-alert/

2020/05/17 追記

このサイトを参考にして、電話に出ないと次の人にコールするように変えてみた。
・[Amazon Connect] 任意時間で電話の呼び出しコールを中断する方法を考えてみた
 https://dev.classmethod.jp/articles/amazon-connect-stop-calling/

本当はS3にアップしたファイルの中身とかでメッセージを変えたり、発信先を変えたりすることになるのかなと思います。

import time
from urllib.parse import unquote_plus
import boto3


def lambda_handler(event, context):
    contact_flow_id = 'hogehoge' # 自分の環境の値に変える
    instance_id = 'fugafuga'
    # alert_bucket_name = event['Records'][0]['s3']['bucket']['name']
    # alert_object_key = unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
    connect = boto3.client('connect')
    common_message = 'とっても大変なことが起きました。もうほんとやばいです。なんとかしてください。'
    message = f'{common_message}'
    call_numbers=('phone01','phone02')  # 通知したい電話番号
    for number in call_numbers:
        res = connect.start_outbound_voice_contact(
                  DestinationPhoneNumber=number,
                  ContactFlowId=contact_flow_id,
                  InstanceId=instance_id,
                  SourcePhoneNumber='発信番号',
                  Attributes={ 
                      'message': message,
                      'is_recieved': 'calling',
                  }
        )
        contact_id = res['ContactId']
        time.sleep(10)     # 時間は適当に調整する
        attr = connect.get_contact_attributes(
                   InstanceId=instance_id,
                   InitialContactId=contact_id
                )
        if attr['Attributes']['is_recieved'] == 'recieved':
            break
        connect.stop_contact(
            InstanceId=instance_id,
            ContactId=contact_id
        )

読書通帳⑧

読んだ本

・新装版 ムーミン谷の彗星(講談社文庫)
 https://www.moomin.co.jp/books/2407

内容と感想

実は44才で初めてのムーミンです。もちろんムーミンというキャラクタは知っていましたが、ストーリーは全く知りませんでした。アニメも観たことがなかったのです。で、なんで急にどうしたの?と思うわけですよね。

今、小5になる娘がいまして、その子がムーミンを読んでハマっておりまして、話題についていけるようにと読みました。

ムーミン谷の彗星は、童話チックな内容を想像していたらちょっと違っていて、少し怖い感じもあるストーリーでした。ムーミン谷に彗星が迫ってきて、それを調べるために天文台に向かって…

スニフやスナフキンもすべてが初登場ということで、とても新鮮な感じです。スノークのおじょうさんとムーミンの出会いも素敵でした。

年甲斐もなくムーミン谷は最後、どうなってしまうのだろうとハラハラしました。とはいえ、決してハラハラドキドキの連続で手に汗を握る、というものではなく、ムーミンの不思議な世界と愉快なキャラクタが、なんだかふんわりした気持ちにさせてくれる本です。

1日半くらいで読み終わったら、娘が「楽しいムーミン一家」を貸してくれたので、今読んでいます。

ちなみに

少し前に飯能のムーミンバレーパークにも行ったのですが、今行ったらもっと楽しいかもという気になっています。残念ながら休園していますが…

・ムーミンバレーパーク・メッツァビレッジ
 https://metsa-hanno.com/

読書通帳⑦

読んだ本

・[試して理解] Linuxのしくみ
 https://gihyo.jp/book/2018/978-4-7741-9607-7

内容と感想

Software Designで連載されている記事をまとめたものなのかな、と思って買いました。ボリューム的に雑誌連載をまとめたものよりも多い気がするので、お得な気分です。

プロセススケジューリング、メモリ管理、仮想記憶、ファイルシステム、ストレージデバイスの順で書かれていて、それぞれでLinuxがどのように振る舞うのかを、実験してみせてくれているのがとてもうれしいです。

いったん、全部に目を通した後、実験プログラムを追試験?してみています。ちゃんと理解しながらやろうと思っているので、それなりに時間がかかっていますが、その価値はあると感じています。

Linuxのより詳細については、Linuxカーネル2.6解読室やLinuxシステムプログラミングなどの方が詳しい印象ですが、Linuxマシンの運用が主体の私にとっては、この本の方が良かったです。

Software Designの連載は続いているので、この本も続編が出るのかな〜と期待しています。楽しみです。

ちなみに

実験プログラムの中身で、なんでこうしているのだろうとわからないところがあったので、Twitterで著者に質問したら、とてもフランクに回答してくださいました。

お尋ねしてすぐに回答をいただけるのはとてもうれしかったです。

読書通帳⑥

読んだ本

・アセンブラで読み解くプログラムの仕組み
 https://gihyo.jp/dp/ebook/2011/978-4-7741-4890-8

内容と感想

数年前に読んだことがあったのですが、そのときは「ふーん」くらいの感想で特に印象に残ってはいませんでした。

低レイヤを知りたい人のためのCコンパイラ作成入門 を読んで実際に関数を実装したところくらいでもうちょっとアセンブラを理解したいなと思って、再度読みました。

以前よりもアセンブラに慣れてきたからか、思ったよりさらっと読めたのと、内容も理解できた気がします。

アドレッシングや関数の呼び出しや引数の取り扱いは低レイヤを知りたい人のためのCコンパイラ作成入門 でやっている内容とほぼ同じ(ただし、低レイヤ〜がAMD64で、アセンブラで〜がx86なので、引数の渡し方が違ったりはしています。)だったので、本当にスッと理解できた気がします。
気だけかもしれません。

共有ライブラリのところが、低レイヤ本の方ではやっていないテーマのようで、共有するための工夫というか苦労というかあるのだな、と分かって面白かったです。

最後の「継続は続く」はもともと、継続をよく理解できていないので、やっぱりあまり理解できませんでした。

今度はSchemeの勉強かな。

ちなみに

次は、[試して理解] Linuxのしくみ を読む予定です。

家にいる時間が増えてきたので、しっかりと力をつけたいと思います!

読書通帳⑤

読んだ本を記録しておくエントリーです。

読んだ本

プログラマの数学
https://www.hyuki.com/math/

ただし、古本屋で買っていた初版の方です。

内容と感想

目次などは著者のページで公開されているとおりです。

プログラミングの基本となる数学をとても平易な文章で書かれていて読みやすいです。最近、ちゃんと本を読んでいなかったので読みやすいものと思って本棚から取り出して読んだのですが、正解でした。
※私は大学では一応理系だったので、数学が全くできないわけではないですが、それほど得意だったわけではありません。

最後の計算不可能な問題のところが不完全な理解のまま読み進めてしまったので、別の機会に改めて読み直した方が良いかも、という気がしています。

論理や数学的帰納法、再帰などはプログラミングする上で理解しているのとそうでないのとでは、だいぶ違ってくる気がします。そのあたりの理解の導入にぴったりな本だと思います。

最後に、読書案内がついているので、そこから何を読むと良いかもわかり安心ですね。ちなみに何冊かはすでに読んだものもありました。

ちなみに

これで結城さんの著書を読んだのは6冊目です。どれもとても良い本ばかりです。今までに読んだ本は以下のとおりです。

  • 増補改訂版 Java言語で学ぶデザインパターン入門
  • 増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編
  • 新版 C言語プログラミングレッスン 文法編
  • 暗号技術入門 第3版
  • 数学ガール

疲労困憊ら〜

はじめに

昨日はボルダリングに行きました。がんばった分、疲労もそれなりに。子どもたち(小学生)はお出かけもあまりできないし、スイミングスクールは休講だし、少しでも運動させてやりたいなぁと思ってがんばります。

さて、コンパイラ自作にチャレンジ中です、という記事です。

なんで自作?

以下の記事を見つけたのがきっかけでした。

・低レイヤを知りたい人のためのコンパイラ作成入門
 https://www.sigbus.info/compilerbook

もともと、コンパイラとかOSとかに興味があったので、やってみようかと軽い気持ちで始めたのですが、これがなんとも面白いのです。

何が面白いかというと

この本は執筆中なこともあり、途中から自分で内容を理解して、実装を進めないといけないようになっています。
もしかしたら、それが著者の狙いなんじゃないかと思うくらい、ちょうどいい感じなのです。

それなりに難しく、いろいろと試行錯誤するのですが、それによって今までなんとなくだった理解が、しっかりとした理解になっていくような気がします。

気がしているだけかもしれません。

まだ途中です

今は、関数を定義してフィボナッチ数列や階乗の計算ができるようになりました。なんとか最後(つまりC言語そのものをコンパイルできる)までがんばりたいと思います。

https://github.com/HideoYukutake/qcc