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()