/**************************************************************\ Main program for the CTX G-APD HV supply, sends commands, reads and monitors status of the G-APD HV supply Sebastian Commichau, Sabrina Stark, Oliver Grimm \**************************************************************/ #include #include #include #include #include #include #include #include #include "ProcessIO.h" #include #include #define DEFAULT_CONFIG "../config/HV.conf" // Default configuration file #define LOCKFILE "/tmp/CTX_HV_LOCK" // Function prototypes void ConsoleCommand(ProcessIO *); void CCCommand(ProcessIO *); void HVMonitor(ProcessIO *); void SignalHandler(int); void CrashHandler(int); // ================ // Main program // ================ // // Several unlikely system call failures are handled via throwing an exception. int main(int argc, char *argv[]) { char str[MAX_COM_SIZE]; pthread_t thread_ConsoleCommand,thread_HVMonitor,thread_CCCommand; int LockDescriptor; // Interpret command line (do before lockfile creation in case of exit()) if((argc==3 && strcmp(argv[1],"-c")!=0) || argc==2) { printf("Usage: %s [-c ] Default file is \"%s\"\n", argv[0], DEFAULT_CONFIG); exit(EXIT_SUCCESS); } // Assure only one instance of the HV control program runs // The flag O_EXCL together with O_CREAT assure that the lock // file cannot be opened by another instance, i.e. there are no parallel write accesses if((LockDescriptor = open(LOCKFILE,O_WRONLY|O_CREAT|O_EXCL,S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)) == -1) { if(errno==EEXIST) { printf("Error: Lock file already existing\n"); sprintf(str,"paste %s -s -d ' '",LOCKFILE); system(str); } else printf("Could not create lock file %s (%s)\n", LOCKFILE, strerror(errno)); exit(EXIT_FAILURE); } close(LockDescriptor); sprintf(str,"echo Created >%s; date >>%s; echo by $USER@$HOSTNAME>>%s",LOCKFILE,LOCKFILE,LOCKFILE); system(str); system("clear"); printf("\n*** HV Control built %s, %s (S.Commichau, S.Stark, O.Grimm) ***\n\n",__DATE__,__TIME__); // Install signal handler and set signal SIGUSR1 to interrupt blocking system calls signal(SIGUSR1, &SignalHandler); siginterrupt (SIGUSR1, true); // Install signals to assure that the lock file is deleted in case of a program crash signal(SIGQUIT, &CrashHandler); signal(SIGILL, &CrashHandler); signal(SIGABRT, &CrashHandler); signal(SIGFPE, &CrashHandler); signal(SIGSEGV, &CrashHandler); signal(SIGBUS, &CrashHandler); signal(SIGTERM, &CrashHandler); signal(SIGINT, &CrashHandler); signal(SIGHUP, &CrashHandler); // Construct main instance and create mutex for thread synchronization ProcessIO pio(argc==3 ? argv[2] : DEFAULT_CONFIG); if (pthread_mutex_init(&pio.control_mutex, NULL) != 0) { perror("pthread_mutex_init failed"); throw; } // Create threads if ((pthread_create(&thread_ConsoleCommand, NULL, (void * (*)(void *)) ConsoleCommand,(void *) &pio)) != 0) { perror("pthread_create failed with console thread"); throw; } if ((pthread_create(&thread_HVMonitor, NULL, (void * (*)(void *)) HVMonitor,(void *) &pio)) != 0) { perror("pthread_create failed with HVMonitor thread"); throw; } if ((pthread_create(&thread_CCCommand, NULL, (void * (*)(void *)) CCCommand,(void *) &pio)) != 0) { perror("pthread_create failed with socket thread"); throw; } // Threads should be accessible for sending signals pio.HVMonitor = thread_HVMonitor; pio.SocketThread = thread_CCCommand; // Wait for threads to quit pthread_join(thread_CCCommand, NULL); pthread_join(thread_ConsoleCommand, NULL); pthread_join(thread_HVMonitor, NULL); // Destruct mutex and main instance pthread_mutex_destroy (&pio.control_mutex); pio.~ProcessIO(); // Remove lockfile if (remove(LOCKFILE)==-1) { sprintf(str, "Could not remove lock file %s", LOCKFILE); perror(str); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } /********************************************************************\ ConsoleCommand thread Handle console input using readline library functions to allow line editing and history capability \********************************************************************/ void ConsoleCommand(ProcessIO *m) { time_t Time; char Buf[MAX_COM_SIZE], *Command; while (!m->Exit) { // Assemble prompt snprintf(m->Prompt, sizeof(m->Prompt),"\rHV"); if (m->NumHVBoards == 0) sprintf(m->Prompt+strlen(m->Prompt),"> "); else { if (m->FirstChain == m->LastChain) sprintf(m->Prompt+strlen(m->Prompt),"|C%d",m->FirstChain); else sprintf(m->Prompt+strlen(m->Prompt),"|C%d-%d",m->FirstChain,m->LastChain); if (m->NumHVBoards == 0) sprintf(m->Prompt+strlen(m->Prompt),"> "); else if (m->FirstBoard == m->LastBoard) sprintf(m->Prompt+strlen(m->Prompt),"|B%d> ",m->FirstBoard); else snprintf(m->Prompt,sizeof(m->Prompt),"\rDAQ|B%d-%d> ",m->FirstBoard,m->LastBoard); } // Read Command Command = readline(m->Prompt); if (Command==NULL) { m->PrintMessage("Error reading command line input\n"); continue; } if(strlen(Command)>0) add_history(Command); // Log command strftime(Buf,MAX_COM_SIZE, "%d/%m/%y %X", localtime(&(Time=time(NULL)))); m->PrintMessage(MsgToLog, "CONSOLE(%s)> %s\n", Buf, Command); // Process command pthread_mutex_lock(&m->control_mutex); m->CommandControl(Command); pthread_mutex_unlock(&m->control_mutex); free(Command); } } /********************************************************************\ CCCommand thread Listen to commands from socket (Central Control) This thread will block execution in the accept() and read() socket function while waiting for a connection or data. If the exit function is invoked through keyboard command, these blocking functions are interrupted by raising the signal SIGUSR1. Testing on errno=EINTR indicates this termination. The dummy signal handler below is needed to prevent the standard thread termination occurring when this signal is received. \********************************************************************/ void CCCommand(ProcessIO *m) { int ServerSocket,ConnectionSocket,ReadResult; struct sockaddr_in SocketAddress, ClientAddress; struct hostent *ClientName; socklen_t SizeClientAddress=sizeof(ClientAddress); char Command[MAX_COM_SIZE], Buf[MAX_COM_SIZE]; time_t Time; // Set up server socket if ((ServerSocket = socket(PF_INET, SOCK_STREAM, 0)) == -1) { m->PrintMessage("Could not open server socket, no remote connection possible (%s).\n", strerror(errno)); return; } // Allows immediate reuse of socket after closing (circumvents TIME_WAIT) int Value=1; if (setsockopt(ServerSocket, SOL_SOCKET, SO_REUSEADDR, (char *) &Value, sizeof (Value)) == -1) { m->PrintMessage("Warning: Could not set server socket option SO_REUSEADDR (%s)\n", strerror(errno)); } SocketAddress.sin_family = PF_INET; SocketAddress.sin_port = htons((unsigned short) m->config->fCCPort); SocketAddress.sin_addr.s_addr = INADDR_ANY; if (bind(ServerSocket, (struct sockaddr *) &SocketAddress, sizeof(SocketAddress)) == -1) { m->PrintMessage("Could not bind port to socket (%s)\n", strerror(errno)); close(ServerSocket); return; } if (listen(ServerSocket, 0) == -1) { m->PrintMessage("Could not set socket to listening (%s)\n", strerror(errno)); close(ServerSocket); return; } while (!m->Exit) { // Looping to wait for incoming connection if ((ConnectionSocket = accept(ServerSocket, (struct sockaddr *) &ClientAddress, &SizeClientAddress)) == -1) { if (errno!=EINTR) m->PrintMessage("Failed to accept incoming connection (%s)\n", strerror(errno)); close(ServerSocket); return; } ClientName = gethostbyaddr((char *) &ClientAddress.sin_addr ,sizeof(struct sockaddr_in),AF_INET); m->PrintMessage("Connected to client at %s (%s).\n", inet_ntoa(ClientAddress.sin_addr), ClientName!=NULL ? ClientName->h_name:"name unknown"); m->Socket = ConnectionSocket; // Looping as long as client exists and program not terminated while (!m->Exit) { // Try to read command from socket memset(Command, 0, sizeof(Command)); ReadResult = read(ConnectionSocket, Command, MAX_COM_SIZE); if (ReadResult==0) break; // Client does not exist anymore if (ReadResult==-1) { if (errno!=EINTR) m->PrintMessage("Error read from socket (%s)\n", strerror(errno)); break; } if (Command[strlen(Command)-1]=='\n') Command[strlen(Command)-1]='\0'; // Remove trailing newline // Log command strftime(Buf, MAX_COM_SIZE, "%d/%m/%y %X", localtime(&(Time=time(NULL)))); m->PrintMessage(MsgToConsole|MsgToLog, "SOCKET(%s)> %s\n", Buf, Command); // Process command pthread_mutex_lock(&m->control_mutex); m->CmdFromSocket = true; m->CommandControl(Command); m->CmdFromSocket = false; pthread_mutex_unlock(&m->control_mutex); } m->Socket = -1; m->PrintMessage("Disconnected from client.\n"); close(ConnectionSocket); } close(ServerSocket); } /********************************************************************\ HVMonitor Monitor HV board status Sebastian Commichau, November 2008 \********************************************************************/ void HVMonitor(ProcessIO *m) { while (!m->Exit) { if (m->state == active) { pthread_mutex_lock(&m->control_mutex); m->Monitor(); pthread_mutex_unlock(&m->control_mutex); } usleep((unsigned long)floor(1000000./(m->NumHVBoards*m->fStatusRefreshRate))); } } /********************************************************************\ Signal handlers \********************************************************************/ // Remove lock file before running default signal code void CrashHandler(int Signal) { remove(LOCKFILE); printf("Caught signal number %d. Removing lockfile and performing standard signal action. Good luck.\n",Signal); signal(Signal, SIG_DFL); raise(Signal); } // Dummy signal handler to return from blocking syscalls void SignalHandler(int Signal) { return; }