def reset_invalid_value(opt): logsyslog(LOG_ERR, ('{conf}: Invalid value for ' + '{option}').format( conf = self.config_file, option = opt)) return getattr(self, opt)
def start(self): """ Load config, daemonize, connect to serial port, listen on socket """ opensyslog(ident = self.name, facility = LOG_DAEMON) self.__load_config() if self.pidfile_path is not None: self.daemon_context.pidfile = lockfile.FileLock(self.pidfile_path) if _pidfile_isbusy(self.daemon_context.pidfile): logsyslog(LOG_ERR, 'Already running (pidfile is locked)') closesyslog() return if _socket_isbusy(self.socket_path): logsyslog(LOG_ERR, 'Already running (socket is in use)') closesyslog() return self.daemon_context.open() with _openfile(self.daemon_context.pidfile.path, 'w', fail = self.__stop) as file: file.write('{pid}'.format(pid = os.getpid())) # opening the serial port here doesn't work # open it in __run instead # self.serial_context.open() logsyslog(LOG_INFO, 'Started') self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.socket.bind(self.socket_path) self.socket.listen(1) logsyslog(LOG_INFO, ('Listening on socket {socket}').format( socket = self.socket_path)) self.__run()
def _openfile(path, mode = 'r', fail = None): path = os.path.realpath(path) try: file = open(path, mode) except IOError as error: if repr(error).find('Permission') >= 0: logsyslog(LOG_ERR, 'Cannot {action} {path}. Permission denied.'.format( action = ('write to' if 'w' in mode else 'read'), path = path)) elif repr(error).find('No such file') >= 0: logsyslog(LOG_ERR, 'No such file or directory: {path}'.format( path = path)) else: logsyslog(LOG_ERR, 'Cannot {action} {path}. Unknown error.'.format( action = ('write to' if 'w' in mode else 'read'), path = path)) else: return file if fail is not None: fail() sys.exit(255) return None
def __stop(self): pid = _get_pid(self.daemon_context.pidfile.path) if pid is None: return logsyslog(LOG_INFO, 'Stopping') os.remove(self.socket.getsockname()) os.remove(self.daemon_context.pidfile.path) self.socket.close() if self.serial_context.isOpen(): self.serial_context.close() self.daemon_context.close() try: os.kill(pid, signal.SIGKILL) except OSError: logsyslog(LOG_ERR, 'Could not stop process id {pid}'.format( pid = pid)) closesyslog()
def __accept_signal(self, sig, frame): if sig == signal.SIGHUP: self.__load_config() else: logsyslog(LOG_INFO, 'Caught signal {sig}'.format(sig = sig)) self.__stop()
def __load_config(self): def reset_invalid_value(opt): logsyslog(LOG_ERR, ('{conf}: Invalid value for ' + '{option}').format( conf = self.config_file, option = opt)) return getattr(self, opt) conf = _openfile(self.config_file, 'r') if conf is not None: with conf: regex_pat = regex.compile(r"""\s* (?| (?P<option> reply_length_strict | data[-_]length | data[-_]encoding | log[-_]file | pidfile[-_]path | socket[-_]path ) \s* (?: =\s* )? (?| " (?P<value> [^"]+ ) " | ' (?P<value> [^']+ ) ' | (?P<value> [^#\r\n]+ ) ) ) """, regex.X|regex.I) line_num = 0 for line in conf: line_num += 1 if line.startswith('#'): continue match = regex_pat.match(line.strip()) if match: # translate the option name to the object's attribute opt = match.group('option').lower().replace('-', '_') if opt.endswith(('file', 'dir', 'path', 'encoding')): # value is a string val = match.group('value') elif opt == 'reply_length_strict': # value must be a boolean if val.lower() not in ['0', '1', 'false', 'true']: val = reset_invalid_value(opt) elif val.lower() in ['0', 'false']: val = False else: val = True else: # value must be numeric and positive val = int(match.group('value')) if val <= 0: val = reset_invalid_value(opt) setattr(self, opt, val) else: logsyslog(LOG_ERR, ('{conf}: Invalid syntax at line ' + '{line}').format( conf = self.config_file, line = line_num)) logsyslog(LOG_INFO, 'Loaded configuration from {conf}'.format( conf = self.config_file))
def __run(self): with open(self.log_file, 'a') as log_file: try: soc = None # flush doesn't work in daemon mode for ttyS? # close and reopen instead device = self.serial_context.port is_ttyS = True try: # is it a number? Serial defaults to /dev/ttyS? if so device += 0 except TypeError: # not a number, assume string try: if not device.startswith('/dev/ttyS'): raise AttributeError except AttributeError: # not a string or not a ttyS? device # assume flushing works is_ttyS = False else: device = '/dev/ttyS{num}'.format(num = device) while True: if not soc or soc.fileno() < 0: logsyslog(LOG_INFO, 'Waiting for connection') soc, soc_addr = self.socket.accept() logsyslog(LOG_INFO, ('Connected to {addr}').format( addr = soc_addr)) data = soc.recv(self.data_length).decode( self.data_encoding) if data == '': logsyslog(LOG_INFO, 'Closing connection') soc.close() continue elif data == 'device': logsyslog(LOG_INFO, 'Device path requested') try: soc.sendall(device.encode(self.data_encoding)) except ConnectionResetError: soc.close() continue logsyslog(LOG_INFO, 'Read from socket: {data}'.format( data = data)) reply_length_byte_length = 0 try: reply_length_byte_length = int(data[0], 16) reply_length = int( data[1 : reply_length_byte_length + 1], 16) except ValueError: reply_length = 0 data = data[reply_length_byte_length + 1:] if not self.serial_context.isOpen(): # first time in the loop logsyslog(LOG_INFO, 'Opening serial port') self.serial_context.open() logsyslog(LOG_INFO, 'Sending {data}'.format( data = data)) # discard any input or output self.serial_context.flushOutput() self.serial_context.flushInput() self.serial_context.write(data.encode(self.data_encoding)) if is_ttyS: self.serial_context.close() self.serial_context.open() else: self.serial_context.flush() logsyslog(LOG_INFO, ('Will read {length} bytes').format( length = reply_length)) if reply_length > 0: reply = self.serial_context.read(reply_length) reply_decoded = reply.decode(self.data_encoding) logsyslog(LOG_INFO, 'Received {data}'.format( data = reply_decoded)) if len(reply_decoded) == reply_length \ or not self.reply_length_strict: try: soc.sendall(reply) except ConnectionResetError: soc.close() continue except: traceback.print_exc(file = log_file) self.__stop()