- Fixed a core dump in the sycamore protocol

- Added missing files aynnet.*
- Fixed the addition to root issue in scriptcontex
This commit is contained in:
koennecke
2009-02-17 08:34:46 +00:00
parent e0d306db60
commit ee49c5b56a
5 changed files with 802 additions and 16 deletions

576
asynnet.c Normal file
View File

@ -0,0 +1,576 @@
/**
* Asynchronous networking for SICS and other programs. This module centrally manages
* a number of network connections for a client program. It is a layer between the
* program and the network which manages non blocking network I/O. To this purpose, the
* client program has to call ANETprocess at convenient intervalls. This module
* has a couple of features:
* - Connections are abstracted to handles which are guranteed to be unique
* rather then socket numbers. Socket numbers may be reused by the OS.
* - This module allows upper level code to figure out if a connection is still
* connected or not.
* - This module introduces a buffer layer between the socket and the application.
* Thus the upper layer does not have to worry much about I/O blocking. This
* is taken care of by this module both for reading and writing.
* - All I/O is non blocking.
* - This module can detect if a client is hanging and close the connection then.
* Hanging is detected by not being able to write to the client for some period
* of time.
*
* copyright: see file COPYRIGHT
*
* Mark Koennecke, January 2009
*/
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "asynnet.h"
#include "rwpuffer.h"
/*--------------------------------------------------------------------------*/
#define SERVERSOCKET 0
#define DATASOCKET 1
#define MAXCONNECTIONS 1024
#define RBUFFERSIZE 262144 /* 256kb */
#define WBUFFERSIZE 2*262144 /* 512kb */
/*--------------------------------------------------------------------------*/
typedef struct {
int socket;
int handle;
int type;
prwBuffer readBuffer;
prwBuffer writeBuffer;
time_t lastOpenForWrite;
ANETcallback readCallback;
void *userData;
ANETkill killUser;
} SocketDescriptor, *pSocketDescriptor;
static SocketDescriptor connections[MAXCONNECTIONS];
static int noConnections = 0;
static unsigned int handleID = 0;
/*------------------------------------------------------------------------*/
static int SocketCompare(const void *s1, const void *s2)
{
pSocketDescriptor socke1, socke2;
socke1 = (pSocketDescriptor) s1;
socke2 = (pSocketDescriptor) s2;
return socke1->handle - socke2->handle;
}
/*------------------------------------------------------------------------*/
static void sortConnections()
{
qsort(connections, noConnections,
sizeof(SocketDescriptor), SocketCompare);
}
/*------------------------------------------------------------------------*/
static pSocketDescriptor findSocketDescriptor(int handle)
{
SocketDescriptor key;
pSocketDescriptor result;
key.handle = handle;
result = bsearch(&key, connections, noConnections,
sizeof(SocketDescriptor), SocketCompare);
return result;
}
/*------------------------------------------------------------------------*/
static ANETlog logOutput = NULL;
static void *logUserData = NULL;
/*------------------------------------------------------------------------*/
static void anetLog(int level, char *fmt, ...)
{
va_list ap;
char buf[256];
char *text = NULL;
int l;
if (logOutput == NULL) {
return;
}
va_start(ap, fmt);
l = vsnprintf(buf, sizeof buf, fmt, ap);
va_end(ap);
if (l < sizeof buf) {
text = buf;
logOutput(level, text, logUserData);
} else {
/* assuming we have a C99 conforming snprintf and need a larger buffer */
text = calloc(l, 1);
va_start(ap, fmt);
vsnprintf(text, l, fmt, ap);
va_end(ap);
logOutput(level, text, logUserData);
free(text);
}
}
/*============= public interface =========================================*/
void ANETsetLog(ANETlog lcb, void *userData)
{
logOutput = lcb;
logUserData = userData;
}
/*------------------------------------------------------------------------*/
int ANETopenServerPort(int iPort, ANETcallback cb, void *userData)
{
SocketDescriptor socke;
int i = 1, status;
struct sockaddr_in addresse;
assert(iPort > 0);
assert(cb != NULL);
memset(&socke, 0, sizeof(SocketDescriptor));
socke.socket = socket(AF_INET, SOCK_STREAM, 0);
if (socke.socket < 0) {
anetLog(ANETERROR, "Failed to open server port: socket: %d", iPort);
return ANETOPENFAIL;
}
status =
setsockopt(socke.socket, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(int));
if (status < 0) {
anetLog(ANETERROR,
"Failed to open server port: setsockopt: %d, errno = %d",
iPort, errno);
return ANETOPENFAIL;
}
memset(&addresse, 0, sizeof(struct sockaddr_in));
addresse.sin_family = AF_INET;
addresse.sin_addr.s_addr = htonl(INADDR_ANY);
addresse.sin_port = htons(iPort);
status = bind(socke.socket, (struct sockaddr *) &addresse,
sizeof(struct sockaddr_in));
if (status < 0) {
anetLog(ANETERROR, "Failed to open server port: bind: %d, errno = %d",
iPort, errno);
return ANETOPENFAIL;
}
status = listen(socke.socket, 8);
if (status < 0) {
anetLog(ANETERROR, "Failed to open server port: listen: %d", iPort);
return ANETOPENFAIL;
}
socke.type = SERVERSOCKET;
socke.handle = handleID;
handleID++;
socke.readCallback = cb;
socke.userData = userData;
socke.lastOpenForWrite = time(NULL);
connections[noConnections] = socke;
noConnections++;
sortConnections();
anetLog(ANETCON, "Opened server port %d", iPort);
return socke.handle;
}
/*-----------------------------------------------------------------------*/
int ANETregisterSocket(int socket)
{
SocketDescriptor socke;
int flags, status;
if (noConnections >= MAXCONNECTIONS) {
anetLog(ANETERROR, "Maximum number of connections exceeded");
return ANETOUTOFSOCKETS;
}
memset(&socke, 0, sizeof(SocketDescriptor));
flags = fcntl(socket, F_GETFL, 0);
status = fcntl(socket, F_SETFL, flags | O_NONBLOCK);
if (status < 0) {
return ANETSOCKERROR;
}
socke.readBuffer = MakeRWPuffer(RBUFFERSIZE);
socke.writeBuffer = MakeRWPuffer(WBUFFERSIZE);
if (socke.readBuffer == NULL || socke.writeBuffer == NULL) {
return ANETMEM;
}
socke.socket = socket;
socke.handle = handleID;
handleID++;
socke.type = DATASOCKET;
socke.lastOpenForWrite = time(NULL);
connections[noConnections] = socke;
noConnections++;
sortConnections();
return socke.handle;
}
/*-------------------------------------------------------------------------*/
int ANETconnect(char *name, int iPort)
{
struct in_addr addr;
struct sockaddr_in addresse;
struct hostent *host;
int socke, status;
/* check for aaa.bbb.ccc.ddd first */
addr.s_addr = inet_addr(name);
if (addr.s_addr < 0) {
host = gethostbyname(name);
if (host == NULL) {
anetLog(ANETERROR, "Failed to locate host: %s", name);
return ANETOPENFAIL;
}
memcpy(&addr, host->h_addr_list, sizeof(struct in_addr));
}
memset(&addresse, 0, sizeof(struct sockaddr_in));
addresse.sin_family = AF_INET;
addresse.sin_port = iPort;
addresse.sin_addr = addr;
socke = socket(AF_INET, SOCK_STREAM, 0);
status = connect(socke, (struct sockaddr *) &addresse,
sizeof(struct sockaddr_in));
if (status < 0) {
anetLog(ANETERROR, "Failed to open socket to %s:%d", name, iPort);
return ANETOPENFAIL;
}
anetLog(ANETCON, "Opened socket %d to %s:%d", socke, name, iPort);
return ANETregisterSocket(socke);
}
/*--------------------------------------------------------------------------*/
void ANETclose(int handle)
{
pSocketDescriptor socke = NULL;
socke = findSocketDescriptor(handle);
if (socke == NULL) {
return;
}
close(socke->socket);
anetLog(ANETCON, "Closed socket %d", socke->socket);
if (socke->readBuffer != NULL) {
KillRWBuffer(socke->readBuffer);
}
if (socke->writeBuffer != NULL) {
KillRWBuffer(socke->writeBuffer);
}
if (socke->userData && socke->killUser) {
socke->killUser(socke->userData);
}
if (noConnections > 1) {
*socke = connections[noConnections - 1];
noConnections--;
sortConnections();
} else {
noConnections = 0;
}
}
/*--------------------------------------------------------------------------*/
static int anetWrite(SocketDescriptor con)
{
int status, length;
void *pPtr;
con.lastOpenForWrite = time(NULL);
pPtr = GetRWBufferData(con.writeBuffer, &length);
if (length > 0) {
status = send(con.socket, pPtr, length, 0);
if (status < 0) {
if (errno == EAGAIN) {
return 1;
}
ANETclose(con.handle);
return 0;
}
RemoveRWBufferData(con.writeBuffer, status);
}
return 1;
}
/*--------------------------------------------------------------------------*/
static int anetRead(SocketDescriptor con)
{
int socke, handle, status;
unsigned int len;
struct sockaddr addresse;
char buffer[8192];
switch (con.type) {
case SERVERSOCKET:
len = sizeof(struct sockaddr);
socke = accept(con.socket, &addresse, &len);
if (socke < 0) {
return 1;
}
handle = ANETregisterSocket(socke);
if (handle > 0) {
status = con.readCallback(handle, con.userData);
if (status != 1) {
ANETclose(handle);
return 0;
}
}
anetLog(ANETCON, "Accepted socket %d on port %d, handle %d",
socke, con.socket, handle);
break;
case DATASOCKET:
memset(buffer, 0, 8192);
status = recv(con.socket, buffer, 8192, 0);
if (status < 0) {
if (errno == EAGAIN) {
return 1;
}
ANETclose(con.handle);
return 0;
} else if (status == 0) {
/* this means EOF */
ANETclose(con.handle);
return 0;
} else {
status = StoreRWBuffer(con.readBuffer, buffer, status);
if (status != 1) {
anetLog(ANETERROR, "Read buffer overrun at handle %d, socket %d",
con.handle, con.socket);
}
if (con.readCallback != NULL) {
con.readCallback(con.handle, con.userData);
}
}
break;
}
return 1;
}
/*---------------------------------------------------------------------------*/
void ANETprocess(void)
{
int i, status, count = 0, socke = 0;
fd_set readMask, writeMask;
struct timeval tmo = { 0, 10 };
FD_ZERO(&readMask);
FD_ZERO(&writeMask);
for (i = 0; i < noConnections; i++) {
socke = connections[i].socket;
FD_SET(socke, &readMask);
FD_SET(socke, &writeMask);
if (socke > count) {
count = socke;
}
}
count++;
status = select(count, &readMask, &writeMask, NULL, &tmo);
if (status < 0) {
if (errno == EINTR) {
return;
}
return;
}
/**
* I always jump out of this loop when a socket is created or closed
* because then the order in the connections array is no longer valid.
* Try again the next time round.
*/
for (i = 0; i < noConnections; i++) {
socke = connections[i].socket;
if (FD_ISSET(socke, &readMask)) {
if (!anetRead(connections[i])) {
return;
}
}
if (FD_ISSET(socke, &writeMask)) {
if (!anetWrite(connections[i])) {
return;
}
} else {
/*
* if I could not write to the socket for three minutes,
* the socket is considered broken and is closed
*/
if (time(NULL) > connections[i].lastOpenForWrite + 180 &&
connections[i].type == DATASOCKET) {
ANETclose(connections[i].handle);
return;
}
}
}
}
/*--------------------------------------------------------------------------*/
int ANETvalidHandle(int handle)
{
pSocketDescriptor con = NULL;
con = findSocketDescriptor(handle);
if (con != NULL) {
return ANETOK;
} else {
return 0;
}
}
/*---------------------------------------------------------------------------*/
int ANETinfo(int handle, char *hostname, int hostnameLen)
{
pSocketDescriptor con = NULL;
struct sockaddr_in sin;
struct hostent *host;
socklen_t len;
con = findSocketDescriptor(handle);
if (con == NULL) {
return ANETDISCONNECTED;
} else {
if (getpeername(con->socket, (struct sockaddr *) &sin, &len) < 0) {
return ANETSOCKERROR;
}
if ((host = gethostbyaddr((char *) &sin.sin_addr,
sizeof(sin.sin_addr), AF_INET)) == NULL) {
return ANETSOCKERROR;
}
memset(hostname, 0, hostnameLen);
strncpy(hostname, host->h_name, hostnameLen);
}
return 1;
}
/*---------------------------------------------------------------------------*/
int ANETwrite(int handle, void *buffer, int count)
{
pSocketDescriptor con = NULL;
int status;
con = findSocketDescriptor(handle);
if (con == NULL) {
return ANETDISCONNECTED;
} else {
status = StoreRWBuffer(con->writeBuffer, buffer, count);
/*
first try if ANETprocess can write some and free the buffer
before giving up
*/
if (status != 1) {
ANETprocess();
status = StoreRWBuffer(con->writeBuffer, buffer, count);
}
if (status != 1) {
anetLog(ANETERROR, "write buffer overrun on handle %d, socket %d",
con->handle, con->socket);
return ANETWRITEBUFFERFULL;
}
}
return ANETOK;
}
/*---------------------------------------------------------------------------*/
int ANETread(int handle, void *buffer, int bufferLength)
{
pSocketDescriptor con = NULL;
int status, length, len;
void *data = NULL;
con = findSocketDescriptor(handle);
if (con == NULL) {
return ANETDISCONNECTED;
} else {
data = GetRWBufferData(con->readBuffer, &length);
if (length == 0) {
len = 0;
} else if (length >= bufferLength) {
len = bufferLength;
} else {
len = length;
}
if (len > 0) {
memcpy(buffer, data, len);
}
}
return len;
}
/*---------------------------------------------------------------------------*/
void *ANETreadPtr(int handle, int *length)
{
pSocketDescriptor con = NULL;
void *data = NULL;
con = findSocketDescriptor(handle);
if (con == NULL) {
return NULL;
} else {
data = GetRWBufferData(con->readBuffer, length);
return data;
}
}
/*---------------------------------------------------------------------------*/
void ANETreadConsume(int handle, int count)
{
pSocketDescriptor con = NULL;
con = findSocketDescriptor(handle);
if (con == NULL) {
return;
} else {
RemoveRWBufferData(con->readBuffer, count);
}
}
/*---------------------------------------------------------------------------*/
void ANETsetReadCallback(int handle, ANETcallback cb,
void *userData, ANETkill killUser)
{
pSocketDescriptor con = NULL;
con = findSocketDescriptor(handle);
if (con == NULL) {
return;
} else {
con->readCallback = cb;
con->userData = userData;
con->killUser = killUser;
}
}
/*----------------------------------------------------------------------------*/
int ANETreadTillTerm(int handle,
ANETtermCallback tcb, void *termData,
ANETwait wcb, void *waitData, char **buffer)
{
pSocketDescriptor con = NULL;
char *data;
int length, status;
while (wcb(waitData) > 0) {
ANETprocess();
con = findSocketDescriptor(handle);
if (con == NULL) {
return ANETDISCONNECTED;
}
data = GetRWBufferData(con->readBuffer, &length);
if (length > 0) {
status = tcb(data, length, termData);
if (status > 0) {
*buffer = malloc(status * sizeof(char));
if (*buffer != NULL) {
memcpy(*buffer, data, status);
}
RemoveRWBufferData(con->readBuffer, status);
return ANETOK;
}
}
}
return ANETTIMEOUT;
}

209
asynnet.h Normal file
View File

@ -0,0 +1,209 @@
/**
* Asynchronous networking for SICS and other programs. This module centrally manages
* a number of network connections for a client program. It is a layer between the
* program and the network which manages non blocking network I/O. To this purpose, the
* client program has to call ANETprocess at convenient intervalls. This module
* has a couple of features:
* - Connections are abstracted to handles which are guranteed to be unique
* rather then socket numbers. Socket numbers may be reused by the OS.
* - This module allows upper level code to figure out if a connection is still
* connected or not.
* - This module introduces a buffer layer between the socket and the application.
* Thus the upper layer does not have to worry much about I/O blocking. This
* is taken care of by this module both for reading and writing.
* - All I/O is non blocking.
* - This module can detect if a client is hanging and close the connection then.
* Hanging is detected by not being able to write to the client for some period
* of time.
*
* copyright: see file COPYRIGHT
*
* Mark Koennecke, January 2009
*/
#ifndef ASYNNET_H_
#define ASYNNET_H_
/*=================== error codes ========================================*/
#define ANETOK 1
#define ANETDISCONNECTED -10000
#define ANETWRITEBUFFERFULL -10001
#define ANETTIMEOUT -10002
#define ANETOPENFAIL -10003
#define ANETSOCKERROR -10004
#define ANETMEM -10005
#define ANETOUTOFSOCKETS -10006
/*================== log levels ==========================================*/
#define ANETNONE 0
#define ANETERROR 1
#define ANETIO 2
#define ANETCON 3
/*================== callback functions ==================================*/
/**
* \brief Callback called when a connection has been accepted on a
* port or data is ready.
* \param handle The handle of the new network connection
* \return 1 if the new connection can be accepted or 0 if not.
*/
typedef int (*ANETcallback) (int handle, void *userData);
/**
* \brief a callback which is called in order to determine if a
* a terminator is present in the data.
* \param data The data to inspect
* \param length The length of the data to inspect
* \param userData An opaque pointer passed through to this
* function
* \return -1 when no terminator is in the data, an integer pointing
* to after the terminator in data (in bytes).
*/
typedef int (*ANETtermCallback) (void *data, int length, void *userData);
/**
* \brief a callback function for waiting on some event.
* This is typically called to do something else while waiting
* for data to arrive. It can also return a negative return value,
* which will effectively implement a timeout.
* \param userData An opaque pointer passed through to this callback
* \return 1 to continue waiting, -1 for a timeout.
*/
typedef int (*ANETwait) (void *userData);
/**
* \brief callback to log events in ANET
* \param level The level of the logging message
* \param txt The logging data
* \param userData An opaque pointer passed through to ANETlog
*/
typedef void (*ANETlog) (int level, char *txt, void *userData);
/**
* \brief a callback for killing userdata associated with a read callback.
* This is called in ANETclose, if defined.
* \param userData The user data to kill.
*/
typedef void (*ANETkill) (void *userData);
/*===================== open/close functions =============================*/
/**
* * \brief open a server port
* \param iPort The port number at which to listen for
* connections.
* \param cb A callback which will be called whenever a new connection
* has been accepted on this port.
* \prama userData An opaque pointer to be passed as an argument to the
* callback function.
* \return A handle for the server port or a negative error code.
*/
int ANETopenServerPort(int iPort, ANETcallback cb, void *userData);
/**
* \brief open a client connection to a server.
* \param name the computer name of the server
* \param iPort The port number at which the server is listening
* \return A handle to the open port or a negative error code.
*/
int ANETconnect(char *name, int iPort);
/**
* \brief register a socket to be managed by this module. The socket
* may have been obtained by any means.
* \param socket The file descriptor of the socket
* \return A handle to use for this socket later on.
*/
int ANETregisterSocket(int socket);
/**
* \brief close a connection
* \param handle The handle of the connection
*/
void ANETclose(int handle);
/**
* \brief This function drives I/O processing, i.e. reading and writing.
* This has to be called by the client of this module at regular intervalls.
*/
void ANETprocess(void);
/**
* \brief tests if a handle is still a valid connection
* \param handle The handle to test.
* \return 1 if this is still a connected socket, 0 else.
*/
int ANETvalidHandle(int handle);
/**
* \brief figure out to which host we are connected.
* \param handle The connection handle
* \param hostname a buffer to copy the hostname into
* \param hostNameLen the length of the hostname buffer
* \return 1 on success, a negative error code else.
*/
int ANETinfo(int handle, char *hostname, int hostNameLen);
/*=================== I/O functions ===========================================
* For reading there are possibilities:
* - Raw reading happens through the combination of
* ANETread and ANETreadConsume.
* - Another way for raw reading is to register a read callback which is
* called anytime new data arrives.
* - The usual case is to wait for a line of terminated command input. This is
* done through ANETreadTillterm.
* ==========================================================================*/
/**
* \brief write to the network
* \param handle The handle for the connection
* \param buffer A pointer to the data to write
* \param count The number of bytes to write.
* \return 1 on success, 0 on failure
*/
int ANETwrite(int handle, void *buffer, int count);
/**
* \brief copy at max bufferLength bytes into buffer. The data is not deleted from
* the read buffer yet.
* \param handle The handle of the connection to read from
* \param buffer a pointer to an area for holding the data
* \param bufferLength The maximum number of bytes which can be copied into
* the buffer.
* \return The number of bytes copied. Can be 0 if no data is available. On
* errors a negative error code is returned.
*/
int ANETread(int handle, void *buffer, int bufferLength);
/**
* \brief Get a pointer to the data which has been read up to now.
* Do not mess with the data!! Else the result may be badly defined!
* \param handle The handle for the connection
* \param length will be set to the length of the data read so far.
* \return NULL when the socket is disconnected, a pointer else.
*/
void *ANETreadPtr(int handle, int *length);
/**
* \brief remove count bytes from the read buffer.
* \param handle The handle for the connection.
* \param count The number of bytes which can be removed from the
* read buffer.
*/
void ANETreadConsume(int handle, int count);
/**
* \brief set a callback to be called when data is available at the port.
* \param handle The handle of the connection
* \param cb The callback function to call
* \param userData An opaque pointer passed on as a parameter to the
* callback function.
*/
void ANETsetReadCallback(int handle, ANETcallback cb, void *userData,
ANETkill killUser);
/**
* \brief wait for terminated data to arrive.
* \param handle The connection handle to read from
* \param tcb a callback function which determines if a terminator is in the
* data.
* \param termData An opaque data pointer passed on to tcb
* \param wcb A callback function called while waiting for data
* to arrive.
* \param waitData An opaque pointer passed on to wcb
* \param buffer A newly allocated buffer holding the data as read
* including the terminator.
* \return 1 on success, a negative error code else.
*/
int ANETreadTillTerm(int handle,
ANETtermCallback tcb, void *termData,
ANETwait wcb, void *waitData, char **buffer);
/**
* Note to Markus: suitable callbacks for the standard case: waiting for \n and
* TaskYield for waiting to a timeout will become part of nread.h, .c.
*/
/*========================== system ====================================*/
/**
* \brief install a logging function
* \param lcb The logging function to install
* \param userData An opaque pointer with data for lcb
*/
void ANETsetLog(ANETlog lcb, void *userData);
#endif /*ASYNNET_H_ */

View File

@ -476,7 +476,7 @@ void sycformat(char *tag, OutCode msgFlag, pDynString msgString,
int SCWriteSycamore(SConnection * pCon, char *pBuffer, int iOut)
{
int iRet;
char pBueffel[MAXMSG], *pBufferFrom, *pBufferTo;
char pBueffel[MAXMSG];
long taskID = 0;
/* char pPrefix[40];*/
pDynString pMsg = NULL;
@ -487,15 +487,6 @@ int SCWriteSycamore(SConnection * pCon, char *pBuffer, int iOut)
if (strlen(pBuffer) == 0) {
return 0;
}
/* Strip \r and \n */
for (pBufferFrom = pBufferTo = pBuffer;; pBufferFrom++) {
if (*pBufferFrom == '\r' || *pBufferFrom == '\n')
continue;
*pBufferTo = *pBufferFrom;
if (*pBufferTo == '\0')
break;
pBufferTo++;
}
if (!SCVerifyConnection(pCon)) {
return 0;

View File

@ -1005,9 +1005,17 @@ static int SctMakeController(SConnection * con, SicsInterp * sics,
return 0;
}
parent = FindHdbParent(NULL, argv[1], &nodeName, con);
if (parent == NULL)
return 0; /* error message already written */
/*
* Install into the Hipadaba when full path given
*/
if(strstr(argv[1],"/") != NULL){
parent = FindHdbParent(NULL, argv[1], &nodeName, con);
if (parent == NULL)
return 0; /* error message already written */
} else {
nodeName = argv[1];
parent = NULL;
}
controller = calloc(1, sizeof(*controller));
assert(controller);
@ -1022,7 +1030,9 @@ static int SctMakeController(SConnection * con, SicsInterp * sics,
ccmd->pPrivate = controller;
ccmd->KillPrivate = SctKillController;
AddHipadabaChild(parent, controller->node, con);
if(parent != NULL){
AddHipadabaChild(parent, controller->node, con);
}
controller->devser = DevMake(con, argc - 2, argv + 2);
if (!controller->devser)

View File

@ -3310,7 +3310,7 @@ static int GetSICSHdbProperty(SConnection * pCon, SicsInterp * pSics,
}
status = GetHdbProperty(targetNode, argv[2], buffer, 511);
if (status != 1) {
SCPrintf(pCon, eValue, "ERROR: property %s not found", argv[2]);
SCPrintf(pCon, eError, "ERROR: property %s not found", argv[2]);
return 0;
}
SCPrintf(pCon, eValue, "%s.%s = %s", argv[1], argv[2], buffer);