This commit is contained in:
CismonX 2018-05-01 17:57:55 +08:00
commit 9ab1638ec1
Signed by: cismonx
GPG Key ID: 3094873E29A482FB
11 changed files with 488 additions and 0 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.h linguist-language=C

36
.gitignore vendored Normal file
View File

@ -0,0 +1,36 @@
.deps
*.lo
*.la
.libs
acinclude.m4
aclocal.m4
autom4te.cache
build
config.guess
config.h
config.h.in
config.log
config.nice
config.status
config.sub
configure
configure.in
include
install-sh
libtool
ltmain.sh
Makefile
Makefile.fragments
Makefile.global
Makefile.objects
missing
mkinstalldirs
modules
run-tests.php
tests/*/*.diff
tests/*/*.out
tests/*/*.php
tests/*/*.exp
tests/*/*.log
tests/*/*.sh
.vscode/

17
.travis.yml Normal file
View File

@ -0,0 +1,17 @@
dist: bionic
group: edge
language: php
php:
- 7.1
- 7.2
- 7.3
- 7.4
script:
- phpize
- ./configure --enable-ioctl-helper
- make
- make test

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 CismonX
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

92
README.md Normal file
View File

@ -0,0 +1,92 @@
# ext-ioctl
[![Travis-CI](https://travis-ci.com/CismonX/ext-ioctl.svg?branch=master)](https://travis-ci.com/CismonX/ext-ioctl)
[![MIT license](https://img.shields.io/badge/licence-MIT-blue.svg)](https://opensource.org/licenses/MIT)
## 1. Introduction
PHP binding for the [`ioctl()`](http://man7.org/linux/man-pages/man2/ioctl.2.html) system call. Supports PHP 7.1 and above.
Using this extension is like playing with fire. Do not touch it unless you know **EXACTLY** what you are doing.
## 2. Documentation
### 2.1 Basic usage
```PHP
/**
* Manipulates the underlying device parameters of special files.
*
* @param resource|int $fd
* @param int $request
* @param string $argp[optional]
* @param int $errno[optional]
* @return int|false
*/
function ioctl($fd, $request, $argp, &$errno) {}
```
* The `$fd` parameter can be an exact file descriptor (integer) or a `resource` holding it.
* The `$argp` parameter should be treated with care.
* If the underlying data type is a struct, memory alignment should be taken into consideration.
* If the `ioctl()` system call will write to `$argp`, you must pass a string buffer with enough size.
* PHP's `pack()` and `unpack()` functions can be very useful for this.
* If operation fails, `$errno` will be set, and the function will return `false`.
See also: [examples](examples/).
### 2.2 Helper functions
In userland PHP it's not possible to allocate a string buffer without initialization, all we can do is something like `$buffer = str_repeat("\0", $length);`.
This extension provides a helper function for better performance.
```PHP
/**
* Allocate a string with given length without initialization.
*
* @param int $len
* @return string
*/
function str_alloc($len) {}
```
Sometimes, if the struct you are working with contains pointers, what you actually get is an unsigned integer, and there's nothing you can do with it.
This extension provides two simple helper functions as a workaround.
```PHP
/**
* Get the address of a string.
*
* @param string $str
* @return int
*/
function str2ptr($str) {}
/**
* Copy data from the specified address into a string.
*
* @param int $ptr
* @param int $length
* @return string
*/
function ptr2str($ptr, $length) {}
```
* With `str2ptr()`, you can get the address of the initial byte of a `string` (the char array itself, not `zend_string`).
* If you receive a pointer, you can use `ptr2str()` to copy data from that address to a string buffer.
Some `ioctl` operations requires a file descriptor in `$argp`. However, you cannot extract the underlying file descriptor from a `resource` in userland PHP. So we need another helper function.
```PHP
/**
* Get underlying file descriptor of a resource. Returns -1 on failure.
*
* @param resource $res
* @return int
*/
function res2fd($res) {}
```
Note that the helper functions are not enabled by default. You should add `--enable-ioctl-helper` when executing configure script.

12
config.m4 Normal file
View File

@ -0,0 +1,12 @@
PHP_ARG_ENABLE(ioctl, for ioctl support,
[ --enable-ioctl Enable ioctl support ])
PHP_ARG_ENABLE(ioctl-helper, for ioctl helper functions,
[ --enable-ioctl-helper Enable ioctl helper functions ], no, no)
if test "$PHP_IOCTL" != "no"; then
PHP_NEW_EXTENSION(ioctl, src/ioctl.c, $ext_shared)
if test "$PHP_IOCTL_HELPER" != "no"; then
AC_DEFINE(IOCTL_ENABLE_HELPER_FUNCTIONS, 1, [ ])
fi
fi

81
examples/winsize.php Normal file
View File

@ -0,0 +1,81 @@
<?php
/**
* ext-ioctl/examples/winsize.php
*
* A simple example for using the ioctl() system call.
* Get the current windows size of this TTY.
* This script only works if you are using PHP-CLI on a TTY.
*
* @author CismonX
*/
// Constants defined in sys/ioctl.h
const TIOCGWINSZ = 0x5413;
// We need `pcntl_signal()` for this example.
if (!extension_loaded('pcntl')) {
echo 'To try this example, please install pcntl extension.', PHP_EOL;
exit;
}
// Get file descriptor of current TTY.
$handle = fopen(rtrim(`tty`), 'r+');
if ($handle === false) {
echo 'To try this example, please run PHP-CLI on a TTY.', PHP_EOL;
exit;
}
/**
* Fetch current TTY window size.
*
* @param resource $handle
* @return array|false
*/
function get_window_size($handle) {
// See definition of struct winsize:
//
// struct winsize {
// unsigned short ws_row;
// unsigned short ws_col;
// unsigned short ws_xpixel; /* unused */
// unsigned short ws_ypixel; /* unused */
// };
//
// Assuming we're on a 64-bit platform, an unsigned short usually contains 16 bits.
// So we should allocate an 8-byte buffer.
$buffer = str_alloc(8);
// Fetch current window size via ioctl().
$result = ioctl($handle, TIOCGWINSZ, $buffer, $errno);
if ($result === false) {
return false;
}
// Unpack the buffer into an array of four integers.
return unpack('S4', $buffer);
}
// Get current window size.
$winsize = get_window_size($handle);
if (!$winsize) {
echo 'Failed to get window size. ', posix_strerror($errno), PHP_EOL;
exit;
}
[, $height, $width] = $winsize;
echo "Window height: $height, width: $width. Try resize window or press Ctrl+C to quit.", PHP_EOL;
// Enable asynchronous signal handling.
pcntl_async_signals(true);
// Register signal handler which gets invoked when window size changes.
pcntl_signal(SIGWINCH, function ($signo) use ($handle) {
$winsize = get_window_size($handle);
if ($winsize) {
[, $height, $width] = $winsize;
echo "SIGWINCH caught. Window height: $height, width: $width.", PHP_EOL;
}
});
while (1) {
sleep(1);
}

182
src/ioctl.c Normal file
View File

@ -0,0 +1,182 @@
//
// ext-ioctl/ioctl.c
//
// @Author CismonX
//
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <php.h>
#include <ext/standard/info.h>
#include <sys/ioctl.h>
#include "php_ioctl.h"
#define ERRNO_SET(zval, err) \
if (zval) { \
ZVAL_DEREF(zval); \
ZVAL_LONG(zval, err); \
}
PHP_FUNCTION(ioctl)
{
zval *fd;
zend_long request;
zend_string *argp = NULL;
zval *err = NULL;
ZEND_PARSE_PARAMETERS_START(2, 4)
Z_PARAM_ZVAL(fd)
Z_PARAM_LONG(request)
Z_PARAM_OPTIONAL
Z_PARAM_STR(argp)
Z_PARAM_ZVAL(err)
ZEND_PARSE_PARAMETERS_END();
php_stream *stream;
int fd_num;
if (Z_TYPE_P(fd) == IS_LONG) {
fd_num = Z_LVAL_P(fd);
} else if (UNEXPECTED(Z_TYPE_P(fd) != IS_RESOURCE)) {
ERRNO_SET(err, -1);
php_error_docref(NULL, E_WARNING, "Invalid fd. Resource or integer expected.");
RETURN_FALSE;
} else {
php_stream_from_zval(stream, fd);
struct {
FILE *file;
int fd;
} *stream_data = stream->abstract;
if (stream_data == NULL) {
ERRNO_SET(err, -2);
php_error_docref(NULL, E_WARNING, "Unexpected error. Failed to fetch stream data.");
RETURN_FALSE;
}
fd_num = stream_data->fd;
}
int result = ioctl(fd_num, (int)request, argp ? ZSTR_VAL(argp) : NULL);
if (result == -1) {
ERRNO_SET(err, errno);
RETURN_FALSE;
}
RETVAL_LONG(result);
}
ZEND_BEGIN_ARG_INFO(ioctl_arginfo, 0)
ZEND_ARG_INFO(0, fd)
ZEND_ARG_TYPE_INFO(0, request, IS_LONG, 0)
ZEND_ARG_TYPE_INFO(0, argp, IS_STRING, 0)
ZEND_ARG_INFO(1, errno)
ZEND_END_ARG_INFO()
#ifdef IOCTL_ENABLE_HELPER_FUNCTIONS
PHP_FUNCTION(str_alloc)
{
zend_long len;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_LONG(len);
ZEND_PARSE_PARAMETERS_END();
if (len < 0) {
php_error_docref(NULL, E_WARNING, "Buffer length should be positive.");
RETURN_EMPTY_STRING();
}
zend_string *buf = zend_string_alloc(len, 0);
#ifdef PHP_DEBUG
// Make the string nul-terminated to prevent warnings on debug builds of PHP.
ZSTR_VAL(buf)[len] = '\0';
#endif
RETVAL_STR(buf);
}
ZEND_BEGIN_ARG_INFO(str_alloc_arginfo, 0)
ZEND_ARG_TYPE_INFO(0, len, IS_LONG, 0)
ZEND_END_ARG_INFO()
PHP_FUNCTION(str2ptr)
{
zend_string *str;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STR(str);
ZEND_PARSE_PARAMETERS_END();
RETVAL_LONG((zend_long)ZSTR_VAL(str));
}
ZEND_BEGIN_ARG_INFO(str2ptr_arginfo, 0)
ZEND_ARG_TYPE_INFO(0, str, IS_STRING, 0)
ZEND_END_ARG_INFO()
PHP_FUNCTION(ptr2str)
{
zend_long ptr;
zend_long length;
ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_LONG(ptr)
Z_PARAM_LONG(length)
ZEND_PARSE_PARAMETERS_END();
RETVAL_STR(zend_string_init((char*)ptr, length, 0));
}
ZEND_BEGIN_ARG_INFO(ptr2str_arginfo, 0)
ZEND_ARG_TYPE_INFO(0, ptr, IS_LONG, 0)
ZEND_ARG_TYPE_INFO(0, length, IS_LONG, 0)
ZEND_END_ARG_INFO()
PHP_FUNCTION(res2fd)
{
zval *res;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ZVAL(res)
ZEND_PARSE_PARAMETERS_END();
php_stream* stream;
php_stream_from_zval(stream, res);
struct {
FILE *file;
int fd;
} *stream_data = stream->abstract;
if (stream_data) {
RETURN_LONG(stream_data->fd);
}
RETVAL_LONG(-1);
}
ZEND_BEGIN_ARG_INFO(res2fd_arginfo, 0)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_END_ARG_INFO()
#endif // IOCTL_ENABLE_HELPER_FUNCTIONS
zend_function_entry ioctl_functions[] = {
PHP_FE(ioctl, ioctl_arginfo)
#ifdef IOCTL_ENABLE_HELPER_FUNCTIONS
PHP_FE(str_alloc, str_alloc_arginfo)
PHP_FE(str2ptr, str2ptr_arginfo)
PHP_FE(ptr2str, ptr2str_arginfo)
PHP_FE(res2fd, res2fd_arginfo)
#endif // IOCTL_ENABLE_HELPER_FUNCTIONS
PHP_FE_END
};
PHP_MINFO_FUNCTION(ioctl)
{
php_info_print_table_start();
php_info_print_table_header(2, "ioctl support", "enabled");
php_info_print_table_end();
}
zend_module_entry ioctl_module_entry = {
STANDARD_MODULE_HEADER,
"ioctl",
ioctl_functions,
NULL,
NULL,
NULL,
NULL,
PHP_MINFO(ioctl),
PHP_IOCTL_VERSION,
STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_IOCTL
ZEND_GET_MODULE(ioctl)
#endif

14
src/php_ioctl.h Normal file
View File

@ -0,0 +1,14 @@
//
// ext-ioctl/php_ioctl.h
//
// @Author CismonX
//
#ifndef PHP_IOCTL_H
#define PHP_IOCTL_H
extern zend_module_entry ioctl_module_entry;
#define PHP_IOCTL_VERSION "1.0.0"
#endif // !PHP_IOCTL_H

8
tests/00-loaded.phpt Normal file
View File

@ -0,0 +1,8 @@
--TEST--
Test whether ext-ioctl is loaded.
--FILE--
<?php
if (!extension_loaded('ioctl'))
echo 'Extension ioctl is not loaded.', PHP_EOL;
?>
--EXPECT--

24
tests/01-helpers.phpt Normal file
View File

@ -0,0 +1,24 @@
--TEST--
Test whether helpers works normally.
--SKIPIF--
<?php
if (!function_exists('res2fd'))
echo 'skip helper functions are disabled';
?>
--FILE--
<?php
$buf = str_alloc(10);
if (strlen($buf) != 10) {
echo 'Test for `str_alloc()` failed.', PHP_EOL;
}
$str = 'hello';
$str_addr = str2ptr($str);
if ($str != ptr2str($str_addr, strlen($str))) {
echo 'Test for `str2ptr()` and `ptr2str()` failed.', PHP_EOL;
}
$res = stream_socket_server('tcp://0.0.0.0:4567');
if (res2fd($res) === false) {
echo 'Test for `res2fd()` failed.', PHP_EOL;
}
?>
--EXPECT--