mirror of
https://dev.iopsys.eu/feed/iopsys.git
synced 2026-01-28 01:47:19 +01:00
refactor and fix
This commit is contained in:
parent
53e743d0b2
commit
24646d3367
23 changed files with 844 additions and 144 deletions
|
|
@ -34,8 +34,9 @@ define Build/Prepare
|
|||
mkdir -p $(PKG_BUILD_DIR)
|
||||
$(CP) -rf ./src/* $(PKG_BUILD_DIR)/
|
||||
cd $(PKG_BUILD_DIR); \
|
||||
npm install better-sqlite3 && \
|
||||
node json2code.js
|
||||
npm install better-sqlite3 quickjs && \
|
||||
node ./scripts/json2code.js && \
|
||||
node ./scripts/qjs-handlers-validate.js
|
||||
endef
|
||||
|
||||
TARGET_CFLAGS += $(FPIC) -I$(PKG_BUILD_DIR)
|
||||
|
|
@ -62,12 +63,13 @@ endef
|
|||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/usr/lib
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_DIR) $(1)/usr/lib/quickjs
|
||||
$(INSTALL_DIR) $(1)/usr/lib/quickjs/dm_handlers
|
||||
cd $(PKG_BUILD_DIR)/dm-files && find . -type d -exec mkdir -p $(1)/usr/lib/quickjs/dm_handlers/{} \;
|
||||
cd $(PKG_BUILD_DIR)/dm-files && find . -name "*.js" -not -name ".*.js" -not -name "makeDM.js" -type f -exec cp {} $(1)/usr/lib/quickjs/dm_handlers/{} \;
|
||||
$(INSTALL_DIR) $(1)/usr/lib/dmf_handlers
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/default.db $(1)/etc/default_dm.db
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/libdm.so $(1)/usr/lib/
|
||||
# Copy only .js handler files recursively, preserving folder structure (skip hidden files/folders)
|
||||
( cd $(PKG_BUILD_DIR)/dm-files; \
|
||||
find . -type d -not -path './.*' -exec $(INSTALL_DIR) $(1)/usr/lib/dmf_handlers/{} \; ; \
|
||||
find . -type f -name '*.js' -not -path './.*' -exec $(INSTALL_BIN) {} $(1)/usr/lib/dmf_handlers/{} \; )
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
|
|
|
|||
BIN
dm-framework/datamodels/src/._qjs-handlers-validate.js
Executable file
BIN
dm-framework/datamodels/src/._qjs-handlers-validate.js
Executable file
Binary file not shown.
|
|
@ -1,6 +1,5 @@
|
|||
/* eslint-disable no-underscore-dangle */
|
||||
/*
|
||||
* Copyright (c) 2023 Genexis B.V. All rights reserved.
|
||||
* Copyright (c) 2025 Genexis B.V. All rights reserved.
|
||||
*
|
||||
* This Software and its content are protected by the Dutch Copyright Act
|
||||
* ('Auteurswet'). All and any copying and distribution of the software
|
||||
|
|
@ -14,15 +13,12 @@ import {
|
|||
getUciOption, getUciByType, setUci, addUci, delUci, delUciOption,
|
||||
} from '../uci.js';
|
||||
import * as dm from '../dm_consts.js';
|
||||
import { getBridgeInterfaceName } from './bridge.js';
|
||||
|
||||
function clearUnusedDevice(oldPorts, newPorts, devices) {
|
||||
oldPorts?.forEach((port) => {
|
||||
if (port.includes('.') && !newPorts?.includes(port)) {
|
||||
const dev = devices?.find((x) => x.name === port);
|
||||
if (dev) {
|
||||
delUci('network', dev['.name']);
|
||||
}
|
||||
dev?.delUci('network', dev['.name']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -41,7 +37,11 @@ function applyBridge(bri, ports, VLANs, VLANPorts) {
|
|||
continue;
|
||||
}
|
||||
|
||||
let ifname = _dm_get(`${port.LowerLayers}.Name`);
|
||||
let ifname = _dm_linker_value(port.LowerLayers);
|
||||
if (!ifname) {
|
||||
_log_error(`ifname not found for port: ${port.LowerLayers}`);
|
||||
continue;
|
||||
}
|
||||
// check vlan
|
||||
const portPath = `Device.Bridging.Bridge.${bri['.index']}.Port.${port['.index']}`;
|
||||
const vp = VLANPorts?.find((x) => x.Port === portPath);
|
||||
|
|
@ -103,25 +103,6 @@ function applyBridge(bri, ports, VLANs, VLANPorts) {
|
|||
if (ifnames.length > 0) {
|
||||
setUci('network', bri._key, { ports: ifnames });
|
||||
}
|
||||
|
||||
// apply for the WiFi.SSID port
|
||||
const ssids = _dm_get('Device.WiFi.SSID.');
|
||||
if (ssids && ssids.length > 0) {
|
||||
const networkName = getBridgeInterfaceName(bri['.index']);
|
||||
if (!networkName) {
|
||||
_log_error('network name is not found for bridge');
|
||||
return;
|
||||
}
|
||||
const ssidPorts = ports?.filter((x) => x.LowerLayers.includes('WiFi.SSID.'));
|
||||
ssids?.forEach((ssid) => {
|
||||
const path = `Device.WiFi.SSID.${ssid['.index']}`;
|
||||
if (ssidPorts?.find((p) => p.Enable && p.LowerLayers === path)) {
|
||||
setUci('wireless', ssid._key, { network: networkName });
|
||||
} else if (ssid.network === networkName) {
|
||||
delUciOption('wireless', ssid._key, 'network');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function applyDeviceBridgingBridgePort(ports, bri) {
|
||||
|
|
@ -148,8 +129,7 @@ export function initDeviceBridgingBridge(bri) {
|
|||
name: bri.Name,
|
||||
enabled: '0',
|
||||
});
|
||||
// create empty interface for the bridge, otherwise the bridge will not be up.
|
||||
// the empty interface will be deleted if there is a real interface is created later.
|
||||
// create empty interface for the bridge
|
||||
addUci('network', 'interface', `itf_${bri._key}`, {
|
||||
device: bri.Name,
|
||||
layer2_bridge: '1',
|
||||
|
|
|
|||
|
|
@ -93,50 +93,6 @@ function importBridge(dev, devices, bridges) {
|
|||
}
|
||||
}
|
||||
|
||||
// // wait until wifi config is available
|
||||
// const hasWifi = runCommand('db get hw.WIFI.start') !== undefined;
|
||||
// if (hasWifi) {
|
||||
// _log_info('waiting for wifi config');
|
||||
// const hasWifiConfig = waitUntilFileExists('/etc/config/wireless', 10000);
|
||||
|
||||
// _log_info(`wifi config found : ${hasWifiConfig}`);
|
||||
|
||||
// let wifiIfaces = [];
|
||||
// const startTime = Date.now();
|
||||
// while ((!wifiIfaces || wifiIfaces.length === 0) && (Date.now() - startTime < 30000)) {
|
||||
// wifiIfaces = getUciByType('wireless', 'wifi-iface');
|
||||
// if (!wifiIfaces || wifiIfaces.length === 0) {
|
||||
// _log_info('no wifi ifaces found, waiting for 1 second');
|
||||
// os.sleep(1000);
|
||||
// }
|
||||
// }
|
||||
|
||||
// _log_info(`wifiIfaces: \n${JSON.stringify(wifiIfaces, null, 2)}`);
|
||||
// wifiIfaces = wifiIfaces?.filter((x) => !(x.multi_ap === '1' || x.type === 'backhaul'));
|
||||
// if (wifiIfaces && wifiIfaces.length > 0) {
|
||||
// wifiIfaces?.forEach((x, i) => {
|
||||
// x.index = i + 1;
|
||||
// });
|
||||
// const itfSect = getUciByType('network', 'interface', { match: { device: dev.name } });
|
||||
// if (itfSect && itfSect.length > 0) {
|
||||
// const itfName = itfSect[0]['.name'];
|
||||
// wifiIfaces = wifiIfaces.filter((x) => x.network === itfName);
|
||||
// wifiIfaces.forEach((x) => {
|
||||
// briPorts.push({
|
||||
// Enable: 1,
|
||||
// Name: x.ifname ? x.ifname : x['.name'],
|
||||
// Alias: `cpe-${x['.name']}`,
|
||||
// TPID: 37120,
|
||||
// PVID: 1,
|
||||
// Type: 'CustomerVLANPort',
|
||||
// LowerLayers: `Device.WiFi.SSID.${x.index}`,
|
||||
// _key: x['.name'],
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if (briPorts.length > 1) {
|
||||
const indexes = Array.from({ length: briPorts.length - 1 }, (v, i) => i + 2);
|
||||
briPorts[0].LowerLayers = indexes.map((i) => `Device.Bridging.Bridge.${bridges.length}.Port.${i}`).join(',');
|
||||
|
|
@ -154,5 +110,3 @@ export function importDeviceBridgingBridge() {
|
|||
|
||||
return bridges;
|
||||
}
|
||||
|
||||
export default importDeviceBridgingBridge;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
import * as std from 'std';
|
||||
import { getUciByType } from '../uci.js';
|
||||
import { findPathInLowerlayer } from '../utils.js';
|
||||
import * as uci2 from '../uci2.js';
|
||||
|
||||
function setMgmtPortLowerLayers(bri) {
|
||||
if (!bri) {
|
||||
|
|
@ -66,7 +66,6 @@ export function getDeviceBridgingBridgePortStatus(bri, port) {
|
|||
}
|
||||
|
||||
export function infoDeviceBridgingBridgePort(path, port) {
|
||||
_log_info(`infoDeviceBridgingBridgePort: ${path} =>:\n ${JSON.stringify(port, null, 2)}`);
|
||||
const mgmtPort = _dm_get(`${path}.ManagementPort`);
|
||||
if (typeof mgmtPort === 'undefined' || mgmtPort) {
|
||||
return;
|
||||
|
|
@ -80,7 +79,6 @@ export function infoDeviceBridgingBridgePort(path, port) {
|
|||
}
|
||||
|
||||
export function getDeviceBridgingBridgePortStatsBytesSent(bri, port) {
|
||||
_log_info(`getDeviceBridgingBridgePortStatsBytesSent: ${port.ifname}`);
|
||||
return std.loadFile(`/sys/class/net/${port.ifname}/statistics/tx_bytes`)?.trim();
|
||||
}
|
||||
|
||||
|
|
@ -140,38 +138,42 @@ export function getDeviceBridgingBridgePortStatsUnknownProtoPacketsReceived(bri,
|
|||
return std.loadFile(`/sys/class/net/${port.ifname}/statistics/rx_unknown_packets`)?.trim();
|
||||
}
|
||||
|
||||
export function getBridgeInterfaceName(briIndex) {
|
||||
const insts = _dm_instances('Device.IP.Interface.');
|
||||
const inst = insts.find((x) => findPathInLowerlayer(x, `Device.Bridging.Bridge.${briIndex}.Port.1`, 'Bridging.Bridge.'));
|
||||
if (inst) {
|
||||
return _dm_get(`${inst}._key`);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getDeviceBridgingBridgePort(bri) {
|
||||
let networkName = bri.Name;
|
||||
if (bri.Name.startsWith('br-')) {
|
||||
networkName = bri.Name.slice(3);
|
||||
}
|
||||
// add Port for WiFi SSIDs
|
||||
const wifiIfaces = getUciByType('wireless', 'wifi-iface', { match: { multi_ap: '1' } });
|
||||
|
||||
const wifiIfaces = getUciByType('wireless', 'wifi-iface', { match: { multi_ap: '2' } });
|
||||
wifiIfaces?.forEach((x, i) => {
|
||||
x.index = i + 1;
|
||||
const ssid = uci2.getUciByType('dmmap_wireless', 'ssid',
|
||||
{ match: { device: x.device, ssid: x.ssid}, confdir: '/etc/bbfdm/dmmap'});
|
||||
x.ssidPath = _dm_linker_path("Device.WiFi.SSID.", "Name", ssid[0]?.name) ?? '';
|
||||
});
|
||||
|
||||
_log_info(`getDeviceBridgingBridgePort: ${networkName} =>:\n ${JSON.stringify(wifiIfaces, null, 2)}`);
|
||||
return wifiIfaces?.filter((x) => x.network === networkName);
|
||||
}
|
||||
|
||||
export function getDeviceBridgingBridgePortLowerLayers(bri, port) {
|
||||
return `Device.WiFi.SSID.${port.index}`;
|
||||
return port.ssidPath;
|
||||
}
|
||||
|
||||
export function getDeviceBridgingBridgePortManagementPort() {
|
||||
return '0';
|
||||
}
|
||||
|
||||
export function getDeviceBridgingBridgePortPVID() {
|
||||
return '1';
|
||||
}
|
||||
|
||||
export function getDeviceBridgingBridgePortTPID() {
|
||||
return '37120';
|
||||
}
|
||||
|
||||
export function getDeviceBridgingBridgePortType() {
|
||||
return 'CustomerVLANPort';
|
||||
}
|
||||
|
||||
export function getDeviceBridgingBridgePortName(bri, port) {
|
||||
return port.ifname;
|
||||
}
|
||||
|
|
|
|||
126
dm-framework/datamodels/src/dm-files/uci2.js
Executable file
126
dm-framework/datamodels/src/dm-files/uci2.js
Executable file
|
|
@ -0,0 +1,126 @@
|
|||
|
||||
/* eslint-disable no-undef */
|
||||
/*
|
||||
* Wrapper around the native QuickJS C binding `_uci_call` which speaks to
|
||||
* libuci directly (see qjs_uci_api.c). The exported helpers mimic the public
|
||||
* API of the original uci.js module so that existing call-sites can switch to
|
||||
* this implementation by simply importing uci2.js.
|
||||
*/
|
||||
|
||||
export function uciBool(val) {
|
||||
// by default enable is true if it is not defined
|
||||
return (val === undefined || val === '1' || val === 'true' || val === 'enable' || val === 'yes');
|
||||
}
|
||||
|
||||
function callUci(method, args) {
|
||||
const [ret, res] = _uci_call(method, args);
|
||||
if (ret !== 0) {
|
||||
// Returning undefined on error keeps behaviour consistent with the
|
||||
// original helpers which silently return undefined.
|
||||
return [ret, undefined];
|
||||
}
|
||||
return [ret, res];
|
||||
}
|
||||
|
||||
export function getUci(args) {
|
||||
const [, res] = callUci('get', args);
|
||||
if (res) {
|
||||
if (res.values) {
|
||||
if (!args.section) {
|
||||
return Object.values(res.values);
|
||||
}
|
||||
return res.values;
|
||||
}
|
||||
if (res.value !== undefined) {
|
||||
return res.value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getUciOption(config, section, option, extraArgs) {
|
||||
let args = { config, section, option };
|
||||
if (extraArgs) {
|
||||
args = { ...args, ...extraArgs };
|
||||
}
|
||||
return getUci(args);
|
||||
}
|
||||
|
||||
export function getUciByType(config, type, extraArgs) {
|
||||
let args = { config, type };
|
||||
if (extraArgs) {
|
||||
args = { ...args, ...extraArgs };
|
||||
}
|
||||
return getUci(args);
|
||||
}
|
||||
|
||||
export function getUciSection(config, section, extraArgs) {
|
||||
let args = { config, section };
|
||||
if (extraArgs) {
|
||||
args = { ...args, ...extraArgs };
|
||||
}
|
||||
return getUci(args);
|
||||
}
|
||||
|
||||
export function setUci(cfg, section, values, type, match, extraArgs) {
|
||||
let args = { config: cfg, section };
|
||||
if (type) args.type = type;
|
||||
if (values) args.values = values;
|
||||
if (match) args.match = match;
|
||||
if (extraArgs) args = { ...args, ...extraArgs };
|
||||
|
||||
const [ret] = callUci('set', args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function addUci(cfg, type, name, values, extraArgs) {
|
||||
let args = { config: cfg, type };
|
||||
if (name) args.name = name;
|
||||
if (values) args.values = values;
|
||||
if (extraArgs) args = { ...args, ...extraArgs };
|
||||
|
||||
const [, res] = callUci('add', args);
|
||||
return res || undefined;
|
||||
}
|
||||
|
||||
export function delUci(cfg, section, type, option, options, match, extraArgs) {
|
||||
let args = { config: cfg };
|
||||
if (section) args.section = section;
|
||||
if (type) args.type = type;
|
||||
if (option) args.option = option;
|
||||
if (options) args.options = options;
|
||||
if (match) args.match = match;
|
||||
if (extraArgs) args = { ...args, ...extraArgs };
|
||||
|
||||
const [, res] = callUci('delete', args);
|
||||
return res || undefined;
|
||||
}
|
||||
|
||||
export function delUciOption(config, section, option, match, extraArgs) {
|
||||
let args = { config, section, option };
|
||||
if (match) args.match = match;
|
||||
if (extraArgs) args = { ...args, ...extraArgs };
|
||||
const [, res] = callUci('delete', args);
|
||||
return res || undefined;
|
||||
}
|
||||
|
||||
export function uciChanges(cfg, extraArgs) {
|
||||
let args = { config: cfg };
|
||||
if (extraArgs) args = { ...args, ...extraArgs };
|
||||
const [, res] = callUci('changes', args);
|
||||
return res && res.changes ? res.changes : undefined;
|
||||
}
|
||||
|
||||
export function commitUci(cfg, extraArgs) {
|
||||
let args = { config: cfg };
|
||||
if (extraArgs) args = { ...args, ...extraArgs };
|
||||
const [ret] = callUci('commit', args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function revertUci(cfg, extraArgs) {
|
||||
let args = { config: cfg };
|
||||
if (extraArgs) args = { ...args, ...extraArgs };
|
||||
const [ret] = callUci('revert', args);
|
||||
return ret;
|
||||
}
|
||||
BIN
dm-framework/datamodels/src/scripts/._json2code.js
Executable file
BIN
dm-framework/datamodels/src/scripts/._json2code.js
Executable file
Binary file not shown.
BIN
dm-framework/datamodels/src/scripts/._qjs-handlers-validate.js
Executable file
BIN
dm-framework/datamodels/src/scripts/._qjs-handlers-validate.js
Executable file
Binary file not shown.
|
|
@ -17,7 +17,16 @@ const path = require('path');
|
|||
const fsSync = require('fs').promises;
|
||||
const sqlite3 = require('better-sqlite3');
|
||||
|
||||
const folderPath = './dm-files';
|
||||
// ------------------------------------------------------------
|
||||
// File path constants
|
||||
// ------------------------------------------------------------
|
||||
const DM_FILES_PATH = 'dm-files';
|
||||
const DM_HEADER_FILE = 'dm.h';
|
||||
const DM_CONSTS_JS_FILE = `${DM_FILES_PATH}/dm_consts.js`;
|
||||
const DM_C_FILE = `dm.c`;
|
||||
const DEFAULT_DB_FILE = `default.db`;
|
||||
const EXPORTS_JS_FILE = `exports.js`;
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Vendor prefix handling
|
||||
// ------------------------------------------------------------
|
||||
|
|
@ -825,7 +834,7 @@ async function exportJSNodeID(file, obj) {
|
|||
}
|
||||
|
||||
async function exportDMHeaderFile() {
|
||||
const headerFile = 'dm.h';
|
||||
const headerFile = DM_HEADER_FILE;
|
||||
nodeID = 0;
|
||||
|
||||
await fsSync.writeFile(headerFile, license);
|
||||
|
|
@ -843,7 +852,7 @@ async function exportDMHeaderFile() {
|
|||
}
|
||||
|
||||
async function exportJSHeaderFile() {
|
||||
const file = './dm-files/dm_consts.js';
|
||||
const file = DM_CONSTS_JS_FILE;
|
||||
nodeID = 0;
|
||||
|
||||
await fsSync.writeFile(file, license);
|
||||
|
|
@ -854,7 +863,7 @@ async function exportJSHeaderFile() {
|
|||
}
|
||||
|
||||
async function exportDMCFile() {
|
||||
const cfile = 'dm.c';
|
||||
const cfile = DM_C_FILE;
|
||||
await fsSync.writeFile(cfile, license);
|
||||
await fsSync.appendFile(cfile, head);
|
||||
await dumpNodeDeclear(cfile, rootObj);
|
||||
|
|
@ -1053,7 +1062,7 @@ function createDynamicTables(obj, db) {
|
|||
}
|
||||
|
||||
function createSqliteDB() {
|
||||
const dbFile = 'default.db';
|
||||
const dbFile = DEFAULT_DB_FILE;
|
||||
if (fs.existsSync(dbFile)) {
|
||||
fs.unlinkSync(dbFile);
|
||||
}
|
||||
|
|
@ -1156,12 +1165,12 @@ function createExportFile(directory) {
|
|||
});
|
||||
|
||||
const headerComment = `// This file is auto-generated. Do not modify it directly!\n\n`;
|
||||
fs.writeFileSync(path.join(directory, 'exports.js'), headerComment + exportStatements);
|
||||
fs.writeFileSync(path.join(directory, EXPORTS_JS_FILE), headerComment + exportStatements);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const objects = combineJSONFiles('./dm-files');
|
||||
const objects = combineJSONFiles(DM_FILES_PATH);
|
||||
// Resolve any {BBF_VENDOR_PREFIX} placeholders BEFORE running other helpers
|
||||
objects.forEach((o) => deepReplaceVendorPrefix(o));
|
||||
convertPathRef(objects);
|
||||
|
|
@ -1200,7 +1209,7 @@ function createExportFile(directory) {
|
|||
await exportDMCFile();
|
||||
await exportDMHeaderFile();
|
||||
await exportJSHeaderFile();
|
||||
createExportFile('./dm-files');
|
||||
createExportFile(DM_FILES_PATH);
|
||||
createSqliteDB();
|
||||
console.log('done.');
|
||||
} catch (error) {
|
||||
88
dm-framework/datamodels/src/scripts/qjs-handlers-validate.js
Normal file
88
dm-framework/datamodels/src/scripts/qjs-handlers-validate.js
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
// This script is used to load and validate the js handlers code in dm-file.
|
||||
(function () {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
// Root directory (dm-files) relative to this script
|
||||
const dmFilesRoot = path.resolve(__dirname, '../dm-files');
|
||||
|
||||
/**
|
||||
* Recursively walk a directory and collect all *.js files that do not start with a dot.
|
||||
* @param {string} dir - directory to walk
|
||||
* @param {string[]} out - accumulator for file paths
|
||||
*/
|
||||
function collectJsFiles(dir, out) {
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
// Skip hidden files/directories (starting with ".")
|
||||
if (entry.name.startsWith('.')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
collectJsFiles(fullPath, out);
|
||||
} else if (entry.isFile() && fullPath.endsWith('.js')) {
|
||||
out.push(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a single JavaScript file with QuickJS (qjs).
|
||||
* Exits the process with -1 on failure.
|
||||
* @param {string} filePath - absolute path of the JS file
|
||||
*/
|
||||
function validateWithQjs(filePath) {
|
||||
// Extract directory and filename for proper working directory
|
||||
const fileDir = path.dirname(filePath);
|
||||
const fileName = path.basename(filePath);
|
||||
|
||||
// Capture stdout/stderr so we can print them on failure
|
||||
// Set the working directory to the file's directory
|
||||
const result = spawnSync('qjs', [fileName], {
|
||||
encoding: 'utf8',
|
||||
cwd: fileDir
|
||||
});
|
||||
|
||||
if (result.status === 0) {
|
||||
return; // Validated successfully
|
||||
}
|
||||
|
||||
// Show QuickJS output so user sees error details
|
||||
console.error(`\n===== QuickJS validation failed: ${filePath} =====`);
|
||||
if (result.stdout) {
|
||||
console.error(result.stdout.trim());
|
||||
}
|
||||
if (result.stderr) {
|
||||
console.error(result.stderr.trim());
|
||||
}
|
||||
console.error('===============================================');
|
||||
process.exit(-1);
|
||||
}
|
||||
|
||||
function main() {
|
||||
if (!fs.existsSync(dmFilesRoot)) {
|
||||
console.error(`dm-files directory not found at: ${dmFilesRoot}`);
|
||||
process.exit(-1);
|
||||
}
|
||||
|
||||
const jsFiles = [];
|
||||
collectJsFiles(dmFilesRoot, jsFiles);
|
||||
|
||||
if (jsFiles.length === 0) {
|
||||
console.log('No JavaScript files found to validate.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Validating ${jsFiles.length} JavaScript file(s) with QuickJS...`);
|
||||
jsFiles.forEach(validateWithQjs);
|
||||
console.log('All files validated successfully.');
|
||||
}
|
||||
|
||||
// Execute when run directly (not required when imported)
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
})();
|
||||
|
|
@ -77,6 +77,7 @@ handle_ebtables_rule() {
|
|||
}
|
||||
|
||||
start_service() {
|
||||
ubus -t 30 wait_for network.device uci
|
||||
config_load bridging
|
||||
config_foreach handle_ebtables_chain chain
|
||||
config_foreach handle_ebtables_rule rule
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=99
|
||||
STOP=10
|
||||
|
||||
USE_PROCD=1
|
||||
PROG=/usr/sbin/dm-agent
|
||||
|
||||
start_service() {
|
||||
logger -t dmapi "waiting network ubus"
|
||||
ubus -t 30 wait_for network.device uci
|
||||
[ "$?" -eq 0 ] && {
|
||||
logger -t dmapi "waiting for network ubus done"
|
||||
} || {
|
||||
logger -t dmapi "failed to wait for network ubus"
|
||||
}
|
||||
|
||||
[ -f "/etc/config/wireless" ] && {
|
||||
logger -t dmapi "waiting wifi ubus"
|
||||
ubus -t 30 wait_for wifi
|
||||
[ "$?" -eq 0 ] && {
|
||||
logger -t dmapi "waiting for wifi ubus done"
|
||||
} || {
|
||||
logger -t dmapi "failed to wait for wifi ubus"
|
||||
}
|
||||
}
|
||||
|
||||
procd_open_instance dm-agent
|
||||
procd_set_param command ${PROG}
|
||||
procd_set_param respawn
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ SRCS = \
|
|||
quickjs/qjs.c \
|
||||
quickjs/qjs_log.c \
|
||||
quickjs/qjs_dm_api.c \
|
||||
quickjs/qjs_uci_api.c \
|
||||
quickjs/qjs_ubus_api.c
|
||||
|
||||
OBJS = $(SRCS:.c=.o)
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ int dmapi_init(const char *service_name)
|
|||
global_ctx.session_state = SESSION_STATE_IDLE;
|
||||
strncpy(global_ctx.service_name, service_name, sizeof(global_ctx.service_name));
|
||||
|
||||
dmlog_init("dmapi", 0);
|
||||
dmlog_init("dmf", 0);
|
||||
dmlog_debug("dmapi_init starting, service_name: %s", global_ctx.service_name);
|
||||
|
||||
if (qjs_init() != 0) {
|
||||
|
|
|
|||
|
|
@ -514,3 +514,105 @@ int dm_resolve_linker(const char *object_path, char **value) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the object path for a given linker value.
|
||||
*
|
||||
* This helper reconstructs the deterministic linker string of the form
|
||||
* "<base_path>[<key_name>==<key_value>]."
|
||||
* that was generated by dm_refresh_linker_nodes() and uses its hash to
|
||||
* look up the real object path from the reference database.
|
||||
*
|
||||
* If no matching entry exists in the database the function will fall
|
||||
* back to returning the linker string itself, exactly mimicking the
|
||||
* behaviour of legacy _bbfdm_get_references().
|
||||
*
|
||||
* The caller is responsible for freeing the returned string.
|
||||
*
|
||||
* @param base_path Base object path ending with a dot (e.g. "Device.WiFi.SSID.")
|
||||
* @param key_name Parameter name that acts as linker key (e.g. "Name")
|
||||
* @param key_value Desired value of the linker key
|
||||
* @param object_path [out] malloc-allocated string holding either the
|
||||
* resolved object path (e.g. "Device.WiFi.SSID.4")
|
||||
* (NULL when no match is found)
|
||||
* @return 0 when resolved, -1 if no match/error
|
||||
*/
|
||||
int dm_resolve_linker_path(const char *base_path,
|
||||
const char *key_name,
|
||||
const char *key_value,
|
||||
char **object_path) {
|
||||
if (!base_path || !key_name || !key_value || !object_path) {
|
||||
dmlog_error("Invalid arguments to dm_resolve_linker_path");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strlen(base_path) == 0 || strlen(key_name) == 0 || strlen(key_value) == 0) {
|
||||
dmlog_error("base_path, key_name and key_value must not be empty");
|
||||
return -1;
|
||||
}
|
||||
|
||||
*object_path = NULL;
|
||||
|
||||
/* Compose the linker string that was used as basis for the reference hash */
|
||||
size_t linker_len = strlen(base_path) + strlen(key_name) + strlen(key_value) + 6; /* "[==]." */
|
||||
char *linker_string = malloc(linker_len);
|
||||
if (!linker_string) {
|
||||
return -1;
|
||||
}
|
||||
snprintf(linker_string, linker_len, "%s[%s==%s].", base_path, key_name, key_value);
|
||||
|
||||
/* Calculate hash for use as option name inside reference_path section */
|
||||
char *hash_path = calculate_hash(linker_string);
|
||||
if (!hash_path) {
|
||||
free(linker_string);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Open UCI context pointing to runtime reference DB (/var/state) */
|
||||
struct uci_context *ctx = uci_alloc_context();
|
||||
if (!ctx) {
|
||||
dmlog_error("Failed to allocate UCI context");
|
||||
free(linker_string);
|
||||
free(hash_path);
|
||||
return -1;
|
||||
}
|
||||
uci_set_confdir(ctx, "/var/state");
|
||||
|
||||
struct uci_package *pkg = NULL;
|
||||
if (uci_load(ctx, UCI_PACKAGE, &pkg) != UCI_OK || !pkg) {
|
||||
dmlog_error("Failed to load UCI package %s", UCI_PACKAGE);
|
||||
uci_free_context(ctx);
|
||||
free(linker_string);
|
||||
free(hash_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct uci_section *ref_path_section = uci_lookup_section(ctx, pkg, "reference_path");
|
||||
if (!ref_path_section) {
|
||||
dmlog_error("reference_path section not found in %s", UCI_PACKAGE);
|
||||
uci_free_context(ctx);
|
||||
free(linker_string);
|
||||
free(hash_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *uci_path = uci_lookup_option_string(ctx, ref_path_section, hash_path);
|
||||
|
||||
int ret = -1; /* default: not resolved */
|
||||
|
||||
if (uci_path) {
|
||||
/* Matching object path found */
|
||||
*object_path = strdup(uci_path);
|
||||
if (*object_path)
|
||||
ret = 0;
|
||||
} else {
|
||||
/* No match. Leave *object_path NULL and return error */
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
uci_free_context(ctx);
|
||||
free(linker_string);
|
||||
free(hash_path);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -74,6 +74,14 @@ int object_has_linker_param(dm_node_id_t object_id);
|
|||
*/
|
||||
int dm_resolve_linker(const char *object_path, char **value);
|
||||
|
||||
/*
|
||||
* Resolve object path from a linker value.
|
||||
* Returns 0 and sets *object_path on success.
|
||||
* Returns -1 and leaves *object_path NULL if no matching object path exists
|
||||
* or on error.
|
||||
*/
|
||||
int dm_resolve_linker_path(const char *base_path, const char *key_name, const char *key_value, char **object_path);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@
|
|||
#include "dm_uci.h"
|
||||
#include "inode_buf.h"
|
||||
#include "qjs_api.h"
|
||||
// Forward declaration from qjs_uci_api.c
|
||||
extern int qjs_uci_api_init();
|
||||
|
||||
static JSRuntime *runtime_g;
|
||||
static JSContext *ctx_g;
|
||||
|
|
@ -184,12 +186,13 @@ static void init_qjs_c_api()
|
|||
qjs_log_api_init();
|
||||
qjs_dm_api_init();
|
||||
qjs_ubus_api_init();
|
||||
qjs_uci_api_init();
|
||||
}
|
||||
|
||||
static int import_js_handlers()
|
||||
{
|
||||
const char *str =
|
||||
"import * as dm_handlers from '/usr/lib/quickjs/dm_handlers/exports.js';\n"
|
||||
"import * as dm_handlers from '/usr/lib/dmf_handlers/exports.js';\n"
|
||||
"globalThis.dm_handlers = dm_handlers;\n";
|
||||
JSValue val = qjs_eval_buf(str, strlen(str), "<get_js_init>", JS_EVAL_TYPE_MODULE);
|
||||
if (JS_IsException(val)) {
|
||||
|
|
|
|||
|
|
@ -534,6 +534,36 @@ static JSValue _dm_resolve(JSContext *ctx, JSValueConst this_val,
|
|||
return js_val;
|
||||
}
|
||||
|
||||
static JSValue _dm_resolve_path(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv)
|
||||
{
|
||||
if (argc < 3 || !JS_IsString(argv[0]) || !JS_IsString(argv[1]) || !JS_IsString(argv[2])) {
|
||||
dmlog_info("_dm_resolve_path, invalid argument");
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
const char *base_path = JS_ToCString(ctx, argv[0]);
|
||||
const char *key_name = JS_ToCString(ctx, argv[1]);
|
||||
const char *key_value = JS_ToCString(ctx, argv[2]);
|
||||
|
||||
char *obj_path = NULL;
|
||||
if (dm_resolve_linker_path(base_path, key_name, key_value, &obj_path) != 0 || obj_path == NULL) {
|
||||
JS_FreeCString(ctx, base_path);
|
||||
JS_FreeCString(ctx, key_name);
|
||||
JS_FreeCString(ctx, key_value);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
JSValue js_val = JS_NewString(ctx, obj_path);
|
||||
|
||||
free(obj_path);
|
||||
JS_FreeCString(ctx, base_path);
|
||||
JS_FreeCString(ctx, key_name);
|
||||
JS_FreeCString(ctx, key_value);
|
||||
|
||||
return js_val;
|
||||
}
|
||||
|
||||
int qjs_dm_api_init()
|
||||
{
|
||||
qjs_register_c_api("_dm_get", _dm_get, 1);
|
||||
|
|
@ -545,5 +575,6 @@ int qjs_dm_api_init()
|
|||
qjs_register_c_api("_dm_update", _dm_update, 1);
|
||||
qjs_register_c_api("_dm_apply", _dm_apply, 1);
|
||||
qjs_register_c_api("_dm_linker_value", _dm_resolve, 1);
|
||||
qjs_register_c_api("_dm_linker_path", _dm_resolve_path, 3);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
430
dm-framework/dm-api/src/quickjs/qjs_uci_api.c
Executable file
430
dm-framework/dm-api/src/quickjs/qjs_uci_api.c
Executable file
|
|
@ -0,0 +1,430 @@
|
|||
|
||||
#include <quickjs/quickjs-libc.h>
|
||||
#include <quickjs/quickjs.h>
|
||||
#include <uci.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "dm_log.h"
|
||||
#include "qjs.h"
|
||||
|
||||
/*
|
||||
* Simple libuci based implementation to offer a subset of the ubus–uci RPC
|
||||
* functions directly to QuickJS without going through ubus. The goal is to
|
||||
* be feature-parity with what dm-files/uci.js currently needs: get, set, add,
|
||||
* delete, commit, revert, changes. All logic is consolidated in the single
|
||||
* entry point _uci_call(<method>, <argsObject>). The function returns an
|
||||
* array [ret, res] where ret == 0 upon success and res carries any return
|
||||
* payload – this mirrors the behaviour of _ubus_call so the existing JS glue
|
||||
* can stay unchanged apart from calling _uci_call instead.
|
||||
*
|
||||
* NOTE: This is intentionally a *minimal* implementation – only the argument
|
||||
* variations that are used in our JavaScript helper (uci2.js) are handled:
|
||||
* – get : { config, section?, option? }
|
||||
* – set : { config, section, values }
|
||||
* – add : { config, type, name?, values? }
|
||||
* – delete : { config, section, option? }
|
||||
* – commit / revert / changes : { config }
|
||||
* Unsupported combinations or failures result in ret == -1.
|
||||
*/
|
||||
|
||||
static const char *js_obj_get_str(JSContext *ctx, JSValue obj, const char *name)
|
||||
{
|
||||
JSValue val = JS_GetPropertyStr(ctx, obj, name);
|
||||
if (JS_IsUndefined(val)) {
|
||||
return NULL;
|
||||
}
|
||||
const char *str = JS_ToCString(ctx, val);
|
||||
JS_FreeValue(ctx, val);
|
||||
return str; /* caller must free via JS_FreeCString */
|
||||
}
|
||||
|
||||
static JSValue create_values_object(JSContext *ctx, struct uci_section *s)
|
||||
{
|
||||
JSValue js_obj = JS_NewObject(ctx);
|
||||
if (!s)
|
||||
return js_obj;
|
||||
|
||||
/* meta fields similar to ubus */
|
||||
JS_SetPropertyStr(ctx, js_obj, ".anonymous", JS_NewBool(ctx, s->anonymous));
|
||||
JS_SetPropertyStr(ctx, js_obj, ".type", JS_NewString(ctx, s->type));
|
||||
JS_SetPropertyStr(ctx, js_obj, ".name", JS_NewString(ctx, s->e.name));
|
||||
|
||||
struct uci_element *oe;
|
||||
uci_foreach_element(&s->options, oe) {
|
||||
struct uci_option *o = uci_to_option(oe);
|
||||
if (!o || o->type != UCI_TYPE_STRING)
|
||||
continue; /* ignore lists for now */
|
||||
JS_SetPropertyStr(ctx, js_obj, o->e.name, JS_NewString(ctx, o->v.string));
|
||||
}
|
||||
return js_obj;
|
||||
}
|
||||
|
||||
static int js_obj_foreach(JSContext *ctx, JSValue obj,
|
||||
int (*cb)(JSContext *, const char *, JSValue, void *),
|
||||
void *opaque)
|
||||
{
|
||||
JSPropertyEnum *props;
|
||||
uint32_t len;
|
||||
int res = 0;
|
||||
if (JS_GetOwnPropertyNames(ctx, &props, &len, obj, JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0)
|
||||
return -1;
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
const char *key = JS_AtomToCString(ctx, props[i].atom);
|
||||
JSValue val = JS_GetPropertyStr(ctx, obj, key);
|
||||
if (cb(ctx, key, val, opaque) != 0) {
|
||||
res = -1;
|
||||
JS_FreeCString(ctx, key);
|
||||
JS_FreeValue(ctx, val);
|
||||
break;
|
||||
}
|
||||
JS_FreeCString(ctx, key);
|
||||
JS_FreeValue(ctx, val);
|
||||
}
|
||||
js_free(ctx, props);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* callback context for set/add */
|
||||
struct set_ctx {
|
||||
struct uci_context *uci;
|
||||
const char *config;
|
||||
const char *section;
|
||||
};
|
||||
|
||||
static int set_option_cb(JSContext *ctx, const char *key, JSValue val, void *opaque)
|
||||
{
|
||||
struct set_ctx *sctx = (struct set_ctx *)opaque;
|
||||
const char *vstr = JS_ToCString(ctx, val);
|
||||
if (!vstr)
|
||||
return -1;
|
||||
|
||||
char path[256];
|
||||
snprintf(path, sizeof(path), "%s.%s.%s", sctx->config, sctx->section, key);
|
||||
|
||||
struct uci_ptr ptr = {};
|
||||
if (uci_lookup_ptr(sctx->uci, &ptr, path, true) != UCI_OK) {
|
||||
JS_FreeCString(ctx, vstr);
|
||||
return -1;
|
||||
}
|
||||
ptr.value = vstr;
|
||||
if (uci_set(sctx->uci, &ptr) != UCI_OK) {
|
||||
JS_FreeCString(ctx, vstr);
|
||||
return -1;
|
||||
}
|
||||
JS_FreeCString(ctx, vstr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int section_matches(JSContext *ctx, JSValue match_obj, struct uci_section *s)
|
||||
{
|
||||
/* No match criteria -> everything matches */
|
||||
if (JS_IsUndefined(match_obj) || JS_IsNull(match_obj) || !JS_IsObject(match_obj))
|
||||
return 1;
|
||||
|
||||
JSPropertyEnum *props;
|
||||
uint32_t len;
|
||||
if (JS_GetOwnPropertyNames(ctx, &props, &len, match_obj, JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0)
|
||||
return 0;
|
||||
|
||||
/* Iterate over every field in the match object and ensure the option exists
|
||||
* and contains exactly the requested value. Supports both string and list
|
||||
* option types.
|
||||
*/
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
const char *key = JS_AtomToCString(ctx, props[i].atom);
|
||||
JSValue val = JS_GetPropertyStr(ctx, match_obj, key);
|
||||
const char *val_str = JS_ToCString(ctx, val);
|
||||
|
||||
/* Retrieve the option */
|
||||
struct uci_option *opt = uci_lookup_option(s->package->ctx, s, key);
|
||||
if (!opt) {
|
||||
/* option missing -> no match */
|
||||
JS_FreeCString(ctx, key);
|
||||
JS_FreeCString(ctx, val_str);
|
||||
JS_FreeValue(ctx, val);
|
||||
js_free(ctx, props);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool this_ok = false;
|
||||
if (opt->type == UCI_TYPE_STRING) {
|
||||
this_ok = (strcmp(opt->v.string, val_str) == 0);
|
||||
} else if (opt->type == UCI_TYPE_LIST) {
|
||||
/* check if value appears in the list */
|
||||
struct uci_element *le;
|
||||
uci_foreach_element(&opt->v.list, le) {
|
||||
if (strcmp(le->name, val_str) == 0) {
|
||||
this_ok = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JS_FreeCString(ctx, key);
|
||||
JS_FreeCString(ctx, val_str);
|
||||
JS_FreeValue(ctx, val);
|
||||
|
||||
/* Early exit on first mismatch */
|
||||
if (!this_ok) {
|
||||
js_free(ctx, props);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
js_free(ctx, props);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static JSValue _uci_call(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv)
|
||||
{
|
||||
int ret = -1;
|
||||
JSValue res_val = JS_UNDEFINED;
|
||||
|
||||
if (argc < 1 || !JS_IsString(argv[0])) {
|
||||
dmlog_error("_uci_call: invalid arguments");
|
||||
goto end;
|
||||
}
|
||||
|
||||
const char *method = JS_ToCString(ctx, argv[0]);
|
||||
JSValue args_obj = (argc > 1 && JS_IsObject(argv[1])) ? argv[1] : JS_UNDEFINED;
|
||||
struct uci_context *uci = uci_alloc_context();
|
||||
if (!uci) {
|
||||
dmlog_error("uci_alloc_context failed");
|
||||
goto end_method;
|
||||
}
|
||||
|
||||
/* Optional custom configuration directory */
|
||||
const char *confdir = NULL;
|
||||
if (args_obj != JS_UNDEFINED) {
|
||||
confdir = js_obj_get_str(ctx, args_obj, "confdir"); /* explicit */
|
||||
}
|
||||
|
||||
/* Support absolute/relative file path in 'config' by splitting dir/name */
|
||||
const char *config = (args_obj != JS_UNDEFINED) ? js_obj_get_str(ctx, args_obj, "config") : NULL;
|
||||
|
||||
char cfg_name_buf[128];
|
||||
if (config && strchr(config, '/')) {
|
||||
/* treat as path */
|
||||
const char *last_slash = strrchr(config, '/');
|
||||
size_t dir_len = last_slash - config;
|
||||
if (!confdir) {
|
||||
confdir = strndup(config, dir_len);
|
||||
}
|
||||
snprintf(cfg_name_buf, sizeof(cfg_name_buf), "%s", last_slash + 1);
|
||||
/* drop possible file extension; uci expects plain filename */
|
||||
config = cfg_name_buf;
|
||||
}
|
||||
|
||||
if (confdir) {
|
||||
uci_set_confdir(uci, confdir);
|
||||
}
|
||||
|
||||
if (!config && strcmp(method, "configs") != 0) {
|
||||
dmlog_error("_uci_call: 'config' is missing for method %s", method);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
struct uci_package *pkg = NULL;
|
||||
if (config && uci_load(uci, config, &pkg) != UCI_OK) {
|
||||
dmlog_error("uci_load failed for %s", config);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (strcmp(method, "get") == 0) {
|
||||
const char *section = js_obj_get_str(ctx, args_obj, "section");
|
||||
const char *option = js_obj_get_str(ctx, args_obj, "option");
|
||||
|
||||
if (option && !section) {
|
||||
/* option without section is invalid */
|
||||
dmlog_error("_uci_call get: option requires section");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (option) {
|
||||
char path[256];
|
||||
snprintf(path, sizeof(path), "%s.%s.%s", config, section, option);
|
||||
struct uci_ptr ptr = {};
|
||||
if (uci_lookup_ptr(uci, &ptr, path, true) != UCI_OK || !ptr.o) {
|
||||
dmlog_error("uci_lookup_ptr failed for %s", path);
|
||||
goto cleanup;
|
||||
}
|
||||
res_val = JS_NewObject(ctx);
|
||||
JS_SetPropertyStr(ctx, res_val, "value", JS_NewString(ctx, ptr.o->v.string));
|
||||
ret = 0;
|
||||
} else if (section) {
|
||||
/* return all options of the section */
|
||||
struct uci_section *s = uci_lookup_section(uci, pkg, section);
|
||||
if (!s) {
|
||||
dmlog_error("section %s not found", section);
|
||||
goto cleanup;
|
||||
}
|
||||
JSValue vals = create_values_object(ctx, s);
|
||||
res_val = JS_NewObject(ctx);
|
||||
JS_SetPropertyStr(ctx, res_val, "values", vals);
|
||||
ret = 0;
|
||||
} else {
|
||||
/* package wide query, possibly filtered by type/match */
|
||||
const char *type_filter = js_obj_get_str(ctx, args_obj, "type");
|
||||
JSValue match_obj = JS_GetPropertyStr(ctx, args_obj, "match");
|
||||
|
||||
JSValue vals = JS_NewObject(ctx);
|
||||
struct uci_element *e;
|
||||
uci_foreach_element(&pkg->sections, e) {
|
||||
struct uci_section *s = uci_to_section(e);
|
||||
if (type_filter && strcmp(s->type, type_filter) != 0)
|
||||
continue;
|
||||
if (!section_matches(ctx, match_obj, s))
|
||||
continue;
|
||||
JS_SetPropertyStr(ctx, vals, s->e.name, create_values_object(ctx, s));
|
||||
}
|
||||
|
||||
res_val = JS_NewObject(ctx);
|
||||
JS_SetPropertyStr(ctx, res_val, "values", vals);
|
||||
ret = 0;
|
||||
|
||||
if (type_filter) JS_FreeCString(ctx, type_filter);
|
||||
JS_FreeValue(ctx, match_obj);
|
||||
}
|
||||
} else if (strcmp(method, "set") == 0) {
|
||||
const char *section = js_obj_get_str(ctx, args_obj, "section");
|
||||
if (!section) {
|
||||
dmlog_error("_uci_call set: section missing");
|
||||
goto cleanup;
|
||||
}
|
||||
JSValue values_obj = JS_GetPropertyStr(ctx, args_obj, "values");
|
||||
if (!JS_IsObject(values_obj)) {
|
||||
dmlog_error("_uci_call set: values not object");
|
||||
JS_FreeValue(ctx, values_obj);
|
||||
goto cleanup;
|
||||
}
|
||||
struct set_ctx sctx = { .uci = uci, .config = config, .section = section };
|
||||
if (js_obj_foreach(ctx, values_obj, set_option_cb, &sctx) != 0) {
|
||||
JS_FreeValue(ctx, values_obj);
|
||||
goto cleanup;
|
||||
}
|
||||
JS_FreeValue(ctx, values_obj);
|
||||
if (uci_save(uci, pkg) != UCI_OK) {
|
||||
dmlog_error("uci_save failed for %s", config);
|
||||
goto cleanup;
|
||||
}
|
||||
ret = 0; /* no res */
|
||||
} else if (strcmp(method, "add") == 0) {
|
||||
const char *type = js_obj_get_str(ctx, args_obj, "type");
|
||||
if (!type) {
|
||||
dmlog_error("_uci_call add: type missing");
|
||||
goto cleanup;
|
||||
}
|
||||
const char *name = js_obj_get_str(ctx, args_obj, "name");
|
||||
|
||||
struct uci_section *s;
|
||||
if (uci_add_section(uci, pkg, type, &s) != UCI_OK) {
|
||||
dmlog_error("uci_add_section failed");
|
||||
goto cleanup;
|
||||
}
|
||||
if (name) {
|
||||
/* rename newly created section to requested name */
|
||||
char tmp[256];
|
||||
snprintf(tmp, sizeof(tmp), "%s.%s", config, s->e.name);
|
||||
struct uci_ptr rptr = {};
|
||||
if (uci_lookup_ptr(uci, &rptr, tmp, true) != UCI_OK) {
|
||||
dmlog_error("rename lookup failed");
|
||||
goto cleanup;
|
||||
}
|
||||
rptr.value = name;
|
||||
if (uci_rename(uci, &rptr) != UCI_OK) {
|
||||
dmlog_error("uci_rename failed");
|
||||
goto cleanup;
|
||||
}
|
||||
/* section name changed - use new name for response */
|
||||
snprintf(tmp, sizeof(tmp), "%s", name);
|
||||
res_val = JS_NewObject(ctx);
|
||||
JS_SetPropertyStr(ctx, res_val, "section", JS_NewString(ctx, tmp));
|
||||
} else {
|
||||
res_val = JS_NewObject(ctx);
|
||||
JS_SetPropertyStr(ctx, res_val, "section", JS_NewString(ctx, s->e.name));
|
||||
}
|
||||
/* optional values */
|
||||
JSValue values_obj = JS_GetPropertyStr(ctx, args_obj, "values");
|
||||
if (JS_IsObject(values_obj)) {
|
||||
struct set_ctx sctx = { .uci = uci, .config = config, .section = s->e.name };
|
||||
js_obj_foreach(ctx, values_obj, set_option_cb, &sctx);
|
||||
}
|
||||
JS_FreeValue(ctx, values_obj);
|
||||
uci_save(uci, pkg);
|
||||
ret = 0;
|
||||
} else if (strcmp(method, "delete") == 0) {
|
||||
const char *section = js_obj_get_str(ctx, args_obj, "section");
|
||||
const char *option = js_obj_get_str(ctx, args_obj, "option");
|
||||
if (!section) {
|
||||
dmlog_error("_uci_call delete: section missing");
|
||||
goto cleanup;
|
||||
}
|
||||
char path[256];
|
||||
if (option)
|
||||
snprintf(path, sizeof(path), "%s.%s.%s", config, section, option);
|
||||
else
|
||||
snprintf(path, sizeof(path), "%s.%s", config, section);
|
||||
struct uci_ptr ptr = {};
|
||||
if (uci_lookup_ptr(uci, &ptr, path, true) != UCI_OK) {
|
||||
dmlog_error("uci_lookup_ptr failed for delete %s", path);
|
||||
goto cleanup;
|
||||
}
|
||||
if (uci_delete(uci, &ptr) != UCI_OK) {
|
||||
dmlog_error("uci_delete failed");
|
||||
goto cleanup;
|
||||
}
|
||||
uci_save(uci, pkg);
|
||||
ret = 0;
|
||||
} else if (strcmp(method, "commit") == 0) {
|
||||
if (uci_commit(uci, &pkg, false) != UCI_OK) {
|
||||
dmlog_error("uci_commit failed");
|
||||
goto cleanup;
|
||||
}
|
||||
ret = 0;
|
||||
} else if (strcmp(method, "revert") == 0) {
|
||||
struct uci_ptr rptr = {};
|
||||
char path[128];
|
||||
snprintf(path, sizeof(path), "%s", config);
|
||||
if (uci_lookup_ptr(uci, &rptr, path, true) != UCI_OK) {
|
||||
dmlog_error("revert lookup failed");
|
||||
goto cleanup;
|
||||
}
|
||||
if (uci_revert(uci, &rptr) != UCI_OK) {
|
||||
dmlog_error("uci_revert failed");
|
||||
goto cleanup;
|
||||
}
|
||||
ret = 0;
|
||||
} else if (strcmp(method, "changes") == 0) {
|
||||
/* Provide minimal stub: always empty */
|
||||
res_val = JS_NewObject(ctx);
|
||||
JS_SetPropertyStr(ctx, res_val, "changes", JS_NewArray(ctx));
|
||||
ret = 0;
|
||||
} else {
|
||||
dmlog_error("_uci_call: unsupported method %s", method);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (config && config != cfg_name_buf)
|
||||
JS_FreeCString(ctx, config);
|
||||
if (confdir)
|
||||
JS_FreeCString(ctx, confdir);
|
||||
if (uci)
|
||||
uci_free_context(uci);
|
||||
|
||||
end_method:
|
||||
JS_FreeCString(ctx, method);
|
||||
end:
|
||||
JSValue arr = JS_NewArray(ctx);
|
||||
JS_SetPropertyUint32(ctx, arr, 0, JS_NewInt32(ctx, ret));
|
||||
JS_SetPropertyUint32(ctx, arr, 1, res_val);
|
||||
return arr;
|
||||
}
|
||||
|
||||
int qjs_uci_api_init()
|
||||
{
|
||||
qjs_register_c_api("_uci_call", _uci_call, 2);
|
||||
return 0;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue