dbg vlan config

This commit is contained in:
Meng 2025-07-20 13:20:50 +02:00
parent f480b001c7
commit f4828d4c1a
21 changed files with 399 additions and 240 deletions

View file

@ -11,7 +11,7 @@ USE_LOCAL:=0
ifneq ($(USE_LOCAL),1)
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://dev.iopsys.eu/bbf/bbfdm.git
PKG_SOURCE_VERSION:=ba2812ae1f9ac216fc880096b1c65f8b63ba249c
PKG_SOURCE_VERSION:=1615b42e405faceceac825f9c0387a58b90785ae
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION)-$(PKG_SOURCE_VERSION).tar.gz
PKG_MIRROR_HASH:=skip
endif

View file

@ -63,8 +63,9 @@ endef
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/usr/lib
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_DIR) $(1)/etc/bbfdm
$(INSTALL_DIR) $(1)/usr/lib/dmf_handlers
$(INSTALL_BIN) $(PKG_BUILD_DIR)/default.db $(1)/etc/default_dm.db
$(INSTALL_BIN) $(PKG_BUILD_DIR)/default.db $(1)/etc/bbfdm/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; \

View file

@ -193,7 +193,8 @@
"db": "true",
"flags": [
"linker"
]
],
"js-value": "ifname"
},
{
"name": "LastChange",
@ -207,7 +208,8 @@
"dataType": "pathRef[]",
"pathRef": [
"Device.Bridging.Bridge.{i}.Port."
]
],
"js-value": "ssidPath"
},
{
"name": "ManagementPort",
@ -235,7 +237,8 @@
"CustomerEdgePort",
"CustomerVLANPort",
"VLANUnawarePort"
]
],
"default": "CustomerVLANPort"
},
{
"name": "PVID",

View file

@ -13,23 +13,23 @@ import {
getUciOption, getUciByType, setUci, addUci, delUci
} from '../uci.js';
import * as dm from '../dm_consts.js';
import { getBridgeDeviceType } from './common.js';
function clearUnusedDevice(oldPorts, newPorts, devices) {
oldPorts?.forEach((port) => {
oldPorts?.forEach(port => {
if (port.includes('.') && !newPorts?.includes(port)) {
const dev = devices?.find((x) => x.name === port);
dev?.delUci('network', dev['.name']);
const dev = devices?.find(x => x.name === port);
if (dev?.['.name']) delUci('network', dev['.name']);
}
});
}
function applyBridge(bri, ports, VLANs, VLANPorts) {
const ifnames = [];
const devices = getUciByType('network', 'device');
const devices = getUciByType('network', 'device')?.filter(x => x.type !== undefined);
const portsVal = getUciOption('network', bri._key, 'ports');
if (portsVal) {
delUci('network', bri._key, null, 'ports');
}
if (portsVal) delUci('network', bri._key, null, 'ports');
// get ports ethernet ifnames
for (const port of ports || []) {
@ -42,58 +42,75 @@ function applyBridge(bri, ports, VLANs, VLANPorts) {
_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);
const vp = VLANPorts?.find(x => x.Port === portPath);
if (!vp?.VLAN) {
ifnames.push(ifname);
continue;
}
// get index of the vlan
const [, indices] = _dm_node(vp.VLAN);
const vlanIdx = indices[indices.length - 1];
const vlan = VLANs?.find((x) => x['.index'] === vlanIdx);
const vlan = VLANs?.find(x => x['.index'] === vlanIdx);
if (!vlan || vlan.VLANID <= 0) {
ifnames.push(ifname);
continue;
}
const eth = ifname;
ifname = `${ifname}.${vlan.VLANID}`;
const dev = devices?.find((x) => x.name === ifname);
const dev = devices?.find(x => x.name === ifname);
let devName;
if (dev) {
devName = dev['.name'];
} else {
devName = `br_${bri['.index']}_port_${vp['.index']}`;
const options = {
addUci('network', 'device', devName, {
ifname: eth,
name: ifname,
vid: vlan.VLANID,
};
addUci(
'network',
'device',
devName,
options,
);
});
}
const uciConfigs = {};
if (!vp.Untagged) {
// Handle Type parameter - determine device type based on port Type or default behavior
let deviceType = '';
if (port.Type) {
deviceType = getBridgeDeviceType(port.Type);
if (deviceType) uciConfigs.type = deviceType;
} else if (!vp.Untagged) {
uciConfigs.type = '8021q';
}
uciConfigs.disabled = vlan.Enable && vp.Enable ? '0' : '1';
if (port.PriorityRegeneration !== '0,1,2,3,4,5,6,7') {
uciConfigs.ingress_qos_mapping = port.PriorityRegeneration.split(',').map((p, i) => `${i}:${p}`);
} else {
uciConfigs.ingress_qos_mapping = '';
deviceType = '8021q';
}
if (port.X_IOPSYS_EU_EgressPriorityRegeneration !== '0,1,2,3,4,5,6,7') {
uciConfigs.egress_qos_mapping = port.X_IOPSYS_EU_EgressPriorityRegeneration.split(',').map((p, i) => `${i}:${p}`);
} else {
uciConfigs.egress_qos_mapping = '';
// Handle TPID parameter
if (port.TPID) {
// If TPID is explicitly set, use it and derive device type if needed
uciConfigs.tpid = port.TPID;
// Set device type based on TPID if not already set
if (!deviceType) {
if (port.TPID === '33024') {
uciConfigs.type = '8021q';
} else if (port.TPID === '34984') {
uciConfigs.type = '8021ad';
}
}
}
uciConfigs.disabled = vlan.Enable && vp.Enable ? '0' : '1';
uciConfigs.ingress_qos_mapping = port.PriorityRegeneration !== '0,1,2,3,4,5,6,7'
? port.PriorityRegeneration.split(',').map((p, i) => `${i}:${p}`)
: '';
uciConfigs.egress_qos_mapping = port.X_IOPSYS_EU_EgressPriorityRegeneration !== ''
? port.X_IOPSYS_EU_EgressPriorityRegeneration.split(',').map((p, i) => `${i}:${p}`)
: '';
setUci('network', devName, uciConfigs);
ifnames.push(ifname);
}
@ -132,22 +149,18 @@ export function initDeviceBridgingBridge(bri) {
// create empty interface for the bridge
addUci('network', 'interface', `itf_${bri._key}`, {
device: bri.Name,
layer2_bridge: '1',
bridge_empty: '1',
});
}
export function filterDeviceBridgingBridge(uci) {
return (uci.type === 'bridge');
}
export const filterDeviceBridgingBridge = uci => uci.type === 'bridge';
export function deinitDeviceBridgingBridge(uci) {
const ports = getUciOption('network', uci, 'ports');
ports?.forEach((port) => {
ports?.forEach(port => {
if (port.includes('.')) {
const dev = getUciByType('network', 'device', { match: { name: port } });
if (dev) {
delUci('network', dev[0]['.name']);
}
if (dev) delUci('network', dev[0]['.name']);
}
});
}

View file

@ -10,11 +10,13 @@
*/
import { getUciByType } from '../uci.js';
import { getBridgePortType, getTPIDFromDeviceType } from './common.js';
function importBridge(dev, devices, bridges) {
const briPorts = [];
const briVLAN = [];
const briVLANPort = [];
// create the management port first
briPorts.push({
Alias: `cpe-${dev.name}`,
@ -36,32 +38,36 @@ function importBridge(dev, devices, bridges) {
_key: dev['.name'],
});
const ethPorts = devices.filter((x) => x.ifname?.startsWith('eth'));
const ethPorts = devices.filter(x => x.ifname?.startsWith('eth'));
for (const portName of (dev.ports || [])) {
let portIndex = ethPorts.findIndex((x) => x.ifname === portName);
let portIndex = ethPorts.findIndex(x => x.ifname === portName);
if (portIndex >= 0) {
// Regular ethernet port
const ethDevice = ethPorts[portIndex];
const portType = getBridgePortType(ethDevice.type) || 'CustomerVLANPort';
const tpid = getTPIDFromDeviceType(ethDevice.type, ethDevice.tpid);
briPorts.push({
Enable: 1,
Name: ethPorts[portIndex]['.name'],
Alias: `cpe-${ethPorts[portIndex]['.name']}`,
TPID: 37120,
Name: ethDevice['.name'],
Alias: `cpe-${ethDevice['.name']}`,
TPID: tpid,
PVID: 1,
Type: 'CustomerVLANPort',
Type: portType,
LowerLayers: `Device.Ethernet.Interface.${portIndex + 1}`,
_key: ethPorts[portIndex]['.name'],
_key: ethDevice['.name'],
});
} else {
// vlan device
const device = devices.find((x) => x.name === portName);
const device = devices.find(x => x.name === portName);
if (!device) {
_log_error('device not found', portName);
// eslint-disable-next-line no-continue
continue;
}
if (device.type === '8021q' || device.type === 'untagged') {
let vlanIndex = briVLAN.findIndex((x) => x.VLANID === device.vid);
if (device.type === '8021q' || device.type === 'untagged' || device.type === '8021ad' || device.type === 'transparent') {
let vlanIndex = briVLAN.findIndex(x => x.VLANID === device.vid);
if (vlanIndex < 0) {
briVLAN.push({ Enable: 1, VLANID: device.vid });
vlanIndex = briVLAN.length;
@ -69,14 +75,21 @@ function importBridge(dev, devices, bridges) {
vlanIndex += 1;
}
// Get the base ethernet device to determine the correct port index
const baseEthDevice = ethPorts.find(x => device.ifname === x.ifname);
const basePortIndex = baseEthDevice ? ethPorts.indexOf(baseEthDevice) : 0;
const portType = getBridgePortType(device.type) || 'CustomerVLANPort';
const tpid = getTPIDFromDeviceType(device.type, device.tpid);
briPorts.push({
Enable: 1,
Name: device['.name'],
Alias: `cpe-${port['.name']}`,
TPID: 33024,
Alias: `cpe-${device['.name']}`,
TPID: tpid,
PVID: device.vid,
Type: 'CustomerVLANPort',
LowerLayers: `Device.Ethernet.Interface.${port['.index'] + 1}`,
Type: portType,
LowerLayers: `Device.Ethernet.Interface.${basePortIndex + 1}`,
_key: device['.name'],
});
@ -95,14 +108,14 @@ function importBridge(dev, devices, bridges) {
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(',');
briPorts[0].LowerLayers = indexes.map(i => `Device.Bridging.Bridge.${bridges.length}.Port.${i}`).join(',');
}
}
export function importDeviceBridgingBridge() {
const bridges = [];
const devices = getUciByType('network', 'device');
devices?.forEach((dev) => {
devices?.forEach(dev => {
if (dev.type === 'bridge') {
importBridge(dev, devices, bridges);
}

View file

@ -10,17 +10,16 @@
*/
import * as std from 'std';
import { isTrue } from '../utils.js';
import { getUciByType } from '../uci.js';
function setMgmtPortLowerLayers(bri) {
if (!bri) {
return 0;
}
if (!bri) return 0;
const portPath = `Device.Bridging.Bridge.${bri['.index']}.Port.`;
const mgmtPort = _dm_instances(portPath, '(ManagementPort="true" OR ManagementPort=1)');
if (mgmtPort.length !== 1) {
return 0;
}
if (mgmtPort.length !== 1) return 0;
const nonMgmtPort = _dm_instances(portPath, '(ManagementPort="false" OR ManagementPort=0)');
_dm_update(`${mgmtPort[0]}.LowerLayers`, nonMgmtPort.join(','));
return 0;
@ -36,115 +35,86 @@ export function changedDeviceBridgingBridgePortManagementPort(bri) {
export function getDeviceBridgingBridgeStatus(bri) {
const enable = _dm_get(`Device.Bridging.Bridge.${bri['.index']}.Enable`);
if (enable) {
return 'Enabled';
}
return 'Disabled';
return enable ? 'Enabled' : 'Disabled';
}
export function getDeviceBridgingBridgeSTPStatus(bri) {
let status = 'Disabled';
const stpState = std.loadFile(`/sys/class/net/${bri.Name}/bridge/stp_state`)?.trim();
if (stpState === '1') {
status = 'Enabled';
}
return status;
return stpState === '1' ? 'Enabled' : 'Disabled';
}
export function getDeviceBridgingBridgePortStatus(bri, port) {
if (!port['.db']) {
return 'Up';
}
if (!port['.db']) return 'Up';
const enable = _dm_get(`Device.Bridging.Bridge.${bri['.index']}.Port.${port['.index']}.Enable`);
if (enable) {
return 'Up';
}
return 'Down';
return enable ? 'Up' : 'Down';
}
export function infoDeviceBridgingBridgePort(path, port) {
const mgmtPort = _dm_get(`${path}.ManagementPort`);
if (typeof mgmtPort === 'undefined' || mgmtPort) {
return;
}
if (typeof mgmtPort === 'undefined' || mgmtPort) return;
const lower = _dm_get(`${path}.LowerLayers`);
if (!lower) {
return;
if (lower) {
port.ifname = _dm_linker_value(lower);
}
port.ifname = _dm_linker_value(lower);
}
export function getDeviceBridgingBridgePortStatsBytesSent(bri, port) {
return std.loadFile(`/sys/class/net/${port.ifname}/statistics/tx_bytes`)?.trim();
// Helper function to read network statistics
function getNetworkStat(port, statName) {
return std.loadFile(`/sys/class/net/${port.ifname}/statistics/${statName}`)?.trim();
}
export function getDeviceBridgingBridgePortStatsBytesReceived(bri, port) {
return std.loadFile(`/sys/class/net/${port.ifname}/statistics/rx_bytes`)?.trim();
}
export const getDeviceBridgingBridgePortStatsBytesSent = (bri, port) =>
getNetworkStat(port, 'tx_bytes');
export function getDeviceBridgingBridgePortStatsPacketsSent(bri, port) {
return std.loadFile(`/sys/class/net/${port.ifname}/statistics/tx_packets`)?.trim();
}
export const getDeviceBridgingBridgePortStatsBytesReceived = (bri, port) =>
getNetworkStat(port, 'rx_bytes');
export function getDeviceBridgingBridgePortStatsPacketsReceived(bri, port) {
return std.loadFile(`/sys/class/net/${port.ifname}/statistics/rx_packets`)?.trim();
}
export const getDeviceBridgingBridgePortStatsPacketsSent = (bri, port) =>
getNetworkStat(port, 'tx_packets');
export function getDeviceBridgingBridgePortStatsErrorsSent(bri, port) {
return std.loadFile(`/sys/class/net/${port.ifname}/statistics/tx_errors`)?.trim();
}
export const getDeviceBridgingBridgePortStatsPacketsReceived = (bri, port) =>
getNetworkStat(port, 'rx_packets');
export function getDeviceBridgingBridgePortStatsErrorsReceived(bri, port) {
return std.loadFile(`/sys/class/net/${port.ifname}/statistics/rx_errors`)?.trim();
}
export const getDeviceBridgingBridgePortStatsErrorsSent = (bri, port) =>
getNetworkStat(port, 'tx_errors');
export function getDeviceBridgingBridgePortStatsDiscardPacketsSent(bri, port) {
return std.loadFile(`/sys/class/net/${port.ifname}/statistics/tx_dropped`)?.trim();
}
export const getDeviceBridgingBridgePortStatsErrorsReceived = (bri, port) =>
getNetworkStat(port, 'rx_errors');
export function getDeviceBridgingBridgePortStatsDiscardPacketsReceived(bri, port) {
return std.loadFile(`/sys/class/net/${port.ifname}/statistics/rx_dropped`)?.trim();
}
export const getDeviceBridgingBridgePortStatsDiscardPacketsSent = (bri, port) =>
getNetworkStat(port, 'tx_dropped');
export function getDeviceBridgingBridgePortStatsMulticastPacketsReceived(bri, port) {
return std.loadFile(`/sys/class/net/${port.ifname}/statistics/multicast`)?.trim();
}
export const getDeviceBridgingBridgePortStatsDiscardPacketsReceived = (bri, port) =>
getNetworkStat(port, 'rx_dropped');
export function getDeviceBridgingBridgePortStatsUnicastPacketsSent(bri, port) {
return std.loadFile(`/sys/class/net/${port.ifname}/statistics/tx_unicast_packets`)?.trim();
}
export const getDeviceBridgingBridgePortStatsMulticastPacketsReceived = (bri, port) =>
getNetworkStat(port, 'multicast');
export function getDeviceBridgingBridgePortStatsUnicastPacketsReceived(bri, port) {
return std.loadFile(`/sys/class/net/${port.ifname}/statistics/rx_unicast_packets`)?.trim();
}
export const getDeviceBridgingBridgePortStatsUnicastPacketsSent = (bri, port) =>
getNetworkStat(port, 'tx_unicast_packets');
export function getDeviceBridgingBridgePortStatsMulticastPacketsSent(bri, port) {
return std.loadFile(`/sys/class/net/${port.ifname}/statistics/tx_multicast_packets`)?.trim();
}
export const getDeviceBridgingBridgePortStatsUnicastPacketsReceived = (bri, port) =>
getNetworkStat(port, 'rx_unicast_packets');
export function getDeviceBridgingBridgePortStatsBroadcastPacketsSent(bri, port) {
return std.loadFile(`/sys/class/net/${port.ifname}/statistics/tx_broadcast_packets`)?.trim();
}
export const getDeviceBridgingBridgePortStatsMulticastPacketsSent = (bri, port) =>
getNetworkStat(port, 'tx_multicast_packets');
export function getDeviceBridgingBridgePortStatsBroadcastPacketsReceived(bri, port) {
return std.loadFile(`/sys/class/net/${port.ifname}/statistics/rx_broadcast_packets`)?.trim();
}
export const getDeviceBridgingBridgePortStatsBroadcastPacketsSent = (bri, port) =>
getNetworkStat(port, 'tx_broadcast_packets');
export function getDeviceBridgingBridgePortStatsUnknownProtoPacketsReceived(bri, port) {
return std.loadFile(`/sys/class/net/${port.ifname}/statistics/rx_unknown_packets`)?.trim();
}
export const getDeviceBridgingBridgePortStatsBroadcastPacketsReceived = (bri, port) =>
getNetworkStat(port, 'rx_broadcast_packets');
export const getDeviceBridgingBridgePortStatsUnknownProtoPacketsReceived = (bri, port) =>
getNetworkStat(port, 'rx_unknown_packets');
export function getDeviceBridgingBridgePort(bri) {
let networkName = bri.Name;
if (bri.Name.startsWith('br-')) {
networkName = bri.Name.slice(3);
}
const networkName = bri.Name.startsWith('br-') ? bri.Name.slice(3) : bri.Name;
const wifiIfaces = getUciByType('wireless', 'wifi-iface', { match: { multi_ap: '2' } });
wifiIfaces?.forEach((x, i) => {
wifiIfaces?.forEach(x => {
const ssid = getUciByType('dmmap_wireless', 'ssid',
{ match: { device: x.device, ssid: x.ssid}, confdir: '/etc/bbfdm/dmmap'});
if (Array.isArray(ssid) && ssid.length > 0) {
@ -152,29 +122,12 @@ export function getDeviceBridgingBridgePort(bri) {
}
});
return wifiIfaces?.filter((x) => x.network === networkName);
return wifiIfaces?.filter(x => x.network === networkName);
}
export function getDeviceBridgingBridgePortLowerLayers(bri, port) {
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;
export function setDeviceBridgingBridgePortManagementPort(val, bri, port) {
if (isTrue(val)) {
_db_set(`Device.Bridging.Bridge.${bri['.index']}.Port.${port['.index']}.Name`, bri.Name);
}
return 1;
}

View file

@ -0,0 +1,61 @@
/*
* 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
* and its content without authorization by Genexis B.V. is
* prohibited. The prohibition includes every form of reproduction and
* distribution.
*
*/
export const bridgePortTypeMap = [
{ portType: 'CustomerNetworkPort', devType: '8021ad' },
{ portType: 'CustomerVLANPort', devType: '8021q' },
{ portType: 'CustomerVLANPort', devType: 'untagged' },
{ portType: 'CustomerVLANPort', devType: '' },
{ portType: 'CustomerVLANPort', devType: undefined },
{ portType: 'VLANUnawarePort', devType: 'transparent' }
];
export function getBridgePortType(devType) {
const mapping = bridgePortTypeMap.find(map => map.devType === devType);
return mapping ? mapping.portType : null;
}
export function getBridgeDeviceType(portType) {
const mapping = bridgePortTypeMap.find(map => map.portType === portType);
return mapping ? mapping.devType : null;
}
export function getDefaultTPID(deviceType) {
switch (deviceType) {
case '8021q':
return '33024';
case '8021ad':
return '34984';
default:
return '37120';
}
}
export function getTPIDFromDeviceType(deviceType, explicitTPID) {
// If explicit TPID is set, use it
if (explicitTPID && explicitTPID !== '') {
return parseInt(explicitTPID, 10);
}
// Default TPID based on device type
switch (deviceType) {
case '8021q':
return 33024;
case '8021ad':
return 34984;
case 'untagged':
case 'transparent':
case '':
case undefined:
default:
return 37120;
}
}

View file

@ -261,4 +261,8 @@ export function transformInputObject(obj) {
});
return result;
}
}
export function isTrue(val) {
return val === 'true' || val === '1' || val === true;
}

View file

@ -58,7 +58,6 @@ define Package/$(PKG_NAME)/install
$(BBFDM_REGISTER_SERVICES) ./bbfdm_service.json $(1) $(PKG_NAME)
$(INSTALL_DIR) $(1)/lib/upgrade/keep.d
# $(INSTALL_BIN) ./files/etc/init.d/dm-agent $(1)/etc/init.d/dm-agent
$(INSTALL_BIN) ./files/lib/upgrade/keep.d/dm-agent $(1)/lib/upgrade/keep.d/dm-agent
$(INSTALL_BIN) $(PKG_BUILD_DIR)/dm-agent $(1)/usr/sbin
endef

View file

@ -1,2 +0,0 @@
config dm_agent 'agent'
option loglevel '1'

View file

@ -1 +0,0 @@
/etc/dm.db

View file

@ -722,21 +722,6 @@ static int start_trans(int proto)
return agent_ctx.trans_id;
}
static int commit_trans()
{
dmlog_debug("commit_trans");
int ret = dmapi_session_end(TRANX_COMMIT_AND_APPLY);
agent_ctx.trans_id = 0;
return ret;
}
static int abort_trans()
{
dmlog_debug("abort_trans");
agent_ctx.trans_id = 0;
return dmapi_session_end(TRANX_ROLLBACK);
}
enum {
TRANS_CMD,
TRANS_OPTIONAL,
@ -777,13 +762,10 @@ static int usp_transaction_handler(struct ubus_context *ctx, struct ubus_object
get_bbf_options(&options, tb[TRANS_OPTIONAL]);
if (strcmp(trans_cmd, "start") != 0 && strcmp(trans_cmd, "status") != 0 &&
(agent_ctx.trans_id == 0 || agent_ctx.trans_id != options.trans_id)) {
dmlog_error("invalid transaction id for cmd: %s. tid: %d", trans_cmd, options.trans_id);
return UBUS_STATUS_INVALID_ARGUMENT;
}
if (strcmp(trans_cmd, "start") == 0) {
set_uci_savedir(options.dm_type);
ret = start_trans(options.dm_type);
if (ret > 0) {
blobmsg_add_u8(&bb, "status", true);
@ -792,10 +774,14 @@ static int usp_transaction_handler(struct ubus_context *ctx, struct ubus_object
blobmsg_add_u8(&bb, "status", false);
}
} else if (strcmp(trans_cmd, "commit") == 0) {
ret = commit_trans();
ret = dmapi_session_end(TRANX_COMMIT);
blobmsg_add_u8(&bb, "status", (ret == 0));
} else if (strcmp(trans_cmd, "apply") == 0) {
set_uci_savedir(options.dm_type);
ret = dmapi_session_apply();
blobmsg_add_u8(&bb, "status", (ret == 0));
} else if (strcmp(trans_cmd, "abort") == 0) {
ret = abort_trans();
ret = dmapi_session_end(TRANX_ROLLBACK);
blobmsg_add_u8(&bb, "status", (ret == 0));
} else if (strcmp(trans_cmd, "status") == 0) {
int64_t rem = uloop_timeout_remaining64(&agent_ctx.trans_timer);
@ -914,7 +900,7 @@ int usp_set_handler(struct ubus_context *ctx, struct ubus_object *obj,
return UBUS_STATUS_INVALID_ARGUMENT;
}
int ret = 0;
int res = 0;
void *array = blobmsg_open_array(&bb, "results");
if (dm_node_is_parameter(node.id)) {
if (set_blob_value(&node, tb[DM_SET_VALUE]) != 0) {
@ -994,7 +980,7 @@ int usp_add_handler(struct ubus_context *ctx, struct ubus_object *obj,
dm_node_t node;
dm_node_t param_node;
bool allow_partial = false;
int ret = 0;
int res = 0;
memset(&bb, 0, sizeof(struct blob_buf));
blob_buf_init(&bb, 0);
@ -1142,7 +1128,6 @@ int usp_del_handler(struct ubus_context *ctx, struct ubus_object *obj,
char *path = blobmsg_get_string(tb[DM_DEL_PATH]);
if (dm_path2node(path, &node) != 0 || dmapi_object_del(&node) < 0) {
set_result(&bb, path, 9005, "Invalid path");
res = -1;
goto end;
}
set_result(&bb, path, 0, "1");

View file

@ -531,16 +531,19 @@ int dbmgr_finalize(void)
int dbmgr_tranx_begin(void)
{
dmlog_debug("dbmgr_tranx_begin");
return exec_trans_sql("BEGIN;", NULL, NULL);
}
int dbmgr_tranx_revert(void)
{
dmlog_debug("dbmgr_tranx_revert");
return exec_trans_sql("ROLLBACK;", NULL, NULL);
}
int dbmgr_tranx_commit(void)
{
dmlog_debug("dbmgr_tranx_commit");
int ret = exec_trans_sql("COMMIT;", NULL, NULL);
if (ret != 0) {

View file

@ -30,10 +30,10 @@
extern int importDM();
#define DEFFAULT_DB_PATH "/etc/default_dm.db"
#define DB_PATH "/etc/dm.db"
#define BACKUP_DB_PATH "/etc/dm-backup.db"
#define NEW_DB_PATH "/etc/dm-new.db"
#define DEFFAULT_DB_PATH "/etc/bbfdm/default_dm.db"
#define DB_PATH "/etc/bbfdm/dm.db"
#define BACKUP_DB_PATH "/etc/bbfdm/dm-backup.db"
#define NEW_DB_PATH "/etc/bbfdm/dm-new.db"
#define INODE_BUF_EXPIRE_TIME 2000 // in ms
#define NODELIST_DEFAULT_CNT 32
@ -41,7 +41,8 @@ extern int importDM();
enum SESSION_STATE {
SESSION_STATE_IDLE,
SESSION_STATE_STARTED,
SESSION_STATE_MODIFYING
SESSION_STATE_MODIFYING,
SESSION_STATE_APPLYING,
};
struct dmapi_context {
@ -193,6 +194,20 @@ int dmapi_session_start()
return 0;
}
int dmapi_session_apply(void)
{
dmlog_debug("dmapi_session_apply");
if (!dmapi_in_session()) {
dmlog_error("not in session, skip apply");
return 0;
}
global_ctx.session_state = SESSION_STATE_APPLYING;
dm_apply_do_apply();
dmlog_debug("dmapi_session_apply end");
return 0;
}
int dmapi_session_end(int action)
{
if (!dmapi_in_session()) {
@ -200,18 +215,27 @@ int dmapi_session_end(int action)
return 0;
}
dmlog_debug("dmapi_session_end, action: %d, state: %d", action, global_ctx.session_state);
switch (action) {
case TRANX_ROLLBACK:
dmlog_debug("dmapi_session_end, rollback");
dmapi_session_revert();
break;
case TRANX_COMMIT:
dmlog_debug("dmapi_session_end, commit");
if (global_ctx.session_state != SESSION_STATE_APPLYING) {
dmlog_warn("dmapi_session_end, missing apply before commit");
}
dmapi_session_commit();
break;
case TRANX_COMMIT_AND_APPLY:
dmlog_debug("dmapi_session_end, commit and apply");
dmapi_session_commit();
dm_apply_do_apply();
break;
case TRANX_NO_ACTION:
dmlog_debug("dmapi_session_end, TRANX_NO_ACTION");
break;
default:
dmlog_error("invalid action %d", action);
@ -220,8 +244,6 @@ int dmapi_session_end(int action)
global_ctx.session_state = SESSION_STATE_IDLE;
qjs_uci_global_cleanup();
dmlog_info("session ended");
return 0;
}
@ -233,8 +255,7 @@ int dmapi_session_commit(void)
return -1;
}
if (global_ctx.session_state == SESSION_STATE_MODIFYING) {
global_ctx.session_state = SESSION_STATE_STARTED;
if (global_ctx.session_state == SESSION_STATE_MODIFYING || global_ctx.session_state == SESSION_STATE_APPLYING) {
if (dbmgr_tranx_commit() == 0) {
return 0;
} else {
@ -255,12 +276,17 @@ int dmapi_session_revert(void)
return -1;
}
if (global_ctx.session_state == SESSION_STATE_MODIFYING) {
if (global_ctx.session_state == SESSION_STATE_MODIFYING || global_ctx.session_state == SESSION_STATE_APPLYING) {
dbmgr_tranx_revert();
global_ctx.session_state = SESSION_STATE_STARTED;
dm_apply_reset_changes();
// Clear instance buffers to ensure any instances added during the transaction
// are removed from the buffer since they were rolled back from the database
inode_buf_free_all();
dm_refresh_linker_nodes(global_ctx.service_name);
dmlog_info("instance buffers cleared after transaction rollback");
}
dm_apply_reset_changes();
return 0;
}
@ -390,6 +416,18 @@ int dmapi_param_get(const dm_node_t *node, char **value)
asprintf(value, "%s", "true");
return 0;
}
// Check if there's a default value defined in JSON
if (param->default_val) {
*value = strdup(param->default_val);
return 0;
}
// For boolean parameters without a default value, return "false"
if (param->data_type == DM_DATA_BOOLEAN) {
*value = strdup("false");
return 0;
}
}
}
}
@ -589,7 +627,7 @@ int dmapi_param_set(const dm_node_t *node, const char *value)
return -1;
}
if (!global_ctx.import_mode && !dm_node_is_writable(node->id)) {
if (!global_ctx.import_mode && !dm_node_is_writable(node->id) && (global_ctx.session_state != SESSION_STATE_APPLYING)) {
dmlog_error("Not able to set readonly parameter %s.", dm_node_str(node));
return -1;
}
@ -640,7 +678,7 @@ int dmapi_param_set(const dm_node_t *node, const char *value)
}
// db
if (global_ctx.session_state != SESSION_STATE_MODIFYING) {
if (global_ctx.session_state != SESSION_STATE_MODIFYING && global_ctx.session_state != SESSION_STATE_APPLYING) {
dbmgr_tranx_begin();
global_ctx.session_state = SESSION_STATE_MODIFYING;
}
@ -850,12 +888,14 @@ int dmapi_object_add(dm_node_t *node)
return -1;
}
start_session_on_fly();
if (!dmapi_in_session()) {
dmlog_error("not in session");
return -1;
}
if (global_ctx.session_state != SESSION_STATE_MODIFYING) {
if (global_ctx.session_state != SESSION_STATE_MODIFYING && global_ctx.session_state != SESSION_STATE_APPLYING) {
dbmgr_tranx_begin();
global_ctx.session_state = SESSION_STATE_MODIFYING;
}
@ -1100,7 +1140,7 @@ int dmapi_object_del(const dm_node_t *node)
return -1;
}
if (global_ctx.session_state != SESSION_STATE_MODIFYING) {
if (global_ctx.session_state != SESSION_STATE_MODIFYING && global_ctx.session_state != SESSION_STATE_APPLYING) {
dbmgr_tranx_begin();
global_ctx.session_state = SESSION_STATE_MODIFYING;
}
@ -1379,7 +1419,12 @@ dm_nodelist_h dm_nodelist_find(const dm_node_t *node, const char *keys, int only
return NULL;
}
if (!keys) {
// Check if this is a multi-index node with incomplete indices
// If so, bypass inode buffer and go directly to database
int expected_cnt = dm_node_index_cnt(node->id);
int has_incomplete_indices = (expected_cnt > 1 && node->cnt < expected_cnt);
if (!keys && !has_incomplete_indices) {
// reuse the db instances in buffer.
int retry;
struct inode_entry *entry = inode_buf_find(node, &retry);
@ -1402,6 +1447,7 @@ dm_nodelist_h dm_nodelist_find(const dm_node_t *node, const char *keys, int only
}
}
// Either keys specified, incomplete indices, or inode buffer failed - use database directly
get_indexes(node, list, keys, only_db);
return (dm_nodelist_h)list;
}

View file

@ -54,6 +54,12 @@ int dmapi_session_start();
*/
int dmapi_session_end(int action);
/** Apply changes in a session.
* @pre dmapi_session_start should be called successfully
* @return 0 in case of success, or a nonzero value in case of error
*/
int dmapi_session_apply(void);
/** Commits a transaction in a session.
* @pre dmapi_session_start should be called successfully
* and have modified parameters or objects by calling

View file

@ -661,7 +661,7 @@ static void reset_confidentials()
return;
int i;
dbmgr_tranx_begin();
// dbmgr_tranx_begin();
int cnt = dm_list_cnt(changed_node_list);
for (i = 0; i < cnt; i++) {
struct node_change *change = (struct node_change *)dm_list_get(changed_node_list, i);
@ -672,7 +672,7 @@ static void reset_confidentials()
}
}
dbmgr_tranx_commit();
// dbmgr_tranx_commit();
}
int dm_apply_reset(void)

View file

@ -108,6 +108,7 @@ static int process_linker_node(struct uci_context *ctx, struct uci_package *pkg,
// Check if this parameter is a linker node
if (!is_linker_node(node->id)) {
dmlog_error("Not a linker parameter %s", dm_node_str(node));
return 0; // Not a linker parameter
}
@ -144,6 +145,7 @@ static int process_linker_node(struct uci_context *ctx, struct uci_package *pkg,
size_t len_linker = strlen(parent_path) + strlen(key_name) + strlen(key_value) + 6; // extra for [==].
char *linker_string = malloc(len_linker);
if (!linker_string) {
dmlog_error("Failed to allocate memory for linker string");
free(key_value);
return -1;
}
@ -164,6 +166,7 @@ static int process_linker_node(struct uci_context *ctx, struct uci_package *pkg,
char *hash_value = calculate_hash(current_path);
if (!hash_path || !hash_value) {
dmlog_error("Failed to calculate hashes for %s", linker_string);
free(linker_string);
free(key_value);
free(hash_path);
@ -257,10 +260,12 @@ static int process_all_linker_nodes(struct uci_context *ctx, struct uci_package
dm_node_t parent_node = {0};
parent_node.id = parent_id;
dm_nodelist_h list = dm_nodelist_get_db(&parent_node);
// For multi-index nodes, bypass inode buffer and go directly to DB
dm_nodelist_h list = dm_nodelist_find(&parent_node, NULL, 1);
if (list != DM_INVALID_NODELIST) {
const dm_node_t *instance_node;
nodelist_for_each_node(instance_node, list) {
// dmlog_debug("processing linker node %s", dm_node_str(instance_node));
// Create the parameter node for this instance
dm_node_t param_node = *instance_node;
param_node.id = linker_id;
@ -334,6 +339,7 @@ static int ensure_uci_sections(struct uci_context *ctx, struct uci_package *pkg)
* Main API function to refresh linker nodes
*/
int dm_refresh_linker_nodes(const char *service_name) {
dmlog_debug("dm_refresh_linker_nodes start");
if (!service_name) {
dmlog_error("Service name is required");
return -1;

View file

@ -0,0 +1,50 @@
/*
* QuickJS UCI helper public API
*
* Provides initialisation helpers for the shared libuci context used by
* qjs_uci_api.c as well as the QuickJS registration entry point.
*/
#ifndef QJS_UCI_API_H
#define QJS_UCI_API_H
#ifdef __cplusplus
extern "C" {
#endif
/*
* Initialise the shared libuci context.
* Safe to call multiple times subsequent calls are no-ops.
* Returns 0 on success, -1 on failure.
*/
int qjs_uci_global_init(void);
/* Set the directory where libuci stores pending changes (may be NULL). */
void qjs_uci_global_set_savedir(const char *save_dir);
/* Free the shared libuci context at shutdown (optional). */
void qjs_uci_global_cleanup(void);
/* Register the _uci_call() C binding with QuickJS. */
int qjs_uci_api_init(void);
#include <json-c/json.h>
/* Simple name/value helper used by dm_uci_* helpers */
typedef struct {
const char *name;
const char *value;
} name_val_t;
int dm_uci_get(const char *uci_path, char **value);
int dm_uci_set(const char *uci_path, const char *value);
int dm_uci_add(const char *config, const char *type, const char *section_name, name_val_t *opt_values, int value_cnt);
int dm_uci_del(const char *config, const char *section);
int dm_uci_get_section_list(const char *config, const char *type, name_val_t *match, int match_cnt, struct json_object **res);
int dm_uci_commit(const char *config);
#ifdef __cplusplus
}
#endif
#endif /* QJS_UCI_API_H */

View file

@ -370,6 +370,7 @@ int qjs_call_get_handler(const dm_node_t *node, char **res)
{
JSValue val;
int free_val = 1;
const struct dm_parameter *param = dm_node_get_parameter(node->id);
if (param->js_val == NULL && !qjs_has_get_handler(node->id)) {
dmlog_error("missing get handler for %s", dm_node_str(node));
@ -383,7 +384,7 @@ int qjs_call_get_handler(const dm_node_t *node, char **res)
return -1;
}
if (param->js_val) {
if (param->js_val) {;
if (param->js_val[0] == '\0') {
val = values[cnt - 1];
free_val = 0; // no free

View file

@ -237,12 +237,12 @@ static JSValue _set_param_value(JSContext *ctx, JSValueConst this_val,
if (value != NULL) {
if (only_db) {
dbmgr_tranx_begin();
// dbmgr_tranx_begin();
ret = dbmgr_set(&node, value);
if (dm_node_index_cnt(node.id) > 0) {
ret |= inode_buf_update_param_value(&node, value);
}
dbmgr_tranx_commit();
// dbmgr_tranx_commit();
} else {
dmapi_param_set(&node, value);
}

View file

@ -307,7 +307,7 @@ static JSValue _uci_call(JSContext *ctx, JSValueConst this_val,
/* Attempt to extract a few common parameters for logging */
const char *log_cfg = NULL, *log_sec = NULL, *log_opt = NULL, *log_type = NULL;
if (args_obj != JS_UNDEFINED) {
if (!JS_IsUndefined(args_obj)) {
log_cfg = js_obj_get_str(ctx, args_obj, "config");
log_sec = js_obj_get_str(ctx, args_obj, "section");
log_opt = js_obj_get_str(ctx, args_obj, "option");
@ -316,7 +316,7 @@ static JSValue _uci_call(JSContext *ctx, JSValueConst this_val,
/* Prepare JSON string of the full argument object for detailed debug */
char *args_json = NULL;
if (args_obj != JS_UNDEFINED) {
if (!JS_IsUndefined(args_obj)) {
JSValue arg_json_val = JS_JSONStringify(ctx, args_obj, JS_UNDEFINED, JS_UNDEFINED);
if (!JS_IsException(arg_json_val)) {
args_json = (char *)JS_ToCString(ctx, arg_json_val);
@ -325,9 +325,9 @@ static JSValue _uci_call(JSContext *ctx, JSValueConst this_val,
}
if (lvl && strcmp(lvl, "dbg") == 0) {
dmlog_debug("_uci_call %s: cfg=%s sec=%s opt=%s type=%s args=%s", method,
log_cfg ? log_cfg : "-", log_sec ? log_sec : "-", log_opt ? log_opt : "-",
log_type ? log_type : "-", args_json ? args_json : "-");
// dmlog_debug("_uci_call %s: cfg=%s sec=%s opt=%s type=%s args=%s", method,
// log_cfg ? log_cfg : "-", log_sec ? log_sec : "-", log_opt ? log_opt : "-",
// log_type ? log_type : "-", args_json ? args_json : "-");
} else if (lvl) {
if (args_json) {
dmlog_info("_uci_call %s: cfg=%s sec=%s opt=%s type=%s args=%s", method,
@ -362,7 +362,7 @@ static JSValue _uci_call(JSContext *ctx, JSValueConst this_val,
struct uci_context *uci = g_uci_ctx;
/* Optional custom configuration directory */
if (args_obj != JS_UNDEFINED) {
if (!JS_IsUndefined(args_obj)) {
confdir = js_obj_get_str(ctx, args_obj, "confdir"); /* explicit */
}
@ -382,7 +382,7 @@ static JSValue _uci_call(JSContext *ctx, JSValueConst this_val,
}
/* Support absolute/relative file path in 'config' by splitting dir/name */
config = (args_obj != JS_UNDEFINED) ? js_obj_get_str(ctx, args_obj, "config") : NULL;
config = (!JS_IsUndefined(args_obj)) ? js_obj_get_str(ctx, args_obj, "config") : NULL;
char cfg_name_buf[128];
if (config && strchr(config, '/')) {
@ -639,8 +639,8 @@ cleanup:
}
if (strcmp(lvl, "dbg") == 0) {
dmlog_debug("_uci_call %s result ret=%d res=%s", method, ret,
payload_str ? payload_str : "<none>");
// dmlog_debug("_uci_call %s result ret=%d res=%s", method, ret,
// payload_str ? payload_str : "<none>");
} else {
dmlog_info("_uci_call %s result ret=%d", method, ret);
}
@ -685,6 +685,7 @@ int qjs_uci_global_init()
if (g_uci_savedir && uci_set_savedir(g_uci_ctx, g_uci_savedir) != UCI_OK) {
uci_free_context(g_uci_ctx);
g_uci_ctx = NULL;
dmlog_error("qjs_uci_global_init: uci_set_savedir failed");
return -1;
}
@ -695,7 +696,7 @@ int qjs_uci_global_init()
void qjs_uci_global_set_savedir(const char *save_dir)
{
dmlog_debug("qjs_uci_global_set_savedir: %s", save_dir);
// dmlog_debug("qjs_uci_global_set_savedir: %s", save_dir);
if (save_dir == g_uci_savedir && g_uci_ctx) {
return;
}
@ -812,9 +813,11 @@ static int parse_uci_path(const char *uci_path,
int dm_uci_get(const char *uci_path, char **value)
{
dmlog_debug("dm_uci_get: path=%s", uci_path ? uci_path : "-");
if (!value)
// dmlog_debug("dm_uci_get: path=%s", uci_path ? uci_path : "-");
if (!value) {
dmlog_error("dm_uci_get: value is NULL");
return -1;
}
*value = NULL;
const char *confdir = NULL;
@ -878,7 +881,7 @@ error_cleanup:
free(config);
free(section);
free(option);
dmlog_debug("dm_uci_get result ret=%d val=%s", (*value) ? 0 : -1, (*value) ? *value : "-");
// dmlog_debug("dm_uci_get result ret=%d val=%s", (*value) ? 0 : -1, (*value) ? *value : "-");
return (*value) ? 0 : -1;
error_restore:
@ -949,32 +952,41 @@ error_restore:
int dm_uci_del(const char *config, const char *section)
{
dmlog_info("dm_uci_del: cfg=%s sec=%s", config ? config : "-", section ? section : "-");
if (!config || !section)
if (!config || !section) {
dmlog_error("dm_uci_del: config or section is NULL");
return -1;
if (ensure_global_ctx() != 0)
}
if (ensure_global_ctx() != 0) {
dmlog_error("dm_uci_del: failed to ensure global context");
return -1;
}
struct uci_context *uci = g_uci_ctx;
bool need_unload = false;
struct uci_package *pkg = uci_lookup_package(uci, config);
if (!pkg) {
if (uci_load(uci, config, &pkg) != UCI_OK)
if (uci_load(uci, config, &pkg) != UCI_OK) {
dmlog_error("dm_uci_del: failed to load package %s", config);
return -1;
}
need_unload = true;
}
char path[256];
snprintf(path, sizeof(path), "%s.%s", config, section);
struct uci_ptr ptr = {};
if (uci_lookup_ptr(uci, &ptr, path, true) != UCI_OK)
if (uci_lookup_ptr(uci, &ptr, path, true) != UCI_OK) {
dmlog_error("dm_uci_del: failed to lookup ptr %s", path);
goto unload_pkg;
}
if (uci_delete(uci, &ptr) != UCI_OK)
if (uci_delete(uci, &ptr) != UCI_OK) {
dmlog_error("dm_uci_del: failed to delete %s", path);
goto unload_pkg;
}
if (need_unload && pkg)
uci_unload(uci, pkg);
dmlog_info("dm_uci_del result ret=0");
return 0;
unload_pkg:
@ -987,23 +999,30 @@ unload_pkg:
int dm_uci_commit(const char *config)
{
dmlog_info("dm_uci_commit: cfg=%s", config ? config : "-");
if (!config)
if (!config) {
dmlog_error("dm_uci_commit: config is NULL");
return -1;
if (ensure_global_ctx() != 0)
}
if (ensure_global_ctx() != 0){
dmlog_error("dm_uci_commit: failed to ensure global context");
return -1;
}
struct uci_context *uci = g_uci_ctx;
bool need_unload = false;
struct uci_package *pkg = uci_lookup_package(uci, config);
if (!pkg) {
if (uci_load(uci, config, &pkg) != UCI_OK)
if (uci_load(uci, config, &pkg) != UCI_OK) {
dmlog_error("dm_uci_commit: failed to load package %s", config);
return -1;
}
need_unload = true;
}
int ret = (uci_commit(uci, &pkg, false) == UCI_OK) ? 0 : -1;
if (need_unload && pkg)
uci_unload(uci, pkg);
dmlog_info("dm_uci_commit result ret=%d", ret);
return ret;
}
@ -1054,7 +1073,6 @@ int dm_uci_add(const char *config, const char *type, const char *section_name, n
uci_save(uci, pkg);
if (need_unload && pkg)
uci_unload(uci, pkg);
dmlog_info("dm_uci_add result ret=0 section=%s", final_name);
return 0;
unload_pkg:
@ -1136,7 +1154,7 @@ static struct json_object *create_json_values_object_c(struct uci_section *s)
int dm_uci_get_section_list(const char *config, const char *type, name_val_t *match, int match_cnt, struct json_object **res)
{
dmlog_debug("dm_uci_get_section_list: cfg=%s type=%s", config ? config : "-", type ? type : "-");
// dmlog_debug("dm_uci_get_section_list: cfg=%s type=%s", config ? config : "-", type ? type : "-");
if (!config || !res)
return -1;
*res = NULL;
@ -1168,6 +1186,6 @@ int dm_uci_get_section_list(const char *config, const char *type, name_val_t *ma
if (need_unload && pkg)
uci_unload(uci, pkg);
*res = root;
dmlog_debug("dm_uci_get_section_list result ret=0");
// dmlog_debug("dm_uci_get_section_list result ret=0");
return 0;
}