MochiuWiki : SUSE, EC, PCB
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
C++の応用 - PolKitのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
C++の応用 - PolKit
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == Polkit (formerly PolicyKit) は、Linuxシステムで特権的な操作を行う際の認可 (authorization) を決定するフレームワークである。<br> sudoのようにプロセス全体をrootで実行するのではなく、細かいアクション単位で許可を判定できる。<br> <br> Polkitは2008年頃にDavid Zeuthenによって開発され、D-Busベースの認可フレームワークとして成熟した。<br> 中核となるAuthorityは <code>org.freedesktop.PolicyKit1</code> というD-Busサービスとして動作し、アプリケーションからSubjectとAction IDを受け取って判定を行う。<br> <br> Action IDは <u>/usr/share/polkit-1/actions/</u> ディレクトリの <code>.policy</code> ファイル (XML形式) で定義され、認可ルールは <u>/etc/polkit-1/rules.d/</u> 等のJavaScriptファイルで記述される。<br> これにより、管理者はアプリケーションやユーザ/groupごとに詳細なポリシーを設定できる。<br> <br> C++からPolkitを利用するには、<code>libpolkit-gobject-1</code> をラップする方法と、D-Busインターフェースを直接呼び出す方法がある。<br> <br> C++と外部ライブラリのみで開発する場合は、GLib / GObjectのリソース管理やD-Busメッセージのパースを適切に行う必要がある。<br> <br> Polkitを適切に使用することで、GUIやCLIアプリケーションから最小限の特権で安全にシステム操作を実行できる。<br> 例えば、システム設定の変更やデバイスのマウント等、root権限が必要な操作をユーザに確認しながら行うことが可能である。<br> <br><br> == Polkitの基本 == ==== 主要な構成要素 ==== Polkitは、下表に示す要素で構成される。<br> <br> <center> {| class="wikitable" |+ Polkitの主要な構成要素 |- ! 要素 !! 説明 |- | Authority || D-Bus上の <code>org.freedesktop.PolicyKit1</code> サービス<br>認可判定の最終的な権限を持つ。 |- | Subject || 認可を求めるエンティティ<br>通常は <code>("unix-process", {"pid": uint32, "start-time": uint64})</code> 形式のD-Bus構造体 |- | Action || <code>.policy</code> XMLファイルで定義された操作<br>Action ID (例: <code>org.freedesktop.policykit.exec</code>) で識別される。 |- | Authentication Agent || ユーザにパスワード等を要求するUIプログラム<br>GUIやTUIで動作する。 |- | Rule files || JavaScriptで記述された <code>.rules</code> ファイル<br>管理者がポリシーをカスタマイズするために使用する。 |} </center> <br> Subjectには、プロセス形式の <code>unix-process</code> の他、セッション形式の <code>unix-session</code> も存在する。<br> <br> C++アプリケーションから自プロセスの認可を確認する場合、<code>unix-process</code> Subjectを使用するのが一般的である。<br> <br> Authentication Agentの実装については、<code>PolkitAgentListener</code> のサブクラス化が必要であるが、本ページではアプリケーション側の認可チェックを中心に解説する。<br> <br> ==== アクションとルールファイル ==== Actionは、<u>/usr/share/polkit-1/actions</u> 内の <code>.policy</code> ファイルで定義される。<br> <br> 各アクションには、以下の属性が含まれる。<br> <br> * action id *: 一意の識別子 *: 例: <u>org.example.myapp.configure</u> *: <br> * description / message *: ユーザに表示される説明文 *: <br> * defaults *: 各認可結果 (allow_any, allow_inactive, allow_active) に対するデフォルトポリシー *: <code>yes</code>、<code>no</code>、<code>auth_self</code>、<code>auth_admin</code> 等が指定できる。 *: <br> * vendor / vendor_url *: アクションの提供者情報 <br> Ruleファイルは、<u>/etc/polkit-1/rules.d/</u> と <u>/usr/share/polkit-1/rules.d/</u> に配置される。<br> ファイル名は <code><数字>-name.rules</code> の形式で、数字の小さいものから順に評価される。<br> <br> JavaScript形式のRuleファイルの例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> polkit.addRule(function(action, subject) { if (action.id == "org.example.myapp.configure" && subject.isInGroup("wheel")) { return polkit.Result.YES; } }); </syntaxhighlight> <br> この例では、<code>wheel</code> グループに属するユーザが <code>org.example.myapp.configure</code> アクションを実行できるようにしている。<br> <br> Ruleファイルを編集した後は、<code>polkit</code> デーモンが自動的に再読み込みを行うが、即座に反映させたい場合は <code>sudo systemctl restart polkit</code> コマンドを実行する。<br> <br><br> == C++からPolkitを利用する方法の比較 == C++からPolkitを利用する主な方法を比較する。<br> <br> <center> {| class="wikitable" |+ Polkit利用方法の比較 |- ! 方法 !! 依存関係 !! 実装難易度 !! 用途 |- | libpolkit-gobject-1 || GLib / GObject、D-Busシステムバス || 低 || 一般的なアプリケーションの認可チェック |- | D-Bus直接呼び出し (libdbus等) || D-Busライブラリ、システムバス || 中 || 軽量実装や特殊なD-Bus制御が必要な場合 |- | sd-bus (systemd) || libsystemd || 中 || systemdベースの環境で直接使用 |} </center> <br> libpolkit-gobject-1は、GObjectのリファレンスカウントとGErrorを適切に扱う必要がある。<br> <br> D-Bus直接呼び出しは、Subject構造体や戻り値のパースを自前で行う必要がある。<br> <br><br> == Polkitのインストール == ==== パッケージ管理システムからインストール ==== 多くのLinuxディストリビューションには、初期状態でPolkitがインストールされている。<br> <br> C++から開発する場合は、追加で開発用パッケージが必要となる。<br> <br> # RHEL sudo dnf install polkit-devel glib2-devel pkg-config # SUSE sudo zypper install polkit-devel glib2-devel pkg-config <br> ==== ソースコードからインストール ==== まず、Polkitのビルドに必要な依存関係のライブラリをインストールする。<br> <br> # RHEL sudo dnf install meson dbus-devel expat-devel systemd-devel glib2-devel \ gtk3-devel gobject-introspection-devel pam-devel \ duktape-devel gtk-doc mozjs115-devel # SUSE sudo zypper install meson dbus-1-devel libexpat-devel systemd-devel \ glib2-devel gtk3-devel gobject-introspection-devel \ mozjs78-devel pam-devel duktape-devel gtk-doc <br> [https://www.freedesktop.org/software/polkit/releases/ Polkit公式Webサイト] または [https://gitlab.freedesktop.org/polkit/polkit/-/tags GitLab]からソースコードをダウンロードする。<br> ダウンロードしたファイルを解凍する。<br> <br> tar xf polkit-<バージョン>.tar.gz cd polkit-<バージョン> <br> または、<code>git clone</code> コマンドを実行して、ソースコードをダウンロードする。<br> <br> git clone https://gitlab.freedesktop.org/polkit/polkit.git cd polkit <br> Polkitのビルドおよびインストールを行う。<br> 指定可能なオプションは、<code>meson configure</code> コマンドで確認できる。<br> <br> # RHEL meson setup build \ --prefix=<Polkitのインストールディレクトリ> \ -Dos_type=redhat \ -Dexamples=true \ -Dman=true \ -Dgtk_doc=true # SUSE meson setup build \ --prefix=<Polkitのインストールディレクトリ> \ -Dos_type=suse \ -Dexamples=true \ -Dman=true \ -Dgtk_doc=true meson compile -C build meson install -C build <br> == CMakeファイルの設定 == ==== Pkg-Configを使用する場合 ==== 以下の例は、<code>pkg_check_modules</code> コマンドを使用して依存関係を解決する例である。<br> <br> <syntaxhighlight lang="cmake"> # CMakeLists.txtファイル find_package(PkgConfig REQUIRED) pkg_check_modules(GLIB2 REQUIRED glib-2.0) pkg_check_modules(POLKITGOBJECT2 REQUIRED polkit-gobject-1) pkg_check_modules(GOBJECT2 REQUIRED gobject-2.0) pkg_check_modules(GIO2 REQUIRED gio-2.0) add_executable(<プロジェクト名> # ...略 ) include_directories( ${GLIB2_INCLUDE_DIRS} ${POLKITGOBJECT2_INCLUDE_DIRS} ${GOBJECT2_INCLUDE_DIRS} ${GIO2_INCLUDE_DIRS} ) link_directories( ${GLIB2_LIBRARY_DIRS} ${POLKITGOBJECT2_LIBRARY_DIRS} ${GOBJECT2_LIBRARY_DIRS} ${GIO2_LIBRARY_DIRS} ) add_definitions( ${GLIB2_CFLAGS_OTHER} ${POLKITGOBJECT2_CFLAGS_OTHER} ${GOBJECT2_CFLAGS_OTHER} ${GIO2_CFLAGS_OTHER} ) target_link_libraries(<プロジェクト名> ${GLIB2_LIBRARIES} ${POLKITGOBJECT2_LIBRARIES} ${GOBJECT2_LIBRARIES} ${GIO2_LIBRARIES} ) </syntaxhighlight> <br> ==== Pkg-Configを使用しない場合 ==== 以下の例は、Pkg-Configを使用せずに直接パスを指定する例である。<br> <br> <syntaxhighlight lang="cmake"> # CMakeLists.txtファイル add_executable(<プロジェクト名> # ...略 ) ## インクルードファイル include_directories( # ...略 /usr/lib64/glib-2.0/include /usr/include/glib-2.0 /usr/include/polkit-1 # ...略 ) ## ライブラリファイル target_link_libraries(<プロジェクト名> # ...略 glib-2.0 polkit-gobject-1 gobject-2.0 gio-2.0 # ...略 ) </syntaxhighlight> <br><br> == libpolkit-gobject-1を使用する方法 == ==== libpolkit-gobject-1とは ==== libpolkit-gobject-1は、Polkitの機能をGLib / GObjectベースのC APIで提供するライブラリである。<br> <br> 主な関数として、Authorityを取得する <code>polkit_authority_get_sync()</code> と、認可を確認する <code>polkit_authority_check_authorization_sync()</code> が存在する。<br> また、Subjectを作成する <code>polkit_unix_process_new_for_owner()</code> 等の関数も提供する。<br> <br> <u>C++から使用する場合、<code>g_object_unref()</code> によるリソース解放や <code>g_error_free()</code> によるGErrorの解放を徹底する。</u><br> <br> また、RAIIラッパーを用意することで、GObjectのリファレンスカウントを安全に管理できる。<br> <br> ==== 開発パッケージのインストール ==== # RHEL sudo dnf install pkg-config polkit-devel glib2-devel # SUSE sudo zypper install pkg-config polkit-devel glib2-devel <br> ==== 認可チェックの実装例 ==== 以下の例では、libpolkit-gobject-1を使用して、Action ID <u>org.example.myapp.configure</u> の認可を確認している。<br> <br> <syntaxhighlight lang="c++"> #include <iostream> #include <memory> #include <unistd.h> #include <polkit/polkit.h> // GErrorをRAIIで管理するヘルパー struct GErrorDeleter { void operator()(GError* e) const { if (e) g_error_free(e); } }; using GErrorPtr = std::unique_ptr<GError, GErrorDeleter>; // GObjectをRAIIで管理するヘルパー struct GObjectDeleter { void operator()(gpointer o) const { if (o) g_object_unref(o); } }; template<typename T> using GObjectPtr = std::unique_ptr<T, GObjectDeleter>; int main() { GError* raw_error = nullptr; PolkitAuthority* authority = polkit_authority_get_sync(nullptr, &raw_error); GErrorPtr error(raw_error); if (!authority) { std::cerr << "Failed to get polkit authority: " << (error ? error->message : "unknown") << "\n"; return 1; } GObjectPtr<PolkitAuthority> auth_ptr(authority); // Subjectを作成 (自プロセス) pid_t pid = getpid(); guint64 start_time = 0; // 実際には /proc/self/stat から取得する PolkitSubject* subject = polkit_unix_process_new_for_owner(pid, start_time, getuid()); if (!subject) { std::cerr << "Failed to create subject\n"; return 1; } GObjectPtr<PolkitSubject> subject_ptr(subject); // 認可チェック PolkitAuthorizationResult* result = polkit_authority_check_authorization_sync( authority, subject, "org.example.myapp.configure", nullptr, // details POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE, nullptr, // cancellable &raw_error); error.reset(raw_error); if (!result) { std::cerr << "Authorization check failed: " << (error ? error->message : "unknown") << "\n"; return 1; } GObjectPtr<PolkitAuthorizationResult> result_ptr(result); if (polkit_authorization_result_get_is_authorized(result)) { std::cout << "Authorized\n"; } else if (polkit_authorization_result_get_is_challenge(result)) { std::cout << "Authentication required\n"; } else { std::cout << "Not authorized\n"; } return 0; } </syntaxhighlight> <br> コンパイル時は、<code>pkg-config</code> を使用して、PolkitとGLibのフラグを取得する。<br> <br> g++ -std=c++17 polkit_check.cpp -o polkit_check $(pkg-config --cflags --libs polkit-gobject-1) <br> <u>※注意</u><br> <u><code>start_time</code> において、実際には <u>/proc/self/stat</u> ファイルから取得する必要がある。</u><br> <u><code>polkit_unix_process_new_for_owner()</code> に0を渡すと、Authority側でstart-timeの検証ができない場合がある。</u><br> <br> <u>認証エージェントを起動させる場合は、flagsに <code>POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION</code> を指定する。</u><br> <br> ==== 非同期認可チェックの例 ==== 以下の例では、<code>GMainLoop</code> と非同期APIを使用して認可チェックを行っている。<br> <br> タイムアウト処理も含めて、GUIや長時間実行されるアプリケーションで使用する場合に適している。<br> <br> <syntaxhighlight lang="c++"> #include <unistd.h> #include <polkit/polkit.h> void check_authorization(PolkitAuthority *authority, GAsyncResult *res, gpointer user_data); gboolean do_cancel(GCancellable *cancellable); int main() { const gchar *action_id = "org.example.policykit.do"; GMainLoop *loop = g_main_loop_new(nullptr, FALSE); PolkitAuthority *authority = polkit_authority_get_sync(nullptr, nullptr); // 親プロセスのPIDを使用する例 auto parent_pid = getppid(); if (parent_pid == 1) { g_printerr("Parent process was reaped by init(1)\n"); return -1; } PolkitSubject *subject = polkit_unix_process_new_for_owner(parent_pid, 0, getuid()); GCancellable *cancellable = g_cancellable_new(); g_timeout_add(1000 * 10, (GSourceFunc)do_cancel, cancellable); polkit_authority_check_authorization( authority, subject, action_id, nullptr, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, cancellable, (GAsyncReadyCallback)check_authorization, loop); g_main_loop_run(loop); g_object_unref(authority); g_object_unref(subject); g_object_unref(cancellable); g_main_loop_unref(loop); return 0; } void check_authorization(PolkitAuthority *authority, GAsyncResult *res, gpointer user_data) { GError *error = nullptr; PolkitAuthorizationResult *result = polkit_authority_check_authorization_finish(authority, res, &error); if (error != nullptr) { g_print("Error checking authorization: %s\n", error->message); g_error_free(error); } else { const gchar *result_str; if (polkit_authorization_result_get_is_authorized(result)) { result_str = "authorized"; } else if (polkit_authorization_result_get_is_challenge(result)) { result_str = "challenge"; } else { result_str = "not authorized"; } g_print("Authorization result: %s\n", result_str); } if (result) g_object_unref(result); auto loop = static_cast<GMainLoop*>(user_data); g_main_loop_quit(loop); } gboolean do_cancel(GCancellable *cancellable) { g_print("Timer has expired; cancelling authorization check\n"); g_cancellable_cancel(cancellable); return FALSE; } </syntaxhighlight> <br> 非同期APIを使用する場合、<code>GCancellable</code> と <code>GMainLoop</code> の組み合わせにより、応答待ち中でも他のイベント処理を継続できる。<br> <br><br> == D-Busを直接使用する方法 == ==== D-Busインターフェースの概要 ==== PolkitのAuthorityは、D-Busシステムバス上で以下に示すサービスとして公開されている。<br> <br> <center> {| class="wikitable" |+ Polkit AuthorityのD-Busインターフェース |- ! 項目 !! 内容 |- | Well-known name || <u>org.freedesktop.PolicyKit1</u> |- | Object path || <u>/org/freedesktop/PolicyKit1/Authority</u> |- | Interface || <u>org.freedesktop.PolicyKit1.Authority</u> |- | Method || <u>CheckAuthorization</u> |- | Bus type || システムバス |} </center> <br> <code>CheckAuthorization()</code> の引数は以下の通りである。<br> <br> * subject *: D-Bus構造体 <code>(sv)</code> *: sに <code>"unix-process"</code>、vに <code>{"pid": uint32, "start-time": uint64}</code> の辞書を渡す。 *: <br> * action_id *: 確認対象のアクションID (string型) *: <br> * details *: 追加情報を表す <code>a{ss}</code> 形式の辞書 *: <br> * flags *: uint32型 *: <u>0</u> は問い合わせのみ *: <u>1</u> (<code>AllowUserInteraction</code>) は認証エージェントの起動を許可する。 *: <br> * cancellation_id *: キャンセル用の文字列 *: 空文字列を指定可能 <br> 戻り値は <code>(bba{ss})</code> 形式の構造体であり、is_authorized (bool)、is_challenge (bool)、details (dict) の順に格納される。<br> <br> ==== 認可チェックの実装例 (sd-busを使用) ==== 以下の例では、libsystemdのsd-busを使用して、Authorityを直接呼び出している。<br> <br> 以下の例では、エラーハンドリングは簡潔化しているため、実運用ではより詳細なチェックが必要である。<br> <br> <syntaxhighlight lang="c++"> #include <iostream> #include <cstring> #include <systemd/sd-bus.h> #include <unistd.h> int main() { sd_bus* bus = nullptr; int r = sd_bus_default_system(&bus); if (r < 0) { std::cerr << "Failed to connect to system bus: " << strerror(-r) << "\n"; return 1; } sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus_message* reply = nullptr; r = sd_bus_call_method( bus, "org.freedesktop.PolicyKit1", "/org/freedesktop/PolicyKit1/Authority", "org.freedesktop.PolicyKit1.Authority", "CheckAuthorization", &error, &reply, "(sv)sa{ss}us", "unix-process", "a{sv}", 2, "pid", "u", static_cast<uint32_t>(getpid()), "start-time", "t", static_cast<uint64_t>(0), "org.example.myapp.configure", 0, // details: empty a{ss} 0u, // flags ""); // cancellation_id if (r < 0) { std::cerr << "CheckAuthorization failed: " << error.message << "\n"; sd_bus_error_free(&error); sd_bus_unref(bus); return 1; } int authorized = 0; int challenge = 0; r = sd_bus_message_read(reply, "(bba{ss})", &authorized, &challenge, nullptr); if (r < 0) { std::cerr << "Failed to parse reply\n"; } else { std::cout << "Authorized: " << authorized << ", Challenge: " << challenge << "\n"; } sd_bus_message_unref(reply); sd_bus_unref(bus); return 0; } </syntaxhighlight> <br> コンパイル時は、<code>pkg-config</code> コマンドを使用してlibsystemdのフラグを取得する。<br> <br> g++ -std=c++17 polkit_dbus_check.cpp -o polkit_dbus_check $(pkg-config --cflags --libs libsystemd) <br> D-Bus直接呼び出しでは、Subject構造体の作成や戻り値のパースを正確に行う必要がある。<br> <br> start-timeに0を渡す場合、Polkitはプロセスのstart-timeを検証できないため、本番環境では <u>/proc/self/stat</u> ファイルから取得すること。<br> <br><br> == トラブルシューティング == ==== 認可が常に拒否される ==== * 原因 ** Action IDが <u>/usr/share/polkit-1/actions</u> ディレクトリ内の <code>.policy</code> ファイルで定義されていない。 ** Ruleファイルで明示的に拒否 (return <code>polkit.Result.NO</code>) されている。 ** Subjectの <code>pid</code> や <code>start-time</code> が正しくない。 *: <br> * 対策 ** <code>pkaction --verbose</code> でAction IDが登録されているか確認する。 ** Ruleファイルの優先順位 (ファイル名の数字) を確認する。 ** Subjectのstart-timeを <u>/proc/self/stat</u> から正しく取得する。 <br> ==== 認証ダイアログが表示されない ==== * 原因 ** <code>CheckAuthorization</code> のflagsに <code>AllowUserInteraction</code> (1) が指定されていない。 ** セッションに認証エージェントが登録されていない。 ** SSHやSystemdサービス等の非対話的環境で実行されている。 *: <br> * 対策 ** flagsに <code>POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION</code> または <code>1u</code> を指定する。 ** グラフィカルセッションやPolkitエージェントが動作している環境でテストする。 ** 非対話的環境では、Ruleファイルで事前に許可するか、別途認証方法を用意する。 <br> ==== コンパイル時にGObjectのリンクエラーが発生する ==== * 原因 ** <code>pkg-config --libs polkit-gobject-1</code> の出力に含まれるGLib/GObjectのライブラリがリンクされていない。 ** ヘッダファイルのincludeパスが通っていない。 *: <br> * 対策 ** 必ず <code>$(pkg-config --cflags --libs polkit-gobject-1)</code> をコンパイルコマンドに含める。 ** 必要に応じて <code>glib-2.0</code> のpkg-configも追加する。 ** CMakeを使用する場合は <code>pkg_check_modules(POLKIT polkit-gobject-1)</code> を使用する。 <br> ==== D-Bus呼び出しで "Permission denied" となる ==== * 原因 ** システムバスへの接続権限がない。 ** 必要なD-Busポリシーが設定されていない。 *: <br> * 対策 ** プログラムが通常ユーザ権限で実行されていることを確認する。 ** D-Bus設定ファイル (<u>/usr/share/dbus-1/system.d/</u> ディレクトリ等) でサービスへのアクセス権限を確認する。 ** Polkit自身はroot権限で動作する必要があるが、呼び出し側は通常ユーザでよい。 <br><br> == 関連情報 == * [https://www.freedesktop.org/software/polkit/docs/latest/ Polkit公式ドキュメント] * [https://gitlab.freedesktop.org/polkit/polkit Polkit GitLab] * [https://www.freedesktop.org/wiki/Software/polkit/ Polkit Wiki] * [https://www.freedesktop.org/software/systemd/man/latest/sd-bus.html sd-bus documentation] * [[Qtの基礎 - 管理者権限]] <br><br> {{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,Polkit,PolicyKit,Authorization,D-Bus,電気回路,電子回路,基板,プリント基板 |description={{PAGENAME}} - C++からPolkit (PolicyKit) の認可を確認する方法。libpolkit-gobject-1のラップとsd-busによるD-Bus直接呼び出し、非同期認可チェック、CMake設定、ビルド、トラブルシューティングを紹介する | This page is {{PAGENAME}} in our wiki about checking Polkit authorization from pure C++ without Qt using libpolkit-gobject-1 and sd-bus |image=/resources/assets/MochiuLogo_Single_Blue.png }} __FORCETOC__ [[カテゴリ:C++]]
C++の応用 - PolKit
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse