UNIXドメインソケットを使ってDockerのバージョンを取得する

手っ取り早くUNIXドメインソケットを利用したプログラムを作るためのサンプルです。

ローカルPCにDockerがインストールされている前提で話を進めます。ターミナルから`docker -v`を実行するとバージョンが表示されることと思います。筆者の環境では次のようになりました。

```
$ docker -v
Docker version 18.09.2, build 6247962
```

このバージョン番号を取得するプログラムをC++で作成します。

dockerが動作しているマシンでは、`/var/run/docker.sock`というソケットが存在しており、これに接続すると、HTTPと同じ要領でdockerの機能にアクセスすることができます。詳細は公式のAPIドキュメントを参照していただくとして、最初の一歩となるプログラムを作成してみました。

`GET /v1.20/version HTTP/1.1`というリクエストを行うと、次のようなJSONデータが返ります。

``` .json
{
  "ApiVersion": "1.39",
  "Arch": "amd64",
  "BuildTime": "2019-02-12T22:47:29.000000000+00:00",
  "Components": [
    {
      "Details": {
        "ApiVersion": "1.39",
        "Arch": "amd64",
        "BuildTime": "2019-02-12T22:47:29.000000000+00:00",
        "Experimental": "false",
        "GitCommit": "6247962",
        "GoVersion": "go1.10.4",
        "KernelVersion": "4.15.0-47-generic",
        "MinAPIVersion": "1.12",
        "Os": "linux"
      },
      "Name": "Engine",
      "Version": "18.09.2"
    }
  ],
  "GitCommit": "6247962",
  "GoVersion": "go1.10.4",
  "KernelVersion": "4.15.0-47-generic",
  "MinAPIVersion": "1.12",
  "Os": "linux",
  "Platform": {
    "Name": ""
  },
  "Version": "18.09.2"
}
```

この中から`Version`の値を取り出します。

JSONパーサは[picojson](https://github.com/kazuho/picojson)を使用しました。

``` .cpp
#include "picojson.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

int write_string(int fd, char const *p)
{
	return ::write(fd, p, strlen(p));
}

void parse_response(char const *begin, char const *end, std::vector<std::string> *headers, std::vector<char> *contents)
{
	headers->clear();
	contents->clear();
	char const *head = begin;
	char const *next = begin;
	while (1) {
		int c = 0;
		if (next < end) {
			c = (unsigned char)*next;
		}
		if (c == '\r' || c == '\n' || c == 0) {
			bool empty = false;
			if (head == next) {
				empty = true;
			} else {
				std::string line(head, next);
				headers->push_back(line);
			}
			if (c == 0) break;
			next++;
			if (c == '\r' && next < end && *next == '\n') {
				next++;
			}
			if (empty) {
				contents->insert(contents->end(), next, end);
				return;
			}
			head = next;
		} else {
			next++;
		}
	}
}

int main()
{
	std::string result;

	struct sockaddr_un sa = {};
	int sock = socket(AF_UNIX, SOCK_STREAM, 0);
	sa.sun_family = AF_UNIX;
	memset(sa.sun_path, 0, sizeof(sa.sun_path));
	strcpy(sa.sun_path, "/var/run/docker.sock");

	if (::connect(sock, (struct sockaddr *)&sa, sizeof(struct sockaddr_un)) != -1) {
		write_string(sock, "GET /v1.20/version HTTP/1.1\r\n");
		write_string(sock, "Host: localhost\r\n");
		write_string(sock, "\r\n");

		char tmp[10000];
		int n = ::read(sock, tmp, sizeof(tmp));
		if (n > 0) {
			bool ok = false;
			std::vector<char> contents;
			{
				std::vector<std::string> headers;
				char const *begin = tmp;
				char const *end = tmp + n;
				parse_response(begin, end, &headers, &contents);
				if (headers.size() > 0) {
					int a, b, c;
					if (sscanf(headers[0].c_str(), "HTTP/%u.%u %u OK", &a, &b, &c) == 3 && c == 200) {
						ok = true;
					}
				}
			}
			std::string err;
			picojson::value v;
			picojson::parse(v, contents.begin(), contents.end(), &err);
			if (v.is<picojson::object>()) {
				picojson::object &obj = v.get<picojson::object>();
				result = obj["Version"].to_str();
			}
		}
	}

	::close(sock);

	puts(result.c_str());
	return 0;
}
```

実行結果は次のようになります。

```
$ g++ main.cpp 
$ ./a.out 
18.09.2
```