Skip to content

Commit 289dffa

Browse files
v0.6.3
Init Commit
1 parent 6ef957f commit 289dffa

File tree

8 files changed

+1085
-0
lines changed

8 files changed

+1085
-0
lines changed

config.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"namespace": "linuxsystem",
3+
"name": "linux-system",
4+
"description": "PHP-Controllable Linux System Extension",
5+
"author": "Project Saturn Studios, LLC",
6+
"version": "0.6.3",
7+
"warnings": {
8+
"unused-variable": true,
9+
"unused-variable-external": true,
10+
"possible-wrong-parameter": true,
11+
"possible-wrong-parameter-undefined": true,
12+
"nonexistent-function": true,
13+
"nonexistent-class": true
14+
},
15+
"optimizations": {
16+
"static-type-inference": true,
17+
"static-type-inference-second-pass": true,
18+
"local-context-pass": true,
19+
"constant-folding": true,
20+
"static-constant-class-folding": true,
21+
"call-gatherer-pass": true,
22+
"check-invalid-reads": true,
23+
"growth-cache-object": true,
24+
"growth-cache-array": true
25+
}
26+
}
27+

install.sh

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
#!/bin/bash
2+
3+
# Portable installer for the HardwarePHP extension.
4+
# - Detects php extension dir dynamically
5+
# - Enables for available SAPIs (CLI/FPM/Apache) when present
6+
# - Finds zephir automatically or respects $ZEPHIR_BIN
7+
# - Provides clear preflight checks and verification
8+
9+
set -Eeuo pipefail
10+
11+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12+
EXTENSION_NAME="linuxsystem"
13+
BUILD_SO="${SCRIPT_DIR}/ext/modules/${EXTENSION_NAME}.so"
14+
LOG_FILE="${SCRIPT_DIR}/build.log"
15+
16+
if [ "${EUID:-$(id -u)}" -ne 0 ]; then
17+
SUDO="sudo"
18+
else
19+
SUDO=""
20+
fi
21+
22+
die() {
23+
echo ""
24+
echo "$*"
25+
exit 1
26+
}
27+
28+
require_cmd() {
29+
local cmd="$1"
30+
command -v "$cmd" >/dev/null 2>&1 || die "Required command not found: $cmd"
31+
}
32+
33+
header() {
34+
echo "=========================================="
35+
echo "LinuxDevices Extension Installer (portable)"
36+
echo "=========================================="
37+
echo ""
38+
}
39+
40+
step() {
41+
echo "$*"
42+
}
43+
44+
ok() {
45+
echo "$*"
46+
}
47+
48+
header
49+
50+
# Preflight
51+
step "🔎 Preflight checks..."
52+
require_cmd php
53+
require_cmd php-config
54+
55+
# Resolve Zephir
56+
if [ -n "${ZEPHIR_BIN:-}" ]; then
57+
ZEPHIR="$ZEPHIR_BIN"
58+
elif command -v zephir >/dev/null 2>&1; then
59+
ZEPHIR="$(command -v zephir)"
60+
elif [ -x "$HOME/.config/composer/vendor/bin/zephir" ]; then
61+
ZEPHIR="$HOME/.config/composer/vendor/bin/zephir"
62+
else
63+
die "Zephir not found. Install via composer (composer global require phalcon/zephir) or set ZEPHIR_BIN."
64+
fi
65+
ok "Found zephir: $ZEPHIR"
66+
67+
PHP_VER_MM="$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')"
68+
PHP_VER_NN="$(php -r 'echo PHP_MAJOR_VERSION.PHP_MINOR_VERSION;')"
69+
PHP_EXT_DIR="$(php-config --extension-dir || true)"
70+
[ -n "$PHP_EXT_DIR" ] || die "Could not determine PHP extension dir (php-config --extension-dir)."
71+
ok "PHP version: ${PHP_VER_MM}"
72+
ok "Extension dir: ${PHP_EXT_DIR}"
73+
74+
# Set safe CFLAGS for GCC 14 compatibility (override any invalid flags from environment)
75+
# Suppress Zephir kernel code warnings that are harmless but break with GCC 14
76+
export CFLAGS="-Wno-error -Wno-error=incompatible-pointer-types -Wno-pointer-compare"
77+
export CPPFLAGS="${CPPFLAGS:-} -Wno-error -Wno-error=incompatible-pointer-types"
78+
echo ""
79+
80+
# Clean previous build
81+
step "🧹 Cleaning previous build..."
82+
cd "${SCRIPT_DIR}"
83+
if ! "$ZEPHIR" fullclean >"$LOG_FILE" 2>&1; then
84+
tail -50 "$LOG_FILE" || true
85+
die "Clean failed. See ${LOG_FILE}."
86+
fi
87+
ok "Clean complete"
88+
echo ""
89+
90+
# Build
91+
step "🔨 Building extension..."
92+
if ! "$ZEPHIR" build >>"$LOG_FILE" 2>&1; then
93+
tail -80 "$LOG_FILE" || true
94+
die "Build failed. See ${LOG_FILE}."
95+
fi
96+
if [ ! -f "$BUILD_SO" ]; then
97+
tail -80 "$LOG_FILE" || true
98+
die "Build output not found at ${BUILD_SO}."
99+
fi
100+
ok "Build complete"
101+
echo ""
102+
103+
# Install .so
104+
step "📦 Installing binary..."
105+
$SUDO cp -f "$BUILD_SO" "${PHP_EXT_DIR}/${EXTENSION_NAME}.so"
106+
$SUDO chmod 755 "${PHP_EXT_DIR}/${EXTENSION_NAME}.so"
107+
ok "Copied to: ${PHP_EXT_DIR}/${EXTENSION_NAME}.so"
108+
echo ""
109+
110+
# Enable extension across detected SAPIs
111+
step "⚙️ Enabling extension..."
112+
declare -a CONF_DIR_CANDIDATES=()
113+
114+
# CLI scan dir (most accurate for CLI)
115+
CLI_SCAN_DIR="$(php --ini 2>/dev/null | awk -F': ' '/Scan for additional \.ini files in:/{print $2}' || true)"
116+
if [ -n "$CLI_SCAN_DIR" ] && [ "$CLI_SCAN_DIR" != "(none)" ] && [ -d "$CLI_SCAN_DIR" ]; then
117+
CONF_DIR_CANDIDATES+=("$CLI_SCAN_DIR")
118+
fi
119+
120+
# Debian/Ubuntu common paths
121+
for d in "/etc/php/${PHP_VER_MM}/cli/conf.d" "/etc/php/${PHP_VER_MM}/fpm/conf.d" "/etc/php/${PHP_VER_MM}/apache2/conf.d"; do
122+
[ -d "$d" ] && CONF_DIR_CANDIDATES+=("$d")
123+
done
124+
125+
# Alpine common path (e.g., /etc/php83/conf.d)
126+
ALPINE_CONF="/etc/php${PHP_VER_NN}/conf.d"
127+
[ -d "$ALPINE_CONF" ] && CONF_DIR_CANDIDATES+=("$ALPINE_CONF")
128+
129+
# FPM generic fallbacks
130+
for d in "/etc/php-fpm.d" "/etc/php-fpm/conf.d"; do
131+
[ -d "$d" ] && CONF_DIR_CANDIDATES+=("$d")
132+
done
133+
134+
# Deduplicate
135+
mapfile -t CONF_DIRS < <(printf "%s\n" "${CONF_DIR_CANDIDATES[@]}" | awk '!seen[$0]++')
136+
137+
if [ "${#CONF_DIRS[@]}" -eq 0 ]; then
138+
echo " ⚠️ No conf.d directories found; enabling only for current CLI context via php.d scan dir."
139+
fi
140+
141+
INI_NAME="30-${EXTENSION_NAME}.ini"
142+
INI_CONTENT="extension=${EXTENSION_NAME}.so"
143+
144+
for confd in "${CONF_DIRS[@]:-}"; do
145+
INI_PATH="${confd}/${INI_NAME}"
146+
if [ ! -f "$INI_PATH" ]; then
147+
echo "$INI_CONTENT" | $SUDO tee "$INI_PATH" >/dev/null
148+
ok "Created: $INI_PATH"
149+
else
150+
ok "Already enabled: $INI_PATH"
151+
fi
152+
done
153+
echo ""
154+
155+
# Verify CLI load
156+
step "🔍 Verifying installation (CLI)..."
157+
if php -m 2>/dev/null | grep -q "^linuxsystem$"; then
158+
ok "Extension loaded successfully in CLI"
159+
else
160+
echo ""
161+
php -m 2>/dev/null | tail -n +1 >/dev/null || true
162+
die "Extension not detected in CLI. Check ${INI_NAME} placement and php --ini."
163+
fi
164+
echo ""
165+
166+
# Show module info
167+
step "=========================================="
168+
step "Extension Information (CLI)"
169+
step "=========================================="
170+
php --ri linuxsystem || true
171+
echo ""
172+
173+
# Reload FPM if present
174+
if command -v systemctl >/dev/null 2>&1; then
175+
if systemctl list-units --type=service 2>/dev/null | grep -q "php${PHP_VER_MM}-fpm\.service"; then
176+
step "🔁 Reloading php${PHP_VER_MM}-fpm..."
177+
$SUDO systemctl reload "php${PHP_VER_MM}-fpm" || true
178+
ok "php${PHP_VER_MM}-fpm reloaded"
179+
elif systemctl list-units --type=service 2>/dev/null | grep -q "php-fpm\.service"; then
180+
step "🔁 Reloading php-fpm..."
181+
$SUDO systemctl reload "php-fpm" || true
182+
ok "php-fpm reloaded"
183+
fi
184+
fi
185+
186+
echo "✅ Installation complete!"
187+
echo ""
188+
echo "File locations:"
189+
echo " • Binary: ${PHP_EXT_DIR}/${EXTENSION_NAME}.so"
190+
if [ "${#CONF_DIRS[@]}" -gt 0 ]; then
191+
for d in "${CONF_DIRS[@]}"; do
192+
echo " • Config: ${d}/${INI_NAME}"
193+
done
194+
else
195+
echo " • Config: (CLI scan dir via php --ini)"
196+
fi
197+
echo ""
198+
199+
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
namespace LinuxSystem;
2+
3+
use LinuxSystem\IOController;
4+
5+
%{
6+
#include <fcntl.h>
7+
#include <unistd.h>
8+
#include <sys/ioctl.h>
9+
#include <linux/gpio.h>
10+
#include <string.h>
11+
}%
12+
13+
abstract class GeneralPurposeController extends IOController
14+
{
15+
private chip = null;
16+
private line = null;
17+
private direction = 0; // 0 = OUTPUT, 1 = INPUT
18+
19+
public function __construct()
20+
{
21+
this->basePath("/dev/gpiochip");
22+
this->register();
23+
}
24+
25+
public function chip(var c = null) -> int
26+
{
27+
if(!is_null(c)) {
28+
let this->chip = c;
29+
}
30+
return intVal(this->chip);
31+
}
32+
33+
public function line(var l = null, int dir = 0) -> int
34+
{
35+
if(!is_null(l)) {
36+
var results = 0;
37+
int fd = intVal(this->fileDescriptor());
38+
let results = this->claimLine(fd, intVal(l), dir);
39+
40+
if(results >= 0) {
41+
let this->line = l;
42+
let this->direction = dir;
43+
}
44+
45+
}
46+
return intVal(this->line);
47+
}
48+
49+
protected function claimLine(int fd, int line, int direction = 0) -> int
50+
{
51+
int line_fd;
52+
53+
%{
54+
struct gpio_v2_line_request req;
55+
int result;
56+
57+
// Configure the line request
58+
memset(&req, 0, sizeof(req));
59+
req.offsets[0] = line;
60+
req.num_lines = 1;
61+
62+
// Set direction: 0 = OUTPUT, 1 = INPUT
63+
if (direction == 1) {
64+
req.config.flags = GPIO_V2_LINE_FLAG_INPUT;
65+
} else {
66+
req.config.flags = GPIO_V2_LINE_FLAG_OUTPUT;
67+
}
68+
69+
strcpy(req.consumer, "scrapyard");
70+
71+
// Request the line (kernel returns new FD in req.fd)
72+
result = ioctl(fd, GPIO_V2_GET_LINE_IOCTL, &req);
73+
74+
if (result < 0) {
75+
line_fd = -1;
76+
} else {
77+
line_fd = req.fd;
78+
}
79+
}%
80+
81+
// Close the chip FD (don't need it anymore)
82+
this->_close(fd);
83+
84+
// Store the line FD in inherited file_descriptor
85+
if (line_fd >= 0) {
86+
this->fileDescriptor(line_fd);
87+
}
88+
89+
return line_fd;
90+
}
91+
92+
public function releaseLine() -> void
93+
{
94+
this->clearFileDescriptor();
95+
}
96+
97+
public function high() -> bool
98+
{
99+
return this->setHigh(this->fileDescriptor());
100+
}
101+
102+
public function low() -> bool
103+
{
104+
return this->setLow(this->fileDescriptor());
105+
}
106+
107+
public function read() -> int
108+
{
109+
return this->getValue(this->fileDescriptor());
110+
}
111+
112+
protected function getValue(int fd) -> int
113+
{
114+
int value;
115+
116+
%{
117+
struct gpio_v2_line_values values;
118+
int result;
119+
120+
memset(&values, 0, sizeof(values));
121+
values.mask = 1; // Read bit 0
122+
123+
result = ioctl(fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &values);
124+
125+
if (result < 0) {
126+
value = -1;
127+
} else {
128+
value = (values.bits & 1) ? 1 : 0;
129+
}
130+
}%
131+
132+
return value;
133+
}
134+
135+
protected function setHigh(int fd) -> bool
136+
{
137+
int result;
138+
139+
%{
140+
struct gpio_v2_line_values values;
141+
142+
memset(&values, 0, sizeof(values));
143+
values.bits = 1; // HIGH
144+
values.mask = 1; // Update bit 0
145+
146+
result = ioctl(fd, GPIO_V2_LINE_SET_VALUES_IOCTL, &values);
147+
}%
148+
149+
return result >= 0;
150+
}
151+
152+
protected function setLow(int fd) -> bool
153+
{
154+
int result;
155+
156+
%{
157+
struct gpio_v2_line_values values;
158+
159+
memset(&values, 0, sizeof(values));
160+
values.bits = 0; // LOW
161+
values.mask = 1; // Update bit 0
162+
163+
result = ioctl(fd, GPIO_V2_LINE_SET_VALUES_IOCTL, &values);
164+
}%
165+
166+
return result >= 0;
167+
}
168+
}

0 commit comments

Comments
 (0)