pkg using sqlite

Table of Contents

lcm

仲間内で雑談してたら、ある人が怪談めいた事を言った。最近台所から変な声 が聞こえると。よく耳を澄ませたら、電池切れそうと、1分間隔ぐらいで繰り 返してた。天井からね。何年も前に取り付けた火災報知器が発声元だったとか。 ちゃんと設計されてるね。まあ、あたりまえか。

そこから話が発展して、たて続けに、冷蔵庫やら車が壊れて(ついでに自分の 体も)往生したなん てのに話題が移った。

そこで、オイラーが発言。壊れるのはしょうがないけど、もっとバラけて壊れ てほしい。某社の製品なら、狙った通りにこわれるタイマーが内蔵できるんじゃ ねぇ。

そう言えば、素数セミなんてのがいたよな。同時発生で問題をおこさないよう に、素数年周期で大発生。これで、カブる事が少なくなる。えと、最小公倍 数だったな。

scheme@(guile-user)> (lcm 12 10)
$1 = 60
scheme@(guile-user)> (lcm 11 13 17)
$2 = 2431

最初の例は、還暦。干支は「十二支」と「十干」の組み合わせから成っていて、 60年で一巡する。次は、3種類の素数セミが、同時発生する周期。

どういう原理だったけな? Gauche-0.9.12/src/libnum.scm 思わず調べちゃったぞ。

(define-in-module scheme (lcm . args)
  (define (lcm2 u v)
    (let1 g ((with-module gauche.internal %gcd) u v)
      (if (zero? u) 0 (* (quotient u g) v))))
  (define (recn arg args)
    (if (null? args)
      arg
      (recn (lcm2 arg (car args)) (cdr args))))
  (let1 args (map (^[arg] (unless (integer? arg)
                            (error "integer required, but got" arg))
                    (abs arg))
                  args)
    (cond [(null? args) 1]
          [(null? (cdr args)) (car args)]
          [else (recn (car args) (cdr args))])))

普通は人は、こちらへ。 Pythonで最小公倍数、最大公約数を計算する

B木

前回からやってるsqliteの核心にBツリーってのがある。DISK等の記憶媒体に データを保存するのに適した構造を実現するのに都合がよい。

RDBMSで使われるB木を学ぼう

B-Tree by Java & Python

手元の資料を漁ったら、1990年代の雑誌、Cマガジンに連載されたものを纏め たものと、奥村先生のアルゴリズム辞典が出てきた。

#define M  2  /* 1ページのデータ数の上限の半分 */
typedef int keytype;                 /* 探索のキーの型 */

typedef struct page {                /* ページの定義 */
    int n;                           /* データ数 */
    keytype key[2 * M];              /* キー */
    struct page *branch[2 * M + 1];  /* 他ページへのポインタ */
} *pageptr;        /* {\tt pageptr} はページへのポインタの型 */

まずは、構造の定義。そして、少し実行してからgdbで検査。

挿入 In, 検索 Sn, 削除 Dn (n:整数) ? i33
reg'd
((.22.33.50.)54(.60.67.70.))

(gdb) p *root
$6 = {
  n = 1,
  key = {54, 0, 0, 0},
  branch = {0x4e665e80, 0x4e6263c0, 0x0, 0x0, 0x0}
(gdb) p *root.branch[0]
$7 = {
  n = 3,
  key = {22, 33, 50, 60},
  branch = {0x0, 0x0, 0x0, 0x0, 0x0}
}
(gdb) p *root.branch[1]
$8 = {
  n = 3,
  key = {60, 67, 70, 0},
  branch = {0x0, 0x0, 0x0, 0x0, 0x0}
}
insert   
 65 (.65.)
 91 (.65.91.)
 44 (.44.65.91.)
 56 (.44.56.65.91.)
 42 ((.42.44.)56(.65.91.))
 73 ((.42.44.)56(.65.73.91.))
 82 ((.42.44.)56(.65.73.82.91.))
 60 ((.42.44.)56(.60.65.)73(.82.91.))
 18 ((.18.42.44.)56(.60.65.)73(.82.91.))
 25 ((.18.25.42.44.)56(.60.65.)73(.82.91.))
 79 ((.18.25.42.44.)56(.60.65.)73(.79.82.91.))
 78 ((.18.25.42.44.)56(.60.65.)73(.78.79.82.91.))
  3 ((.3.18.)25(.42.44.)56(.60.65.)73(.78.79.82.91.))
 66 ((.3.18.)25(.42.44.)56(.60.65.66.)73(.78.79.82.91.))
 97 ((.3.18.)25(.42.44.)56(.60.65.66.)73(.78.79.)82(.91.97.))
 47 ((.3.18.)25(.42.44.47.)56(.60.65.66.)73(.78.79.)82(.91.97.))
delete
 65 ((.3.18.)25(.42.44.47.)56(.60.66.)73(.78.79.)82(.91.97.))
 91 ((.3.18.)25(.42.44.47.)56(.60.66.)73(.78.79.82.97.))
 44 ((.3.18.)25(.42.47.)56(.60.66.)73(.78.79.82.97.))
 56 ((.3.18.)25(.42.47.60.66.)73(.78.79.82.97.))
 42 ((.3.18.)25(.47.60.66.)73(.78.79.82.97.))
 73 ((.3.18.)25(.47.60.66.)78(.79.82.97.))
 82 ((.3.18.)25(.47.60.66.)78(.79.97.))
 60 ((.3.18.)25(.47.66.)78(.79.97.))
 18 ((.3.25.47.66.)78(.79.97.))
 25 ((.3.47.66.)78(.79.97.))
 79 ((.3.47.)66(.78.97.))
 78 (.3.47.66.97.)
  3 (.47.66.97.)
 66 (.47.97.)
 97 (.47.)
 47 .

これB木の成長と衰退の様子の現場中継。左の数字はランダムに発生させたkey 値だ。右 側の一見consっぽいのは、もとえ、括弧で囲まれたものは、一つのpage構造体 のkey値になる。

4個のキーまでは収納できるけど、それ以上になると別のページを作ってそち らに逃すようになってる。ページ内で整列。逃した場合も整列される様にされ てるのが味噌だ。

real sqlite3

btree.[ch]が、自動作成されるみたい。ヘッダーの方を(本体に比べて短かい という理由で)覗いてみる。

 ** Field usage summary:
 **
 **               Table BTrees                   Index Btrees
 **
 **   pKey        always NULL                    encoded key
 **   nKey        the ROWID                      length of pKey
 **   pData       data                           not used
 **   aMem        not used                       decomposed key value
 **   nMem        not used                       entries in aMem
 **   nData       length of pData                not used
 **   nZero       extra zeros after pData        not used
 **
 ** This object is used to pass information into sqlite3BtreeInsert().

struct BtreePayload {
  const void *pKey;       /* Key content for indexes.  NULL for tables */
  sqlite3_int64 nKey;     /* Size of pKey for indexes.  PRIMARY KEY for tabs */
  const void *pData;      /* Data for tables. */
  sqlite3_value *aMem;    /* First of nMem value in the unpacked pKey */
  u16 nMem;               /* Number of aMem[] value.  Might be zero */
  int nData;              /* Size of pData.  0 if none. */
  int nZero;              /* Extra zero data appended after pData,nData */
};

テーブルで使う場合とインディクスで使う場合と、大谷みたいな起用のされか たをするんだな。

本体のbtree.c

#define NN 1             /* Number of neighbors on either side of pPage */
#define NB 3             /* (NN*2+1): Total pages involved in the balance */

typedef struct CellArray CellArray;
struct CellArray {
  int nCell;              /* Number of cells in apCell[] */
  MemPage *pRef;          /* Reference page */
  u8 **apCell;            /* All cells begin balanced */
  u16 *szCell;            /* Local size of all cells in apCell[] */
  u8 *apEnd[NB*2];        /* MemPage.aDataEnd values */
  int ixNx[NB*2];         /* Index of at which we move to the next apEnd[] */
};

なんか、奥村先生っぽい構造が出てきたな。

pkg by sqlite3

sqliteの使い所が前回の説明にあった。個人でチマチマ使うのにはベスト・マッ チだそうだ。そんな訳なんで、FreeBSDでの利用を探ってみる。できれば、 sqliteを呼出ている現場を押さえてみたい。本当の実例ね。

/var/db

DBと言うか、データの集積場所が、こちらだ。関係がありそうな奴を見る。

[sakae@fb /var/db]$ ls -l pkg/
total 104580
-rw-r--r--  1 root  wheel       158 Mar 10 06:17 FreeBSD.meta
-rw-r--r--  1 root  wheel  50585600 Mar 12 05:22 local.sqlite
-rw-r--r--  1 root  wheel  48918528 Mar 10 06:17 repo-FreeBSD.sqlite
-rw-r--r--  1 root  wheel         0 Mar 12 05:22 repo-FreeBSD.sqlite-journal
-r--r--r--  1 root  wheel   7445436 Feb 23 05:57 vuln.xml
[sakae@fb /var/db]$ file pkg/local.sqlite
pkg/local.sqlite: SQLite 3.x database, user version 36, last written using SQLite version 3040000, file counter 5799, database pages 12350, 1st free page 8232, free pages 1047, cookie 0x58, schema 4, UTF-8, version-valid-for 5799

ファイル名でsqliteと名乗っているので、詐称してないか確認。確かに本物だ な。local.sqliteは多分、今このマシンにインストールされてるパッケージの 情報だろう。もう一つは、FreeBSDプロジェクトが提供してるパッケージの情 報だろう。ついでに、vuln.xmlも調べてみると、セキュリティー勧告用のデー タっぽかった。

これでpkgがsqlite3を使っている事が確定。次は、どう使っているかだな。

find source

それには、ソースを見るのが一番。/usr/src/usr.sbin/pkgに突入する。きっ と、 sqlite3_open とかが、記載されているはず。

が、予想は大外れ。man pkgして列挙される、 pkg-info とかの片鱗すら見 付からない。あるのは、

static const char confirmation_message[] =
"The package management tool is not yet installed on your system.\n"
"Do you want to fetch and install it now? [y/N]: ";

とか、bootstrapって言うような文字列。そうか、本物をインストールするた めのコマンドなんだな。pkg自体がpkgになってるとな。 usr/posts/ports-mgmt 経由で、 freebsd/pkg at github に、行きついた。

実体は、

[sakae@fb ~]$ ls -l /usr/local/sbin/pkg*
-rwxr-xr-x  1 root  wheel   2695004 Jan  3 10:11 /usr/local/sbin/pkg*
-rwxr-xr-x  1 root  wheel  21973592 Jan  3 10:11 /usr/local/sbin/pkg-static*
-rwxr-xr-x  1 root  wheel  21803548 Jul  1  2021 /usr/local/sbin/pkg-static.pkgsave*

pkgなんでOSのリリースとは非同期でアップデート出来るとな。

[sakae@fb ~]$ ldd /usr/local/sbin/pkg
/usr/local/sbin/pkg:
        libelf.so.2 => /lib/libelf.so.2 (0x206a8000)
        libjail.so.1 => /lib/libjail.so.1 (0x206c4000)
        libssl.so.111 => /usr/lib/libssl.so.111 (0x206cc000)
        libcrypto.so.111 => /lib/libcrypto.so.111 (0x2074d000)
        libarchive.so.7 => /usr/lib/libarchive.so.7 (0x209bd000)
        libbz2.so.4 => /usr/lib/libbz2.so.4 (0x20a89000)
        libz.so.6 => /lib/libz.so.6 (0x20a9d000)
        liblzma.so.5 => /usr/lib/liblzma.so.5 (0x20ab7000)
        libprivatezstd.so.5 => /usr/lib/libprivatezstd.so.5 (0x20ae1000)
        libm.so.5 => /lib/libm.so.5 (0x20b91000)
        libutil.so.9 => /lib/libutil.so.9 (0x20bcd000)
        libmd.so.6 => /lib/libmd.so.6 (0x20be4000)
        libthr.so.3 => /lib/libthr.so.3 (0x20c08000)
        libc.so.7 => /lib/libc.so.7 (0x20c32000)
        libbsdxml.so.4 => /lib/libbsdxml.so.4 (0x20e04000)

あれ? libsqliteを利用してないの? いや多分埋没してるんでしょう。完全 に埋没させると、スタチィク版になるんだな。こうなると、 一応、証拠を固めておくか。

[sakae@fb ~]$ strings /usr/local/sbin/pkg | grep sqlite3_open
sqlite3_open_v2
sqlite3_open

watch

後は、心おきなく、ソースを散策する。src/main.cはpkgコマンドのメイン。 installとかsearchとかは、サブコマンドになる。んで、例として、info.cを 題材にしてみる。

このファイル中には、 sqlite3_xxx なんてのは一切ない。レールの作りのよう にインピーダンス・マッチングしてるんだな。そのかわりに、

ret = pkgdb_access(PKGDB_MODE_READ, PKGDB_DB_LOCAL);

	ret = pkgdb_open(&db, PKGDB_DEFAULT);

       if ((it = pkgdb_query(db, pkgname, match)) == NULL) {

これを頼りに探してみると、

[sakae@fb ~/pkg/libpkg]$ grep pkgdb_open *.c
pkgdb.c:pkgdb_open(struct pkgdb **db_p, pkgdb_t type)
pkgdb.c:        return (pkgdb_open_all(db_p, type, NULL));
pkgdb.c:pkgdb_open_repos(struct pkgdb *db, const char *reponame)
pkgdb.c:pkgdb_open_all(struct pkgdb **db_p, pkgdb_t type, const char *reponame)
pkgdb.c:                        ret = pkgdb_open_repos(db, reponame);
pkgdb.c: * Used both in the shell and pkgdb_open
[sakae@fb ~/pkg/libpkg]$ wc pkgdb*
    3031    8887   74076 pkgdb.c
    1318    3532   31602 pkgdb_iterator.c
     523    2040   15609 pkgdb_query.c
    4872   14459  121287 total

pkgdb.c

prepare_sql(sqlite3 *s, const char *sql)
{
        int ret;
        sqlite3_stmt *stmt;

        ret = sqlite3_prepare_v2(s, sql, strlen(sql), &stmt,
            NULL);
        if (ret != SQLITE_OK) {
                ERROR_SQLITE(s, sql);
                return (NULL);
        }
        return (stmt);
}

一枚皮をかぶせるとORMな手法になるのかな。オブジェクト関係マッピング

pkgdb_init(sqlite3 *sdb)
{
        const char      sql[] = ""
        "PRAGMA journal_mode = TRUNCATE;"
        "PRAGMA synchronous = FULL;"
        "BEGIN;"
        "CREATE TABLE packages ("
                "id INTEGER PRIMARY KEY,"
                "origin TEXT NOT NULL,"
                "name TEXT NOT NULL,"
           :
        return (sql_exec(sdb, sql, DBVERSION));
}

そんな事より、正体見たり。後は、これらのファイルを丁寧に見てくダ ケーーーーです。

[USERS2] = {
        NULL,
        "INSERT INTO pkg_users(package_id, user_id) "
        "VALUES (?1, (SELECT id FROM users WHERE name = ?2))",
        "IT",
},

これ、SQLでの値を引っ張ってきて埋め込みの手法か。

pkg shell

恐しい事に、pkgコマンドからsqlite3を呼び出せるようになってる。十分に注 意して使えって但し書きがついてるけどね。

[sakae@fb ~]$ pkg shell
SQLite version 3.40.0 2022-11-16 12:10:08
Enter ".help" for usage hints.
sqlite> .tables
annotation           pkg_annotation       pkg_requires
categories           pkg_categories       pkg_script
config_files         pkg_conflicts        pkg_shlibs
deps                 pkg_directories      pkg_shlibs_provided
directories          pkg_groups           pkg_shlibs_required
files                pkg_licenses         pkg_users
groups               pkg_lock             provides
licenses             pkg_lock_pid         requires
lua_script           pkg_lua_script       script
mtree                pkg_option           shlibs
option               pkg_option_default   users
option_desc          pkg_option_desc
packages             pkg_provides

色々なテーブルがあるな。

sqlite> .mode table
sqlite> select * from users;
+----+------------+
| id |    name    |
+----+------------+
| 1  | _tss       |
| 2  | messagebus |
| 3  | avahi      |
| 4  | polkitd    |
| 5  | cups       |
| 6  | colord     |
| 7  | webcamd    |
| 8  | git_daemon |
| 9  | pulse      |
| 10 | mysql      |
+----+------------+

pkgの導入に伴なって新らしいユーザーが作成されましたとな。

sqlite> select * from script limit 1;
+-----------+----------------------------------------------------------+
| script_id |                          script                          |
+-----------+----------------------------------------------------------+
| 1         | if ! /usr/sbin/service ldconfig restart >/dev/null; then |
|           |                 if [ -z "${INSTALL_AS_USER}" ]; then     |
|           |                         exit 1                           |
|           |                 fi                                       |
|           |         fi                                               |
+-----------+----------------------------------------------------------+

スクリプトまでDBに入れてあるのはいいけど、雑誌や本のようにパラパラ出来ないのが欠点。 世はディジタルな時代で電子本が大流行だけど、オイラーは普通の本の方が好きだ。

other cmd using sqlite

便利なsqliteは、あちこちで利用されてるだろう。2つの側面から調べてみる。

on FreeBSD

まずは、OS関係者が使っていないか。ソースをガサ入れ。

[sakae@fb /usr/src]$ find . -name '*.c' | xargs grep -l sqlite3_
./contrib/wpa/hostapd/hlr_auc_gw.c
./contrib/wpa/src/radius/radius_server.c
./contrib/wpa/src/eap_server/eap_sim_db.c
./contrib/wpa/src/ap/eap_user_db.c
./contrib/wpa/src/ap/ieee802_1x.c
./contrib/wpa/src/ap/hostapd.c
./contrib/sqlite3/sqlite3.c
./contrib/sqlite3/tea/generic/tclsqlite3.c
./contrib/sqlite3/shell.c
./contrib/apr-util/dbd/apr_dbd_sqlite3.c
./contrib/apr-util/dbd/apr_dbd.c
./contrib/subversion/subversion/libsvn_subr/sqlite.c
./contrib/subversion/subversion/libsvn_subr/sqlite3wrapper.c
./crypto/heimdal/lib/hdb/hdb-sqlite.c
./crypto/heimdal/lib/krb5/scache.c

wpaは、無線LAN方面かな。あとは良く知らないなあ。

on pkg

色々なパッケージが利用してないか?

[sakae@fb ~]$ pkg info -r sqlite3-3.40.1,1
sqlite3-3.40.1,1:
        nss-3.88.1
        sqlitebrowser-3.12.1_7
        py39-sqlite3-3.9.16_7
        tracker3-3.1.2_6
        grilo-plugins-0.3.15
        webkit2-gtk3-2.34.6_4
        gnupg-2.3.8
        tracker-2.3.4_9
        libsoup-2.74.3
        libsoup3-3.2.2
        colord-1.4.6
        py38-sqlite3-3.8.16_7
        gom-0.4

案外、知らないパッケージが使っていた。このうち、自分が意思を持っていれ たのは、sqlitebrowserだけだ。知らないうちに、汚染されちゃってる訳ね。

etc