Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/common-db/src/lib/common/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2640,7 +2640,7 @@ export function decrypt(key: string, value: string): string {
* @param it The variable to test
* @returns true if it is Record<string, any>
*/
export function isObject(it: any): it is Record<string, any> {
export function isObject(it: unknown): it is Record<string, any> {
// This is necessary because:
// typeof null === 'object'
// typeof [] === 'object'
Expand All @@ -2654,8 +2654,8 @@ export function isObject(it: any): it is Record<string, any> {
*
* @param it The variable to test
*/
export function isArray(it: any): it is any[] {
return Array.isArray(it); // from node 0.1 is a part of engine
export function isArray(it: unknown): it is any[] {
return Array.isArray(it);
}

/**
Expand Down
122 changes: 71 additions & 51 deletions packages/controller/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@ interface RepoRequester {
callback: ioBroker.MessageCallbackInfo;
}

interface SendResponseToOptions {
/** The message we want to respond to */
receivedMsg: ioBroker.SendableMessage;
/** The response payload */
payload: Record<string, unknown>;
}

/** Host information including host id and running version */
type HostInformation = ioBroker.HostCommon & { host: string; runningVersion: string };

Expand Down Expand Up @@ -272,7 +279,7 @@ function getConfig(): ioBroker.IoBrokerJson | never {
* @param _config
* @param secret
*/
async function _startMultihost(_config: Record<string, any>, secret: string | false): Promise<void> {
async function _startMultihost(_config: ioBroker.IoBrokerJson, secret: string | false): Promise<void> {
const MHService = await import('./lib/multihostServer.js');
const cpus = os.cpus();
mhService = new MHService.MHServer(
Expand All @@ -297,7 +304,7 @@ async function _startMultihost(_config: Record<string, any>, secret: string | fa
*
* @param __config - the iobroker config object
*/
async function startMultihost(__config?: Record<string, any>): Promise<boolean | void> {
async function startMultihost(__config?: ioBroker.IoBrokerJson): Promise<boolean | void> {
if (compactGroupController) {
return;
}
Expand Down Expand Up @@ -341,7 +348,7 @@ async function startMultihost(__config?: Record<string, any>): Promise<boolean |
let obj: ioBroker.SystemConfigObject | null | undefined;
let errText;
try {
obj = await objects!.getObjectAsync(SYSTEM_CONFIG_ID);
obj = await objects!.getObject(SYSTEM_CONFIG_ID);
} catch (e) {
// will log error below
errText = e.message;
Expand Down Expand Up @@ -1405,7 +1412,7 @@ async function collectDiagInfo(type: DiagInfoType): Promise<void | Record<string
let err;

try {
systemConfig = await objects!.getObjectAsync(SYSTEM_CONFIG_ID);
systemConfig = await objects!.getObject(SYSTEM_CONFIG_ID);
} catch (e) {
err = e;
}
Expand Down Expand Up @@ -2074,8 +2081,7 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise<null | voi

// Collect statistics (only if license has been confirmed - user agreed)
if (
systemConfig?.common &&
systemConfig.common.diag &&
systemConfig?.common?.diag &&
systemConfig.common.licenseConfirmed &&
(!lastDiagSend || Date.now() - lastDiagSend > 30_000) // prevent sending of diagnostics by multiple admin instances
) {
Expand Down Expand Up @@ -2849,9 +2855,8 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise<null | voi
const configFile = tools.getConfigFileName();
if (fs.existsSync(configFile)) {
try {
let config = fs.readFileSync(configFile).toString('utf8');
const config: ioBroker.IoBrokerJson = fs.readJsonSync(configFile);
const stat = fs.lstatSync(configFile);
config = JSON.parse(config);
sendTo(msg.from, msg.command, { config, isActive: uptimeStart > stat.mtimeMs }, msg.callback);
} catch {
const error = `Cannot parse file ${configFile}`;
Expand All @@ -2871,56 +2876,58 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise<null | voi
break;

case 'writeBaseSettings': {
let error;
if (msg.message) {
const configFile = tools.getConfigFileName();
if (fs.existsSync(configFile)) {
let config;
if (typeof msg.message === 'string') {
try {
config = JSON.parse(msg.message);
} catch {
error = `Cannot parse data ${msg.message}`;
}
} else {
config = msg.message;
}
if (!msg.message) {
const error = `No data found on writeBaseSettings from "${msg.from}"`;
logger.error(`${hostLogPrefix} ${error}`);
return sendResponseTo({ receivedMsg: msg, payload: { error } });
}

if (!error) {
// todo validate structure, because very important
if (!config.system) {
error = 'Cannot find "system" in data';
} else if (!config.objects) {
error = 'Cannot find "objects" in data';
} else if (!config.states) {
error = 'Cannot find "states" in data';
} else if (!config.log) {
error = 'Cannot find "log" in data';
}
}
const configFile = tools.getConfigFileName();

if (!error) {
try {
fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
} catch {
error = `Cannot write file ${configFile}`;
}
}
}
} else {
error = `No data found for writeBaseSettings ${msg.from}`;
if (!fs.existsSync(configFile)) {
const error = `No config file exists on writeBaseSettings from "${msg.from}"`;
logger.error(`${hostLogPrefix} ${error}`);
return sendResponseTo({ receivedMsg: msg, payload: { error } });
}

if (error) {
logger.error(`${hostLogPrefix} ${error}`);
if (msg.callback && msg.from) {
sendTo(msg.from, msg.command, { error }, msg.callback);
let config: ioBroker.IoBrokerJson | undefined;
if (typeof msg.message === 'string') {
try {
config = JSON.parse(msg.message);
} catch {
return sendResponseTo({
receivedMsg: msg,
payload: { error: `Cannot parse data: "${msg.message}"` }
});
}
} else {
msg.callback && msg.from && sendTo(msg.from, msg.command, { result: 'ok' }, msg.callback);
config = msg.message;
}

break;
if (!config) {
return sendResponseTo({ receivedMsg: msg, payload: { error: 'Empty config' } });
}

if (!config.system) {
return sendResponseTo({ receivedMsg: msg, payload: { error: 'Cannot find "system" in data' } });
}
if (!config.objects) {
return sendResponseTo({ receivedMsg: msg, payload: { error: 'Cannot find "objects" in data' } });
}
if (!config.states) {
return sendResponseTo({ receivedMsg: msg, payload: { error: 'Cannot find "states" in data' } });
}
if (!config.log) {
return sendResponseTo({ receivedMsg: msg, payload: { error: 'Cannot find "log" in data' } });
}

try {
fs.writeFileSync(configFile, JSON.stringify(config, null, 2));

Check failure

Code scanning / CodeQL

Potential file system race condition

The file may have changed since it [was checked](1).
} catch {
return sendResponseTo({ receivedMsg: msg, payload: { error: `Cannot write file ${configFile}` } });
}

return sendResponseTo({ receivedMsg: msg, payload: { result: 'ok' } });
}

case 'addNotification':
Expand Down Expand Up @@ -3039,6 +3046,19 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise<null | voi
}
}

/**
* Wrapper around sendTo for message responses
*
* @param options The received message and response payload
*/
async function sendResponseTo(options: SendResponseToOptions): Promise<void> {
const { receivedMsg, payload } = options;

if (receivedMsg.callback && receivedMsg.from) {
await sendTo(receivedMsg.from, receivedMsg.command, payload, receivedMsg.callback);
}
}

/**
* Collect all instances on this host and call `initInstances`
*/
Expand Down Expand Up @@ -5242,7 +5262,7 @@ export async function init(compactGroupId?: number): Promise<void> {
stopTimeout += 5_000;
}

// If bootstrap file detected, it must be deleted, but give time for a bootstrap process to use this file
// If a bootstrap file detected, it must be deleted, but give time for a bootstrap process to use this file
if (fs.existsSync(VENDOR_BOOTSTRAP_FILE)) {
setTimeout(() => {
try {
Expand Down