/* * Copyright information and license terms for this software can be * found in the file LICENSE that is included with the distribution */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(_WIN32) && !defined(_MINGW) FILE *popen(const char *command, const char *mode) { return _popen(command, mode); } int pclose(FILE *stream) { return _pclose(stream); } #endif using namespace std; using namespace epics::pvData; using namespace epics::pvAccess; namespace { /// 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; } 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->getBuffer()+pos, size); buffer->setPosition(pos+size); return str; } else return std::string(); } struct ServerEntry { string guid; string protocol; vector addresses; int8 version; }; typedef map ServerMap; static ServerMap serverMap; // return true if new server response is recevived bool processSearchResponse(osiSockAddr const & responseFrom, ByteBuffer & receiveBuffer) { // first byte is PVA_MAGIC int8 magic = receiveBuffer.getByte(); if(magic != PVA_MAGIC) return false; // second byte version int8 version = receiveBuffer.getByte(); if(version == 0) { // 0 -> 1 included incompatible changes return false; } // 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 false; size_t payloadSize = receiveBuffer.getInt(); if (payloadSize < (12+4+16+2)) return false; epics::pvAccess::ServerGUID guid; receiveBuffer.get(guid.value, 0, sizeof(guid.value)); /*int32 searchSequenceId = */receiveBuffer.getInt(); osiSockAddr serverAddress; memset(&serverAddress, 0, sizeof(serverAddress)); serverAddress.ia.sin_family = AF_INET; // 128-bit IPv6 address if (!decodeAsIPv6Address(&receiveBuffer, &serverAddress)) return false; // 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; // NOTE: htons might be a macro (e.g. vxWorks) int16 port = receiveBuffer.getShort(); serverAddress.ia.sin_port = htons(port); 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); return true; } else return false; } else { ServerEntry serverEntry; serverEntry.guid = guidString; serverEntry.protocol = protocol; serverEntry.addresses.push_back(serverAddress); serverEntry.version = version; serverMap[guidString] = serverEntry; return true; } } 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 InetAddrVector broadcastAddresses; { IfaceNodeVector ifaces; if(discoverInterfaces(ifaces, socket, 0)) { fprintf(stderr, "Unable to populate interface list\n"); return false; } for(IfaceNodeVector::const_iterator it(ifaces.begin()), end(ifaces.end()); it!=end; ++it) { if(it->validBcast && it->bcast.sa.sa_family == AF_INET) { osiSockAddr bcast = it->bcast; bcast.ia.sin_port = htons(broadcastPort); broadcastAddresses.push_back(bcast); } } } // set broadcast address list if (!addressList.empty()) { // if auto is true, add it to specified list InetAddrVector* appendList = 0; if (autoAddressList) appendList = &broadcastAddresses; InetAddrVector list; getSocketAddressList(list, addressList, broadcastPort, appendList); if (!list.empty()) { // delete old list and take ownership of a new one broadcastAddresses = list; } } for (size_t i = 0; i < broadcastAddresses.size(); i++) LOG(logLevelDebug, "Broadcast address #%zu: %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; memset(&bindAddr, 0, sizeof(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; } // set timeout #ifdef _WIN32 // ms DWORD timeout = 250; #else struct timeval timeout; memset(&timeout, 0, sizeof(struct timeval)); timeout.tv_sec = 0; timeout.tv_usec = 250000; #endif 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; } 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_CLIENT_PROTOCOL_REVISION); sendBuffer.putByte((EPICS_BYTE_ORDER == EPICS_ENDIAN_BIG) ? 0x80 : 0x00); // data + 7-bit endianess sendBuffer.putByte((int8_t)CMD_SEARCH); // search sendBuffer.putInt(4+1+3+16+2+1+2); // "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); // protocol count sendBuffer.putShort((int16_t)0); // name count bool oneOK = false; for (size_t i = 0; i < broadcastAddresses.size(); i++) { if(pvAccessIsLoggable(logLevelDebug)) { char strBuffer[64]; sockAddrToDottedIP(&broadcastAddresses[i].sa, strBuffer, sizeof(strBuffer)); LOG(logLevelDebug, "UDP Tx (%zu) -> %s", sendBuffer.getPosition(), strBuffer); } status = ::sendto(socket, sendBuffer.getBuffer(), 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; char rxbuff[1024]; ByteBuffer receiveBuffer(rxbuff, sizeof(rxbuff)/sizeof(char)); osiSockAddr fromAddress; osiSocklen_t addrStructSize = sizeof(sockaddr); int sendCount = 0; while (true) { receiveBuffer.clear(); // receive packet from socket int bytesRead = ::recvfrom(socket, (char*)receiveBuffer.getBuffer(), receiveBuffer.getRemaining(), 0, (sockaddr*)&fromAddress, &addrStructSize); if (bytesRead > 0) { if(pvAccessIsLoggable(logLevelDebug)) { char strBuffer[64]; sockAddrToDottedIP(&fromAddress.sa, strBuffer, sizeof(strBuffer)); LOG(logLevelDebug, "UDP Rx (%d) <- %s", bytesRead, strBuffer); } receiveBuffer.setPosition(bytesRead); receiveBuffer.flip(); processSearchResponse(fromAddress, receiveBuffer); } else { if (bytesRead == -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) { // OK } else if (socketError == SOCK_ECONNREFUSED || // avoid spurious ECONNREFUSED in Linux socketError == SOCK_ECONNRESET) // or ECONNRESET in Windows { // OK } else { // unexpected error char errStr[64]; epicsSocketConvertErrnoToString(errStr, sizeof(errStr)); fprintf(stderr, "Socket recv error: %s\n", errStr); break; } } if (++sendCount < 3) { // TODO duplicate code bool oneOK = false; for (size_t i = 0; i < broadcastAddresses.size(); i++) { // send the packet status = ::sendto(socket, sendBuffer.getBuffer(), 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; } else break; } } // 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" "\noptions:\n" " -h: Help: Print this message\n" " -V: Print version and exit\n" " -i Print server info (when server address list/GUID is given)\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" "\nexamples:\n" "\tpvlist\n" "\tpvlist ioc0001\n" "\tpvlist 10.5.1.205:10000\n" "\tpvlist 0x83DE3C540000000000BF351F\n\n" , DEFAULT_TIMEOUT); } }//namespace /*+************************************************************************** * * 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; double timeOut = DEFAULT_TIMEOUT; bool printInfo = false; /* istream* inputStream = 0; ifstream ifs; bool fromStream = false; */ setvbuf(stdout,NULL,_IOLBF,BUFSIZ); /* Set stdout to line buffering */ while ((opt = getopt(argc, argv, ":hVw:qdF:f:i")) != -1) { switch (opt) { case 'h': /* Print usage */ usage(); return 0; case 'V': /* Print version */ { fprintf(stdout, "pvAccess %u.%u.%u%s\n", EPICS_PVA_MAJOR_VERSION, EPICS_PVA_MINOR_VERSION, EPICS_PVA_MAINTENANCE_VERSION, (EPICS_PVA_DEVELOPMENT_FLAG)?"-SNAPSHOT":""); fprintf(stdout, "pvData %u.%u.%u%s\n", EPICS_PVD_MAJOR_VERSION, EPICS_PVD_MINOR_VERSION, EPICS_PVD_MAINTENANCE_VERSION, (EPICS_PVD_DEVELOPMENT_FLAG)?"-SNAPSHOT":""); fprintf(stdout, "Base %s\n", EPICS_VERSION_FULL); 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 */ break; case 'd': /* Debug log level */ debug = true; break; case 'i': /* Print server info */ printInfo = 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); bool noArgs = (optind == argc); bool byGUIDSearch = false; for (int i = optind; i < argc; i++) { string serverAddress = argv[i]; // by GUID search if (serverAddress.length() == 26 && serverAddress[0] == '0' && serverAddress[1] == 'x') { byGUIDSearch = true; break; } } bool allOK = true; if (noArgs || byGUIDSearch) discoverServers(timeOut); // just list all the discovered servers if (noArgs) { for (ServerMap::const_iterator iter = serverMap.begin(); iter != serverMap.end(); iter++) { const ServerEntry& entry = iter->second; cout << "GUID 0x" << entry.guid << " version " << (int)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; } } else { for (int i = optind; i < argc; i++) { string serverAddress = argv[i]; // by GUID search if (serverAddress.length() == 26 && serverAddress[0] == '0' && serverAddress[1] == 'x') { bool resolved = false; for (ServerMap::const_iterator iter = serverMap.begin(); iter != serverMap.end(); iter++) { const ServerEntry& entry = iter->second; if (strncmp(entry.guid.c_str(), &(serverAddress[2]), 24) == 0) { // found match // TODO for now we take only first server address serverAddress = inetAddressToString(entry.addresses[0]); resolved = true; break; } } if (!resolved) { fprintf(stderr, "Failed to resolve GUID '%s'!\n", serverAddress.c_str()); allOK = false; continue; } } StructureConstPtr argstype(getFieldCreate()->createFieldBuilder() ->setId("epics:nt/NTURI:1.0") ->add("scheme", pvString) ->add("path", pvString) ->addNestedStructure("query") ->add("op", pvString) ->endNested() ->createStructure()); PVStructure::shared_pointer args(getPVDataCreate()->createPVStructure(argstype)); args->getSubFieldT("scheme")->put("pva"); args->getSubFieldT("path")->put("server"); args->getSubFieldT("query.op")->put(printInfo ? "info" : "channels"); if(debug) { std::cerr<<"Query to "<getSubField("value")); PVStringArray::const_svector val(pvs->view()); std::copy(val.begin(), val.end(), std::ostream_iterator(std::cout, "\n")); } else { std::cout<