def load(self, logFailure=False): """ Loads or reloads the torrc contents, raising an IOError if there's a problem. Arguments: logFailure - if the torrc fails to load and we've never provided a warning for this before then logs a warning """ self.valsLock.acquire() # clears contents and caches self.contents, self.configLocation = None, None self.displayableContents = None self.strippedContents = None self.corrections = None try: self.configLocation = getConfigLocation() configFile = open(self.configLocation, "r") self.contents = configFile.readlines() configFile.close() except IOError, exc: if logFailure and not self.isLoadFailWarned: log.warn("Unable to load torrc (%s)" % sysTools.getFileErrorMsg(exc)) self.isLoadFailWarned = True self.valsLock.release() raise exc
def load(self, logFailure = False): """ Loads or reloads the torrc contents, raising an IOError if there's a problem. Arguments: logFailure - if the torrc fails to load and we've never provided a warning for this before then logs a warning """ self.valsLock.acquire() # clears contents and caches self.contents, self.configLocation = None, None self.displayableContents = None self.strippedContents = None self.corrections = None try: self.configLocation = getConfigLocation() configFile = open(self.configLocation, "r") self.contents = configFile.readlines() configFile.close() except IOError, exc: if logFailure and not self.isLoadFailWarned: msg = "Unable to load torrc (%s)" % sysTools.getFileErrorMsg(exc) log.log(CONFIG["log.torrc.readFailed"], msg) self.isLoadFailWarned = True self.valsLock.release() raise exc
def showSnapshotPrompt(self): """ Lets user enter a path to take a snapshot, canceling if left blank. """ pathInput = popups.inputPrompt("Path to save log snapshot: ") if pathInput: try: self.saveSnapshot(pathInput) popups.showMsg("Saved: %s" % pathInput, 2) except IOError, exc: popups.showMsg("Unable to save snapshot: %s" % sysTools.getFileErrorMsg(exc), 2)
def registerEvent(self, event): """ Notes event and redraws log. If paused it's held in a temporary buffer. Arguments: event - LogEntry for the event that occurred """ if not event.type in self.loggedEvents: return # strips control characters to avoid screwing up the terminal event.msg = uiTools.getPrintable(event.msg) # note event in the log file if we're saving them if self.logFile: try: self.logFile.write(event.getDisplayMessage(True) + "\n") self.logFile.flush() except IOError, exc: log.log( self._config["log.logPanel.logFileWriteFailed"], "Unable to write to log file: %s" % sysTools.getFileErrorMsg(exc)) self.logFile = None
def showWriteDialog(self): """ Provies an interface to confirm if the configuration is saved and, if so, where. """ # display a popup for saving the current configuration configLines = torConfig.getCustomOptions(True) popup, width, height = popups.init(len(configLines) + 2) if not popup: return try: # displayed options (truncating the labels if there's limited room) if width >= 30: selectionOptions = ("Save", "Save As...", "Cancel") else: selectionOptions = ("Save", "Save As", "X") # checks if we can show options beside the last line of visible content isOptionLineSeparate = False lastIndex = min(height - 2, len(configLines) - 1) # if we don't have room to display the selection options and room to # grow then display the selection options on its own line if width < (30 + len(configLines[lastIndex])): popup.setHeight(height + 1) popup.redraw(True) # recreates the window instance newHeight, _ = popup.getPreferredSize() if newHeight > height: height = newHeight isOptionLineSeparate = True key, selection = 0, 2 while not uiTools.isSelectionKey(key): # if the popup has been resized then recreate it (needed for the # proper border height) newHeight, newWidth = popup.getPreferredSize() if (height, width) != (newHeight, newWidth): height, width = newHeight, newWidth popup.redraw(True) # if there isn't room to display the popup then cancel it if height <= 2: selection = 2 break popup.win.erase() popup.win.box() popup.addstr(0, 0, "Configuration being saved:", curses.A_STANDOUT) visibleConfigLines = height - 3 if isOptionLineSeparate else height - 2 for i in range(visibleConfigLines): line = uiTools.cropStr(configLines[i], width - 2) if " " in line: option, arg = line.split(" ", 1) popup.addstr(i + 1, 1, option, curses.A_BOLD | uiTools.getColor("green")) popup.addstr(i + 1, len(option) + 2, arg, curses.A_BOLD | uiTools.getColor("cyan")) else: popup.addstr(i + 1, 1, line, curses.A_BOLD | uiTools.getColor("green")) # draws selection options (drawn right to left) drawX = width - 1 for i in range(len(selectionOptions) - 1, -1, -1): optionLabel = selectionOptions[i] drawX -= (len(optionLabel) + 2) # if we've run out of room then drop the option (this will only # occure on tiny displays) if drawX < 1: break selectionFormat = curses.A_STANDOUT if i == selection else curses.A_NORMAL popup.addstr(height - 2, drawX, "[") popup.addstr(height - 2, drawX + 1, optionLabel, selectionFormat | curses.A_BOLD) popup.addstr(height - 2, drawX + len(optionLabel) + 1, "]") drawX -= 1 # space gap between the options popup.win.refresh() key = cli.controller.getController().getScreen().getch() if key == curses.KEY_LEFT: selection = max(0, selection - 1) elif key == curses.KEY_RIGHT: selection = min(len(selectionOptions) - 1, selection + 1) if selection in (0, 1): loadedTorrc, promptCanceled = torConfig.getTorrc(), False try: configLocation = loadedTorrc.getConfigLocation() except IOError: configLocation = "" if selection == 1: # prompts user for a configuration location configLocation = popups.inputPrompt("Save to (esc to cancel): ", configLocation) if not configLocation: promptCanceled = True if not promptCanceled: try: torConfig.saveConf(configLocation, configLines) msg = "Saved configuration to %s" % configLocation except IOError, exc: msg = "Unable to save configuration (%s)" % sysTools.getFileErrorMsg(exc) popups.showMsg(msg, 2) finally: popups.finalize()
def drawTorMonitor(stdscr, startTime): """ Main draw loop context. Arguments: stdscr - curses window startTime - unix time for when arm was started """ initController(stdscr, startTime) control = getController() # provides notice about any unused config keys for key in conf.getConfig("arm").getUnusedKeys(): log.log(CONFIG["log.configEntryUndefined"], "Unused configuration entry: %s" % key) # tells daemon panels to start for panelImpl in control.getDaemonPanels(): panelImpl.start() # allows for background transparency try: curses.use_default_colors() except curses.error: pass # makes the cursor invisible try: curses.curs_set(0) except curses.error: pass # logs the initialization time msg = "arm started (initialization took %0.3f seconds)" % (time.time() - startTime) log.log(CONFIG["log.startTime"], msg) # main draw loop overrideKey = None # uses this rather than waiting on user input isUnresponsive = False # flag for heartbeat responsiveness check if not torTools.getConn().isAlive(): overrideKey = ord('w') # shows wizard while not control.isDone(): displayPanels = control.getDisplayPanels() isUnresponsive = heartbeatCheck(isUnresponsive) # sets panel visability for panelImpl in control.getAllPanels(): panelImpl.setVisible(panelImpl in displayPanels) # redraws the interface if it's needed control.redraw(False) stdscr.refresh() # wait for user keyboard input until timeout, unless an override was set if overrideKey: key, overrideKey = overrideKey, None else: curses.halfdelay(CONFIG["features.redrawRate"] * 10) key = stdscr.getch() if key == curses.KEY_RIGHT: control.nextPage() elif key == curses.KEY_LEFT: control.prevPage() elif key == ord('p') or key == ord('P'): control.setPaused(not control.isPaused()) elif key == ord('m') or key == ord('M'): cli.menu.menu.showMenu() elif key == ord('q') or key == ord('Q'): # provides prompt to confirm that arm should exit if CONFIG["features.confirmQuit"]: msg = "Are you sure (q again to confirm)?" confirmationKey = cli.popups.showMsg(msg, attr=curses.A_BOLD) quitConfirmed = confirmationKey in (ord('q'), ord('Q')) else: quitConfirmed = True if quitConfirmed: control.quit() elif key == ord('x') or key == ord('X'): # provides prompt to confirm that arm should issue a sighup msg = "This will reset Tor's internal state. Are you sure (x again to confirm)?" confirmationKey = cli.popups.showMsg(msg, attr=curses.A_BOLD) if confirmationKey in (ord('x'), ord('X')): try: torTools.getConn().reload() except IOError, exc: log.log( log.ERR, "Error detected when reloading tor: %s" % sysTools.getFileErrorMsg(exc)) elif key == ord('h') or key == ord('H'): overrideKey = cli.popups.showHelpPopup()
def drawTorMonitor(stdscr, startTime): """ Main draw loop context. Arguments: stdscr - curses window startTime - unix time for when arm was started """ initController(stdscr, startTime) control = getController() # provides notice about any unused config keys for key in conf.get_config("arm").unused_keys(): log.notice("Unused configuration entry: %s" % key) # tells daemon panels to start for panelImpl in control.getDaemonPanels(): panelImpl.start() # allows for background transparency try: curses.use_default_colors() except curses.error: pass # makes the cursor invisible try: curses.curs_set(0) except curses.error: pass # logs the initialization time log.info("arm started (initialization took %0.3f seconds)" % (time.time() - startTime)) # main draw loop overrideKey = None # uses this rather than waiting on user input isUnresponsive = False # flag for heartbeat responsiveness check while not control.isDone(): displayPanels = control.getDisplayPanels() isUnresponsive = heartbeatCheck(isUnresponsive) # sets panel visability for panelImpl in control.getAllPanels(): panelImpl.setVisible(panelImpl in displayPanels) # redraws the interface if it's needed control.redraw(False) stdscr.refresh() # wait for user keyboard input until timeout, unless an override was set if overrideKey: key, overrideKey = overrideKey, None else: curses.halfdelay(CONFIG["features.redrawRate"] * 10) key = stdscr.getch() if key == curses.KEY_RIGHT: control.nextPage() elif key == curses.KEY_LEFT: control.prevPage() elif key == ord("p") or key == ord("P"): control.setPaused(not control.isPaused()) elif key == ord("m") or key == ord("M"): cli.menu.menu.showMenu() elif key == ord("q") or key == ord("Q"): # provides prompt to confirm that arm should exit if CONFIG["features.confirmQuit"]: msg = "Are you sure (q again to confirm)?" confirmationKey = cli.popups.showMsg(msg, attr=curses.A_BOLD) quitConfirmed = confirmationKey in (ord("q"), ord("Q")) else: quitConfirmed = True if quitConfirmed: control.quit() elif key == ord("x") or key == ord("X"): # provides prompt to confirm that arm should issue a sighup msg = "This will reset Tor's internal state. Are you sure (x again to confirm)?" confirmationKey = cli.popups.showMsg(msg, attr=curses.A_BOLD) if confirmationKey in (ord("x"), ord("X")): try: torTools.getConn().reload() except IOError, exc: log.error("Error detected when reloading tor: %s" % sysTools.getFileErrorMsg(exc)) elif key == ord("h") or key == ord("H"): overrideKey = cli.popups.showHelpPopup()
def registerEvent(self, event): """ Notes event and redraws log. If paused it's held in a temporary buffer. Arguments: event - LogEntry for the event that occurred """ if not event.type in self.loggedEvents: return # strips control characters to avoid screwing up the terminal event.msg = uiTools.getPrintable(event.msg) # note event in the log file if we're saving them if self.logFile: try: self.logFile.write(event.getDisplayMessage(True) + "\n") self.logFile.flush() except IOError, exc: log.log(self._config["log.logPanel.logFileWriteFailed"], "Unable to write to log file: %s" % sysTools.getFileErrorMsg(exc)) self.logFile = None
def __init__(self, stdscr, loggedEvents, config=None): panel.Panel.__init__(self, stdscr, "log", 0) threading.Thread.__init__(self) self.setDaemon(True) self._config = dict(DEFAULT_CONFIG) if config: config.update(self._config, { "features.log.maxLinesPerEntry": 1, "features.log.prepopulateReadLimit": 0, "features.log.maxRefreshRate": 10, "cache.logPanel.size": 1000}) # collapses duplicate log entries if false, showing only the most recent self.showDuplicates = self._config["features.log.showDuplicateEntries"] self.msgLog = [] # log entries, sorted by the timestamp self.loggedEvents = loggedEvents # events we're listening to self.regexFilter = None # filter for presented log events (no filtering if None) self.lastContentHeight = 0 # height of the rendered content when last drawn self.logFile = None # file log messages are saved to (skipped if None) self.scroll = 0 self._isPaused = False self._pauseBuffer = [] # location where messages are buffered if paused self._lastUpdate = -1 # time the content was last revised self._halt = False # terminates thread if true self._cond = threading.Condition() # used for pausing/resuming the thread # restricts concurrent write access to attributes used to draw the display # and pausing: # msgLog, loggedEvents, regexFilter, scroll, _pauseBuffer self.valsLock = threading.RLock() # cached parameters (invalidated if arguments for them change) # last set of events we've drawn with self._lastLoggedEvents = [] # _getTitle (args: loggedEvents, regexFilter pattern, width) self._titleCache = None self._titleArgs = (None, None, None) # fetches past tor events from log file, if available torEventBacklog = [] if self._config["features.log.prepopulate"]: setRunlevels = list(set.intersection(set(self.loggedEvents), set(RUNLEVELS))) readLimit = self._config["features.log.prepopulateReadLimit"] addLimit = self._config["cache.logPanel.size"] torEventBacklog = getLogFileEntries(setRunlevels, readLimit, addLimit, self._config) # adds arm listener and fetches past events log.LOG_LOCK.acquire() try: armRunlevels = [log.DEBUG, log.INFO, log.NOTICE, log.WARN, log.ERR] log.addListeners(armRunlevels, self._registerArmEvent) # gets the set of arm events we're logging setRunlevels = [] for i in range(len(armRunlevels)): if "ARM_" + RUNLEVELS[i] in self.loggedEvents: setRunlevels.append(armRunlevels[i]) armEventBacklog = [] for level, msg, eventTime in log._getEntries(setRunlevels): runlevelStr = log.RUNLEVEL_STR[level] armEventEntry = LogEntry(eventTime, "ARM_" + runlevelStr, msg, RUNLEVEL_EVENT_COLOR[runlevelStr]) armEventBacklog.insert(0, armEventEntry) # joins armEventBacklog and torEventBacklog chronologically into msgLog while armEventBacklog or torEventBacklog: if not armEventBacklog: self.msgLog.append(torEventBacklog.pop(0)) elif not torEventBacklog: self.msgLog.append(armEventBacklog.pop(0)) elif armEventBacklog[0].timestamp < torEventBacklog[0].timestamp: self.msgLog.append(torEventBacklog.pop(0)) else: self.msgLog.append(armEventBacklog.pop(0)) finally: log.LOG_LOCK.release() # crops events that are either too old, or more numerous than the caching size self._trimEvents(self.msgLog) # leaving lastContentHeight as being too low causes initialization problems self.lastContentHeight = len(self.msgLog) # adds listeners for tor and torctl events conn = torTools.getConn() conn.addEventListener(TorEventObserver(self.registerEvent)) conn.addTorCtlListener(self._registerTorCtlEvent) # opens log file if we'll be saving entries if self._config["features.logFile"]: logPath = self._config["features.logFile"] # make dir if the path doesn't already exist baseDir = os.path.dirname(logPath) if not os.path.exists(baseDir): os.makedirs(baseDir) try: self.logFile = open(logPath, "a") log.log(self._config["log.logPanel.logFileOpened"], "arm %s opening log file (%s)" % (VERSION, logPath)) except IOError, exc: log.log(self._config["log.logPanel.logFileWriteFailed"], "Unable to write to log file: %s" % sysTools.getFileErrorMsg(exc)) self.logFile = None
def __init__(self, stdscr, loggedEvents, config=None): panel.Panel.__init__(self, stdscr, "log", 0) threading.Thread.__init__(self) self.setDaemon(True) self._config = dict(DEFAULT_CONFIG) if config: config.update( self._config, { "features.log.maxLinesPerEntry": 1, "features.log.prepopulateReadLimit": 0, "features.log.maxRefreshRate": 10, "cache.logPanel.size": 1000 }) # collapses duplicate log entries if false, showing only the most recent self.showDuplicates = self._config["features.log.showDuplicateEntries"] self.msgLog = [] # log entries, sorted by the timestamp self.loggedEvents = loggedEvents # events we're listening to self.regexFilter = None # filter for presented log events (no filtering if None) self.lastContentHeight = 0 # height of the rendered content when last drawn self.logFile = None # file log messages are saved to (skipped if None) self.scroll = 0 self._isPaused = False self._pauseBuffer = [ ] # location where messages are buffered if paused self._lastUpdate = -1 # time the content was last revised self._halt = False # terminates thread if true self._cond = threading.Condition( ) # used for pausing/resuming the thread # restricts concurrent write access to attributes used to draw the display # and pausing: # msgLog, loggedEvents, regexFilter, scroll, _pauseBuffer self.valsLock = threading.RLock() # cached parameters (invalidated if arguments for them change) # last set of events we've drawn with self._lastLoggedEvents = [] # _getTitle (args: loggedEvents, regexFilter pattern, width) self._titleCache = None self._titleArgs = (None, None, None) # fetches past tor events from log file, if available torEventBacklog = [] if self._config["features.log.prepopulate"]: setRunlevels = list( set.intersection(set(self.loggedEvents), set(RUNLEVELS))) readLimit = self._config["features.log.prepopulateReadLimit"] addLimit = self._config["cache.logPanel.size"] torEventBacklog = getLogFileEntries(setRunlevels, readLimit, addLimit, self._config) # adds arm listener and fetches past events log.LOG_LOCK.acquire() try: armRunlevels = [log.DEBUG, log.INFO, log.NOTICE, log.WARN, log.ERR] log.addListeners(armRunlevels, self._registerArmEvent) # gets the set of arm events we're logging setRunlevels = [] for i in range(len(armRunlevels)): if "ARM_" + RUNLEVELS[i] in self.loggedEvents: setRunlevels.append(armRunlevels[i]) armEventBacklog = [] for level, msg, eventTime in log._getEntries(setRunlevels): runlevelStr = log.RUNLEVEL_STR[level] armEventEntry = LogEntry(eventTime, "ARM_" + runlevelStr, msg, RUNLEVEL_EVENT_COLOR[runlevelStr]) armEventBacklog.insert(0, armEventEntry) # joins armEventBacklog and torEventBacklog chronologically into msgLog while armEventBacklog or torEventBacklog: if not armEventBacklog: self.msgLog.append(torEventBacklog.pop(0)) elif not torEventBacklog: self.msgLog.append(armEventBacklog.pop(0)) elif armEventBacklog[0].timestamp < torEventBacklog[ 0].timestamp: self.msgLog.append(torEventBacklog.pop(0)) else: self.msgLog.append(armEventBacklog.pop(0)) finally: log.LOG_LOCK.release() # crops events that are either too old, or more numerous than the caching size self._trimEvents(self.msgLog) # leaving lastContentHeight as being too low causes initialization problems self.lastContentHeight = len(self.msgLog) # adds listeners for tor and torctl events conn = torTools.getConn() conn.addEventListener(TorEventObserver(self.registerEvent)) conn.addTorCtlListener(self._registerTorCtlEvent) # opens log file if we'll be saving entries if self._config["features.logFile"]: logPath = self._config["features.logFile"] # make dir if the path doesn't already exist baseDir = os.path.dirname(logPath) if not os.path.exists(baseDir): os.makedirs(baseDir) try: self.logFile = open(logPath, "a") log.log(self._config["log.logPanel.logFileOpened"], "arm %s opening log file (%s)" % (VERSION, logPath)) except IOError, exc: log.log( self._config["log.logPanel.logFileWriteFailed"], "Unable to write to log file: %s" % sysTools.getFileErrorMsg(exc)) self.logFile = None