From b75e058620e615842132ff671a17cd7607dcda09 Mon Sep 17 00:00:00 2001 From: Matej Sekoranja Date: Mon, 13 Oct 2014 09:15:04 +0200 Subject: [PATCH] pvlist utility added --- pvAccessCPP.files | 1 + pvtoolsSrc/Makefile | 4 + pvtoolsSrc/pvlist.cpp | 522 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 527 insertions(+) create mode 100644 pvtoolsSrc/pvlist.cpp diff --git a/pvAccessCPP.files b/pvAccessCPP.files index 19574db..096a09e 100644 --- a/pvAccessCPP.files +++ b/pvAccessCPP.files @@ -542,3 +542,4 @@ testApp/utils/testAtomicBoolean.cpp testApp/utils/testHexDump.cpp testApp/utils/testInetAddressUtils.cpp testApp/utils/transportRegistryTest.cpp +pvtoolsSrc/pvlist.cpp diff --git a/pvtoolsSrc/Makefile b/pvtoolsSrc/Makefile index 400de95..055d918 100644 --- a/pvtoolsSrc/Makefile +++ b/pvtoolsSrc/Makefile @@ -14,6 +14,10 @@ PROD_HOST += pvinfo pvinfo_SRCS += pvinfo.cpp pvinfo_LIBS += pvAccess pvData pvMB Com +PROD_HOST += pvlist +pvlist_SRCS += pvlist.cpp +pvlist_LIBS += pvAccess pvData pvMB Com + PROD_HOST += eget eget_SRCS += eget.cpp eget_LIBS += pvAccess pvData pvMB ca Com diff --git a/pvtoolsSrc/pvlist.cpp b/pvtoolsSrc/pvlist.cpp new file mode 100644 index 0000000..7d8d550 --- /dev/null +++ b/pvtoolsSrc/pvlist.cpp @@ -0,0 +1,522 @@ + +#include + +#include +#include +#include +#include +#include +#include +#include + + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace std::tr1; + +using namespace epics::pvData; +using namespace epics::pvAccess; + +/// Byte to hexchar mapping. +static const char lookup[] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + +/// Get hex representation of byte. +string toHex(int8* ba, size_t len) { + string sb; + + for (size_t i = 0; i < len; i++) + { + int8 b = ba[i]; + + int upper = (b>>4)&0x0F; + sb += lookup[upper]; + + int lower = b&0x0F; + sb += lookup[lower]; + } + + return sb; +} + + +static string emptyString; + +std::size_t readSize(ByteBuffer* buffer) { + int8 b = buffer->getByte(); + if(b==-1) + return -1; + else if(b==-2) { + int32 s = buffer->getInt(); + if(s<0) THROW_BASE_EXCEPTION("negative size"); + return s; + } + else + return (std::size_t)(b<0 ? b+256 : b); +} + +string deserializeString(ByteBuffer* buffer) { + + std::size_t size = /*SerializeHelper::*/readSize(buffer); + if(size!=(size_t)-1) // TODO null strings check, to be removed in the future + { + // entire string is in buffer, simply create a string out of it (copy) + std::size_t pos = buffer->getPosition(); + string str(buffer->getArray()+pos, size); + buffer->setPosition(pos+size); + return str; + } + else + return emptyString; +} + +struct ServerEntry { + string guid; + string protocol; + vector addresses; + int8 version; +}; + + +typedef map ServerMap; +static ServerMap serverMap; + +void processSearchResponse(osiSockAddr const & responseFrom, ByteBuffer & receiveBuffer) +{ + // first byte is PVA_MAGIC + int8 magic = receiveBuffer.getByte(); + if(magic != PVA_MAGIC) + return; + + // second byte version + int8 version = receiveBuffer.getByte(); + + // only data for UDP + int8 flags = receiveBuffer.getByte(); + if (flags < 0) + { + // 7-bit set + receiveBuffer.setEndianess(EPICS_ENDIAN_BIG); + } + else + { + receiveBuffer.setEndianess(EPICS_ENDIAN_LITTLE); + } + + // command ID and paylaod + int8 command = receiveBuffer.getByte(); + if (command != (int8)0x04) + return; + + size_t payloadSize = receiveBuffer.getInt(); + if (payloadSize < (12+4+16+2)) + return; + + + GUID guid; + receiveBuffer.get(guid.value, 0, sizeof(guid.value)); + + /*int32 searchSequenceId = */receiveBuffer.getInt(); + + osiSockAddr serverAddress; + serverAddress.ia.sin_family = AF_INET; + + // 128-bit IPv6 address + if (!decodeAsIPv6Address(&receiveBuffer, &serverAddress)) return; + + // accept given address if explicitly specified by sender + if (serverAddress.ia.sin_addr.s_addr == INADDR_ANY) + serverAddress.ia.sin_addr = responseFrom.ia.sin_addr; + + serverAddress.ia.sin_port = htons(receiveBuffer.getShort()); + + string protocol = /*SerializeHelper::*/deserializeString(&receiveBuffer); + + /*bool found =*/ receiveBuffer.getByte(); // != 0; + + + string guidString = toHex((int8*)guid.value, sizeof(guid.value)); + + ServerMap::iterator iter = serverMap.find(guidString); + if (iter != serverMap.end()) + { + bool found = false; + vector& vec = iter->second.addresses; + for (vector::const_iterator ai = vec.begin(); + ai != vec.end(); + ai++) + if (sockAddrAreIdentical(&(*ai), &serverAddress)) + { + found = true; + break; + } + + if (!found) + vec.push_back(serverAddress); + } + else + { + ServerEntry serverEntry; + serverEntry.guid = guidString; + serverEntry.protocol = protocol; + serverEntry.addresses.push_back(serverAddress); + serverEntry.version = version; + + serverMap[guidString] = serverEntry; + } + + return; +} + +bool discoverServers(double timeOut) +{ + osiSockAttach(); + + SOCKET socket = epicsSocketCreate(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (socket == INVALID_SOCKET) + { + char errStr[64]; + epicsSocketConvertErrnoToString(errStr, sizeof(errStr)); + fprintf(stderr, "Failed to create a socket: %s\n", errStr); + return false; + } + + // + // read config + // + + Configuration::shared_pointer configuration(new SystemConfigurationImpl()); + + string addressList = configuration->getPropertyAsString("EPICS_PVA_ADDR_LIST", ""); + bool autoAddressList = configuration->getPropertyAsBoolean("EPICS_PVA_AUTO_ADDR_LIST", true); + int broadcastPort = configuration->getPropertyAsInteger("EPICS_PVA_BROADCAST_PORT", PVA_BROADCAST_PORT); + + // quary broadcast addresses of all IFs + auto_ptr broadcastAddresses(getBroadcastAddresses(socket, broadcastPort)); + + // set broadcast address list + if (!addressList.empty()) + { + // if auto is true, add it to specified list + InetAddrVector* appendList = 0; + if (autoAddressList) + appendList = broadcastAddresses.get(); + + auto_ptr list(getSocketAddressList(addressList, broadcastPort, appendList)); + if (list.get() && list->size()) { + // delete old list and take ownership of a new one + broadcastAddresses = list; + } + } + + for (size_t i = 0; broadcastAddresses.get() && i < broadcastAddresses->size(); i++) + LOG(logLevelDebug, + "Broadcast address #%d: %s.", i, inetAddressToString((*broadcastAddresses)[i]).c_str()); + + // --- + + int optval = 1; + int status = ::setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (char *)&optval, sizeof(optval)); + if (status) + { + char errStr[64]; + epicsSocketConvertErrnoToString(errStr, sizeof(errStr)); + fprintf(stderr, "Error setting SO_BROADCAST: %s\n", errStr); + epicsSocketDestroy (socket); + return false; + } + + osiSockAddr bindAddr; + bindAddr.ia.sin_family = AF_INET; + bindAddr.ia.sin_port = htons(0); + bindAddr.ia.sin_addr.s_addr = htonl(INADDR_ANY); + + status = ::bind(socket, (sockaddr*)&(bindAddr.sa), sizeof(sockaddr)); + if (status) + { + char errStr[64]; + epicsSocketConvertErrnoToString(errStr, sizeof(errStr)); + fprintf(stderr, "Failed to bind: %s\n", errStr); + epicsSocketDestroy(socket); + return false; + } + + osiSockAddr responseAddress; + osiSocklen_t sockLen = sizeof(sockaddr); + // read the actual socket info + status = ::getsockname(socket, &responseAddress.sa, &sockLen); + if (status) { + char errStr[64]; + epicsSocketConvertErrnoToString(errStr, sizeof(errStr)); + fprintf(stderr, "Failed to get local socket address: %s.", errStr); + return false; + } + + char buffer[1024]; + ByteBuffer sendBuffer(buffer, sizeof(buffer)/sizeof(char)); + + sendBuffer.putByte(PVA_MAGIC); + sendBuffer.putByte(PVA_VERSION); + sendBuffer.putByte((EPICS_BYTE_ORDER == EPICS_ENDIAN_BIG) ? 0x80 : 0x00); // data + 7-bit endianess + sendBuffer.putByte((int8_t)3); // search + sendBuffer.putInt(4+1+3+16+2+1); // "zero" payload + + sendBuffer.putInt(0); // sequenceId + sendBuffer.putByte((int8_t)0x81); // reply required // TODO unicast vs multicast; for now we mark ourselves as unicast + sendBuffer.putByte((int8_t)0); // reserved + sendBuffer.putShort((int16_t)0); // reserved + + // NOTE: is it possible (very likely) that address is any local address ::ffff:0.0.0.0 + encodeAsIPv6Address(&sendBuffer, &responseAddress); + sendBuffer.putShort((int16_t)ntohs(responseAddress.ia.sin_port)); + + sendBuffer.putByte((int8_t)0x00); // no restriction on protocol + sendBuffer.putShort((int16_t)0); // count + + bool oneOK = false; + for (size_t i = 0; i < broadcastAddresses->size(); i++) + { + // send the packet + status = ::sendto(socket, sendBuffer.getArray(), sendBuffer.getPosition(), 0, + &((*broadcastAddresses)[i].sa), sizeof(sockaddr)); + if (status < 0) + { + char errStr[64]; + epicsSocketConvertErrnoToString(errStr, sizeof(errStr)); + fprintf(stderr, "Send error: %s\n", errStr); + } + else + oneOK = true; + } + + if (!oneOK) + return false; + + + // set timeout in case message is not sent + struct timeval timeout; + memset(&timeout, 0, sizeof(struct timeval)); + timeout.tv_sec = 1; + timeout.tv_usec = 0; + + status = ::setsockopt (socket, SOL_SOCKET, SO_RCVTIMEO, + (char*)&timeout, sizeof(timeout)); + if (status) + { + char errStr[64]; + epicsSocketConvertErrnoToString(errStr, sizeof(errStr)); + fprintf(stderr, "Error setting SO_RCVTIMEO: %s\n", errStr); + return false; + } + + char rxbuff[1024]; + ByteBuffer receiveBuffer(rxbuff, sizeof(rxbuff)/sizeof(char)); + + osiSockAddr fromAddress; + osiSocklen_t addrStructSize = sizeof(sockaddr); + + while (true) + { + receiveBuffer.clear(); + + // receive packet from socket + int bytesRead = ::recvfrom(socket, (char*)receiveBuffer.getArray(), + receiveBuffer.getRemaining(), 0, + (sockaddr*)&fromAddress, &addrStructSize); + if (bytesRead > 0) + { + receiveBuffer.setPosition(bytesRead); + receiveBuffer.flip(); + + processSearchResponse(fromAddress, receiveBuffer); + + } + else if (status <= 0) + { + if (status == -1) + { + int socketError = SOCKERRNO; + + // interrupted or timeout + if (socketError == SOCK_EINTR || + socketError == EAGAIN || // no alias in libCom + // windows times out with this + //socketError == SOCK_ETIMEDOUT || + socketError == SOCK_EWOULDBLOCK) + continue; + + if (socketError == SOCK_ECONNREFUSED || // avoid spurious ECONNREFUSED in Linux + socketError == SOCK_ECONNRESET) // or ECONNRESET in Windows + continue; + + char errStr[64]; + epicsSocketConvertErrnoToString(errStr, sizeof(errStr)); + fprintf(stderr, "Socket recv error: %s\n", errStr); + } + break; + } + + } + + + for (ServerMap::const_iterator iter = serverMap.begin(); + iter != serverMap.end(); + iter++) + { + const ServerEntry& entry = iter->second; + + cout << "GUID 0x" << entry.guid << ", version " << entry.version << ": " + << entry.protocol << "@["; + + size_t count = entry.addresses.size(); + for (size_t i = 0; i < count; i++) + { + cout << inetAddressToString(entry.addresses[i]); + if (i < (count-1)) + cout << ", "; + } + cout << ']' << endl; + } + + // TODO shutdown sockets? + // TODO this resouce is not released on failure + epicsSocketDestroy(socket); + + return true; +} + + +#define DEFAULT_TIMEOUT 3.0 + +void usage (void) +{ + fprintf (stderr, "\nUsage: pvlist [options] ...\n\n" + " -h: Help: Print this message\n" + "options:\n" + " -w : Wait time, specifies timeout, default is %f second(s)\n" + " -q: Quiet mode, print only error messages\n" + " -d: Enable debug output\n" + " -F : Use as an alternate output field separator\n" +// " -f : Use as an input that provides a list input parameters(s) to be read, use '-' for stdin\n" + "\nexample: pvinfo ...\n\n" + , DEFAULT_TIMEOUT); +} + + +/*+************************************************************************** + * + * Function: main + * + * Description: pvlist main() + * Evaluate command line options, ... + * + * Arg(s) In: [options] []... + * + * Arg(s) Out: none + * + * Return(s): Standard return code (0=success, 1=error) + * + **************************************************************************-*/ + +int main (int argc, char *argv[]) +{ + int opt; /* getopt() current option */ + bool debug = false; + bool quiet = false; + double timeOut = DEFAULT_TIMEOUT; + // char fieldSeparator = ' '; + + /* + istream* inputStream = 0; + ifstream ifs; + bool fromStream = false; + */ + setvbuf(stdout,NULL,_IOLBF,BUFSIZ); /* Set stdout to line buffering */ + + while ((opt = getopt(argc, argv, ":hw:qdF:f:")) != -1) { + switch (opt) { + case 'h': /* Print usage */ + usage(); + return 0; + case 'w': /* Set PVA timeout value */ + if(epicsScanDouble(optarg, &timeOut) != 1 || timeOut <= 0.0) + { + fprintf(stderr, "'%s' is not a valid timeout value " + "- ignored. ('pvlist -h' for help.)\n", optarg); + timeOut = DEFAULT_TIMEOUT; + } + break; + case 'q': /* Quiet mode */ + quiet = true; + break; + case 'd': /* Debug log level */ + debug = true; + break; + /* + case 'F': // Store this for output formatting + fieldSeparator = (char) *optarg; + break; + case 'f': // Use input stream as input + { + string fileName = optarg; + if (fileName == "-") + inputStream = &cin; + else + { + ifs.open(fileName.c_str(), ifstream::in); + if (!ifs) + { + fprintf(stderr, + "Failed to open file '%s'.\n", + fileName.c_str()); + return 1; + } + else + inputStream = &ifs; + } + + fromStream = true; + break; + }*/ + case '?': + fprintf(stderr, + "Unrecognized option: '-%c'. ('pvlist -h' for help.)\n", + optopt); + return 1; + case ':': + fprintf(stderr, + "Option '-%c' requires an argument. ('pvlist -h' for help.)\n", + optopt); + return 1; + default : + usage(); + return 1; + } + } + + SET_LOG_LEVEL(debug ? logLevelDebug : logLevelError); + + if (!quiet) + fprintf(stderr, "Searching...\n"); + + discoverServers(timeOut); + + if (!quiet) + fprintf(stderr, "done.\n"); + + return 0; +}