CでPythonの拡張モジュールを作成する

OMFG! -読者は叫ぶかもしれません。 PythonがあるのになぜCで何かを書くのか、それは大体正しいでしょう。 しかし、 幸いことに、私たちの環境に優しい友人は全能ではありません。 だから...



タスクの説明



現在のプロジェクトのフレームワーク( Libvirtに基づく仮想マシン管理システム)では、Linuxでループデバイスをプログラムで操作する必要がありました。 subprocess.Popen()を介してlosetupコマンドラインコマンドを呼び出すことに基づいた最初のバージョンは、Ubuntu 8.04で非常によく機能しましたが、リリース後、RHELや他のシステムで宣言された機能が動作しないというバグが報告されました。 いくつかの試行の後、losetupはそれらの中でわずかに異なる引数を受け入れ、タスクをうまく動作させないことが判明しました。



losetupのソースコードを調べてみると、IOCTL呼び出しをデバイスに送信することで必要なすべての操作が行われていることがわかりました。 Python fcntl.ioctl()で、何かがおかしくなりました。 レベルを下げて、Cでモジュールを書くことが決定されました。



免責事項



結局のところ、fcntl.ioctl()は、必要なものをすべて実装するのに十分です。 最初は何が怖かったのか覚えていません。 おそらく1日10時間未満で働く必要があります;)



一方、すぐに使用した場合、このトピックは存在しません。



繰り返しますが、斜めに読む人のために-Pythonには素晴らしいモジュールfcntl.ioctl()があります。 以下はすべて一例です。



APIプランニング



Pythonでできることは、Pythonで行うことだけです。 うまくいかないのは、Cで低レベルにすることです。



私がPythonでできないことは少し蓄積されています:実際にイメージをマウント/アンマウントし、デバイスがビジーかどうかを確認します。



タスクの一部として、暗号化やその他の機能をサポートするための要件はなかったため、Cインターフェイスは非常にシンプルであることが判明しました。





スケルトンを作る



このモジュールは、コマンドラインユーティリティと同様に、losetupと呼ばれます。 お気に入りのEclipse + PyDevを起動して、プロジェクトを作成します。 その中にlosetup.pyを作成します。このファイルには、モジュールのすべてのPythonコードが含まれます。



システムとの低レベルの対話を実装するモジュールは、_losetupと呼ばれます。 losetupは_losetupをインポートし、それを使用して高レベルAPIを実装します。



2つのファイルlosetupmodule.cおよびlosetupmodule.hを配置するsrcフォルダーを作成します



losetupmodule.c

#include <Python.h&rt;



#include "losetupmodule.h"



// -

static PyObject * LosetupError;



//

static PyObject *

losetup_mount (PyObject * self, PyObject * args)

{

return Py_BuildValue( "" );

}



//

static PyObject *

losetup_unmount (PyObject * self, PyObject * args)

{

return Py_BuildValue( "" );

}



// , -

static PyObject *

losetup_is_used (PyObject * self, PyObject * args)

{

int fd, is_used;

const char * device;

struct loop_info64 li;



if ( ! PyArg_ParseTuple(args, "s" , & device)) {

return NULL ;

}



if ((fd = open (device, O_RDONLY)) < 0 ) {

return PyErr_SetFromErrno(LosetupError);

}



is_used = ioctl(fd, LOOP_GET_STATUS64, & li) == 0 ;



close(fd);

return Py_BuildValue( "i" , is_used);

}



//

// , , ,

static PyMethodDef LosetupMethods[] = {

{ "mount" , losetup_mount, METH_VARARGS, "Mount image to device. Usage _losetup.mount(loop_device, file)." },

{ "unmount" , losetup_unmount, METH_VARARGS, "Unmount image from device. Usage _losetup.unmount(loop_device)." },

{ "is_used" , losetup_is_used, METH_VARARGS, "Returns True is loopback device is in use." },

{ NULL , NULL , 0 , NULL } /* Sentinel */

};



//

PyMODINIT_FUNC

init_losetup ( void )

{

PyObject * m;



// _losetup

m = Py_InitModule( "_losetup" , LosetupMethods);

if (m == NULL )

return ;



//

LosetupError = PyErr_NewException( "_losetup.error" , NULL , NULL );

Py_INCREF(LosetupError);

PyModule_AddObject(m, "error" , LosetupError);

}





#include <Python.h&rt;



#include "losetupmodule.h"



// -

static PyObject * LosetupError;



//

static PyObject *

losetup_mount (PyObject * self, PyObject * args)

{

return Py_BuildValue( "" );

}



//

static PyObject *

losetup_unmount (PyObject * self, PyObject * args)

{

return Py_BuildValue( "" );

}



// , -

static PyObject *

losetup_is_used (PyObject * self, PyObject * args)

{

int fd, is_used;

const char * device;

struct loop_info64 li;



if ( ! PyArg_ParseTuple(args, "s" , & device)) {

return NULL ;

}



if ((fd = open (device, O_RDONLY)) < 0 ) {

return PyErr_SetFromErrno(LosetupError);

}



is_used = ioctl(fd, LOOP_GET_STATUS64, & li) == 0 ;



close(fd);

return Py_BuildValue( "i" , is_used);

}



//

// , , ,

static PyMethodDef LosetupMethods[] = {

{ "mount" , losetup_mount, METH_VARARGS, "Mount image to device. Usage _losetup.mount(loop_device, file)." },

{ "unmount" , losetup_unmount, METH_VARARGS, "Unmount image from device. Usage _losetup.unmount(loop_device)." },

{ "is_used" , losetup_is_used, METH_VARARGS, "Returns True is loopback device is in use." },

{ NULL , NULL , 0 , NULL } /* Sentinel */

};



//

PyMODINIT_FUNC

init_losetup ( void )

{

PyObject * m;



// _losetup

m = Py_InitModule( "_losetup" , LosetupMethods);

if (m == NULL )

return ;



//

LosetupError = PyErr_NewException( "_losetup.error" , NULL , NULL );

Py_INCREF(LosetupError);

PyModule_AddObject(m, "error" , LosetupError);

}







losetupmodule.hで、 util-linux-ngから無慈悲に引き裂かれた一連の定義のみ



アセンブリをカスタマイズする



モジュールはさまざまな方法で構築できますが、最もシンプルで信頼性の高いものはsetuptools(distutils)を使用する方法です。



setup.pyを作成します

from setuptools import setup, Extension

setup(name = 'losetup' ,

version = '1.0.1' ,

description = 'Python API for "loop" Linux module' ,

author = 'Sergey Kirillov' ,

author_email = 'serg@rainboo.com' ,

ext_modules = [Extension( '_losetup' , [ 'src/losetupmodule.c' ], include_dirs = [ 'src' ])],

py_modules = [ 'losetup' ]

)







「ext_modules = [Extension( '_ losetup'、['src / losetupmodule.c']、include_dirs = ['src'])]]」行のすべての白魔術。 これは、コードがsrc / losetupmodule.cにあり、srcに含まれている_losetupという名前の拡張機能について説明しています。 これは、distutilsが拡張機能を収集し、インストールし、そこからあらゆる種類のパッケージを作成するのに十分です(win32インストーラーを含みますが、それほど簡単ではありません)。



「python setup.py build」を呼び出して、すべてがビルドされていることを確認します



筋肉を構築する



mount()メソッドを実装します

static PyObject *

losetup_mount (PyObject * self, PyObject * args)

{

int ffd, fd;

int mode = O_RDWR;

struct loop_info64 loopinfo64;

const char * device, * filename;



// Check parameters

if ( ! PyArg_ParseTuple(args, "ss" , & device, & filename)) {

return NULL ;

}



// Initialize loopinfo64 struct, and set filename

memset( & loopinfo64, 0 , sizeof (loopinfo64));

strncpy(( char * )loopinfo64.lo_file_name, filename, LO_NAME_SIZE - 1 );

loopinfo64.lo_file_name[LO_NAME_SIZE - 1 ] = 0 ;



// Open image file

if ((ffd = open(filename, O_RDWR)) < 0 ) {

if (errno == EROFS) // Try to reopen as read-only on EROFS

ffd = open(filename, mode = O_RDONLY);

if (ffd < 0 ) {

return PyErr_SetFromErrno(LosetupError);

}

loopinfo64.lo_flags |= LO_FLAGS_READ_ONLY;

}



// Open loopback device

if ((fd = open(device, mode)) < 0 ) {

close(ffd);

return PyErr_SetFromErrno(LosetupError);

}



// Set image

if (ioctl(fd, LOOP_SET_FD, ffd) < 0 ) {

close(fd);

close(ffd);

return PyErr_SetFromErrno(LosetupError);

}

close (ffd);



// Set metadata

if (ioctl(fd, LOOP_SET_STATUS64, & loopinfo64)) {

ioctl (fd, LOOP_CLR_FD, 0 );

close (fd);

return PyErr_SetFromErrno(LosetupError);

}

close(fd);



return Py_BuildValue( "" );

}







static PyObject *

losetup_mount (PyObject * self, PyObject * args)

{

int ffd, fd;

int mode = O_RDWR;

struct loop_info64 loopinfo64;

const char * device, * filename;



// Check parameters

if ( ! PyArg_ParseTuple(args, "ss" , & device, & filename)) {

return NULL ;

}



// Initialize loopinfo64 struct, and set filename

memset( & loopinfo64, 0 , sizeof (loopinfo64));

strncpy(( char * )loopinfo64.lo_file_name, filename, LO_NAME_SIZE - 1 );

loopinfo64.lo_file_name[LO_NAME_SIZE - 1 ] = 0 ;



// Open image file

if ((ffd = open(filename, O_RDWR)) < 0 ) {

if (errno == EROFS) // Try to reopen as read-only on EROFS

ffd = open(filename, mode = O_RDONLY);

if (ffd < 0 ) {

return PyErr_SetFromErrno(LosetupError);

}

loopinfo64.lo_flags |= LO_FLAGS_READ_ONLY;

}



// Open loopback device

if ((fd = open(device, mode)) < 0 ) {

close(ffd);

return PyErr_SetFromErrno(LosetupError);

}



// Set image

if (ioctl(fd, LOOP_SET_FD, ffd) < 0 ) {

close(fd);

close(ffd);

return PyErr_SetFromErrno(LosetupError);

}

close (ffd);



// Set metadata

if (ioctl(fd, LOOP_SET_STATUS64, & loopinfo64)) {

ioctl (fd, LOOP_CLR_FD, 0 );

close (fd);

return PyErr_SetFromErrno(LosetupError);

}

close(fd);



return Py_BuildValue( "" );

}











単純に思えますが、おそらくここで何が起こっているのか完全には明らかではありません。 主な要素を見てみましょう。

if ( ! PyArg_ParseTuple(args, "ss" , & device, & filename)) {

return NULL ;

}





if ( ! PyArg_ParseTuple(args, "ss" , & device, & filename)) {

return NULL ;

}









METH_VARARGSとして宣言された関数は、引数をタプルとして受け取ります。 PyArg_ParseTuple()は、引数が指定されたパターン(この場合、「ss」は2行)に一致することを確認し、データを受け取るか、引数がパターンに一致しない場合、エラーを設定してfalseを返します。 これがどのように機能するかについての詳細は、拡張機能でのパラメーターの抽出にあります



Pythonの観点からは、次のようになります。

 >>> _losetupをインポート
 >>> _losetup.mount( "aaa")
トレースバック(最後の最後の呼び出し):
  ファイル「<stdin>」、1行目、<module>
 TypeError:関数は正確に2つの引数を取ります(1つ指定)
 >>> _losetup.mount(1,2)
トレースバック(最後の最後の呼び出し):
  ファイル「<stdin>」、1行目、<module>
 TypeError:引数1は、intではなく文字列でなければなりません
 >>> 




進む

return PyErr_SetFromErrno(LosetupError);







PyErr_SetFromErrnoは、指定されたタイプの例外を作成し、グローバル変数errnoからエラーコードを取得し、NULLを返します-これは、例外が発生したことを意味します。 ドキュメントリンク: Intermezzo:エラーと例外例外処理



Pythonの場合、次のようになります。

 >>> _losetup.mount( '/ dev / loop0'、 '/ tmp / somefile')
トレースバック(最後の最後の呼び出し):
  ファイル「<stdin>」、1行目、<module>
 _losetup.error:(2、 'そのようなファイルまたはディレクトリはありません')
 >>> 




return Py_BuildValue( "" );





return Py_BuildValue( "" );







この関数は特別なデータを返す必要がないため、Noneを返します。 詳細については、 任意の値を作成するをご覧ください



他の機能も同様に実装されます。



PyPIに公開する



したがって、モジュールが作成されます。 人類にそれを使用する機会を与える必要があります。 これを行う最も簡単な方法は、 Python Package Indexでモジュールを公開することです。



PyPIに登録されています。



登録後、コンソールに書き込みます

python setup.pyレジスタ


アカウント情報を入力すると、setuptoolsがPyPIにパッケージを作成します。



python setup.py sdistアップロード


ソース配布(コードとメタデータを含むtgzアーカイブ)を行い、PyPIにアップロードします。



結果はhttp://pypi.python.org/pypi/losetup/で見ることができます



嫌いなRHELを使い、easy_install -U losetupを記述し、魔法の言葉「crib-crab-boom」を言いながら、setuptoolsがパッケージをダウンロードしてビルドし、システムにインストールします。



メインアプリケーションのsetup.pyに依存関係としてlosetupを追加します。 これで、インストール中にsetuptoolsがモジュールもインストールします。



完了



そのため、Pythonから下の抽象化レベルに簡単に移行し、システムとの低レベルの対話用のモジュールを作成するのが簡単であることが突然判明しました。



また、より多くのことを考え、より少なくする必要があることの良い例も得ました。 私たちのグリーンフレンドは強力であり、そのようなエキゾチックなタスクでさえ、彼と別れなくても解決できます。



私もあなたにお願いします。



中古文学






All Articles