[or-cvs] r21062: {arm} Weekend bugfix bundle. added: most commands can be immediate (in arm/trunk: . interface)

atagar at seul.org atagar at seul.org
Mon Nov 30 04:12:57 UTC 2009


Author: atagar
Date: 2009-11-29 23:12:57 -0500 (Sun, 29 Nov 2009)
New Revision: 21062

Modified:
   arm/trunk/ChangeLog
   arm/trunk/README
   arm/trunk/TODO
   arm/trunk/arm.py
   arm/trunk/interface/connPanel.py
   arm/trunk/interface/controller.py
   arm/trunk/interface/fileDescriptorPopup.py
   arm/trunk/interface/headerPanel.py
Log:
Weekend bugfix bundle.
added: most commands can be immediately executed from the help page (feature request by arma)
fix: truncating header's version fields if too long (caught by hexa)
fix: file descriptor dialog now provides a wider variety of error messages in case of failure
fix: offset issue in connections listing when scroll bar was visible
fix: removing family connections from listing when control port is closed
fix: preventing TorCtl startup issues from going to stdout (duplicates warnings)



Modified: arm/trunk/ChangeLog
===================================================================
--- arm/trunk/ChangeLog	2009-11-29 21:02:19 UTC (rev 21061)
+++ arm/trunk/ChangeLog	2009-11-30 04:12:57 UTC (rev 21062)
@@ -1,6 +1,16 @@
 CHANGE LOG
 
-11/8/09 - version 1.2.2
+11/29/09 - version 1.3.0
+Weekend bugfix bundle.
+
+    * added: most commands can be immediately executed from the help page (feature request by arma)
+    * fix: truncating header's version fields if too long (caught by hexa)
+    * fix: file descriptor dialog now provides a wider variety of error messages in case of failure
+    * fix: offset issue in connections listing when scroll bar was visible
+    * fix: removing family connections from listing when control port is closed
+    * fix: preventing TorCtl startup issues from going to stdout (duplicates warnings)
+
+11/8/09 - version 1.2.2 (r20927)
 This will be the last update for a while since I'm about to start a new job.
 
     * added: including family relays on connections listing

Modified: arm/trunk/README
===================================================================
--- arm/trunk/README	2009-11-29 21:02:19 UTC (rev 21061)
+++ arm/trunk/README	2009-11-30 04:12:57 UTC (rev 21062)
@@ -20,13 +20,17 @@
 
 Requirements:
 Python 2.5
-TorCtl (retrieved in svn checkout)
+TorCtl (this is included with arm)
 Tor is running with an available control port. This means either...
   ... starting Tor with '--controlport <PORT>'
   ... or including 'ControlPort <PORT>' in your torrc
-For full functionality this requires common *nix commands including: ps, pidof,
-  tail, pwdx, host, netstat, lsof, and ulimit
 
+For full functionality this also needs:
+Common *nix commands including: ps, pidof, tail, pwdx, host, netstat, lsof, and
+  ulimit
+To be ran with the same user as tor to avoid permission issues with netstat,
+  lsof, and reading the torrc
+
 This is started via 'arm' (use the '--help' argument for usage).
 
 FAQ:

Modified: arm/trunk/TODO
===================================================================
--- arm/trunk/TODO	2009-11-29 21:02:19 UTC (rev 21061)
+++ arm/trunk/TODO	2009-11-30 04:12:57 UTC (rev 21062)
@@ -10,14 +10,11 @@
 			Not sure how to address this - problem is that the calls to 'host' can 
 			take a while to time out. Might need another thread to kill the calls?
 			Or forcefully terminate thread if it's taking too long (might be noisy)?
-	* version labels provided on Debian are longer than expected
-			caught by hexa
 
 - Features / Site
 	* abstract away netstat calls
 			In preparation for drop in replacement of lsof or calls to tor's
 			GETINFO.
-	* file descriptor stats
 	* add page that allows raw control port access
 			Piggyback on the arm connection, providing something like an interactive
 			prompt. In addition, provide:
@@ -36,8 +33,6 @@
 			requested by arma
 	* show advertised bandwidth
 			if set and there's extra room available show 'MaxAdvertisedBandwidth'
-	* when help popup is showing options let them be directly opened
-			requested by arma
 	* check family connections to see if they're alive (VERSION cell handshake?)
 	* update site's screenshots (pretty out of date...)
 

Modified: arm/trunk/arm.py
===================================================================
--- arm/trunk/arm.py	2009-11-29 21:02:19 UTC (rev 21061)
+++ arm/trunk/arm.py	2009-11-30 04:12:57 UTC (rev 21062)
@@ -19,8 +19,8 @@
 from interface import controller
 from interface import logPanel
 
-VERSION = "1.2.2"
-LAST_MODIFIED = "Nov 8, 2009"
+VERSION = "1.3.0"
+LAST_MODIFIED = "Nov 29, 2009"
 
 DEFAULT_CONTROL_ADDR = "127.0.0.1"
 DEFAULT_CONTROL_PORT = 9051
@@ -186,6 +186,9 @@
       print "Unrecognized event flag: %s" % flag
     sys.exit()
   
+  # temporarily disables TorCtl logging to prevent issues from going to stdout when starting
+  TorUtil.loglevel = "NONE"
+  
   # attempts to open a socket to the tor server
   s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
   try:

Modified: arm/trunk/interface/connPanel.py
===================================================================
--- arm/trunk/interface/connPanel.py	2009-11-29 21:02:19 UTC (rev 21061)
+++ arm/trunk/interface/connPanel.py	2009-11-30 04:12:57 UTC (rev 21062)
@@ -340,12 +340,14 @@
           
           familyResolutionsTmp[(familyAddress, familyPort)] = fingerprint
           connectionsTmp.append(("family", familyAddress, familyPort, familyAddress, familyPort, familyCountryCode, connTime))
-        except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed):
+        except (socket.error, TorCtl.ErrorReply):
           # use dummy entry for sorting - the redraw function notes that entries are unknown
           portIdentifier = str(65536 + tmpCounter)
           familyResolutionsTmp[("256.255.255.255", portIdentifier)] = fingerprint
           connectionsTmp.append(("family", "256.255.255.255", portIdentifier, "256.255.255.255", portIdentifier, "??", time.time()))
           tmpCounter += 1
+        except TorCtl.TorCtlClosed:
+          pass # connections aren't shown when control port is unavailable
       
       self.lastUpdate = time.time()
       
@@ -568,7 +570,7 @@
                   ipStart = etc.find("256")
                   if ipStart > -1: etc = etc[:ipStart] + ("%%-%is" % len(etc[ipStart:])) % "UNKNOWN"
               
-              padding = self.maxX - (len(src) + len(dst) + len(etc) + 27) # padding needed to fill full line
+              padding = self.maxX - (len(src) + len(dst) + len(etc) + 27) - xOffset # padding needed to fill full line
               lineEntry = "<%s>%s  -->  %s  %s%s%5s (<b>%s</b>)%s</%s>" % (color, src, dst, etc, " " * padding, timeLabel, type.upper(), " " * (9 - len(type)), color)
               
               if self.isCursorEnabled and entry == self.cursorSelection:

Modified: arm/trunk/interface/controller.py
===================================================================
--- arm/trunk/interface/controller.py	2009-11-29 21:02:19 UTC (rev 21061)
+++ arm/trunk/interface/controller.py	2009-11-30 04:12:57 UTC (rev 21062)
@@ -361,6 +361,7 @@
   
   isUnresponsive = False    # true if it's been over ten seconds since the last BW event (probably due to Tor closing)
   isPaused = False          # if true updates are frozen
+  overrideKey = None        # immediately runs with this input rather than waiting for the user if set
   page = 0
   regexFilters = []             # previously used log regex filters
   
@@ -422,7 +423,13 @@
     finally:
       cursesLock.release()
     
-    key = stdscr.getch()
+    # wait for user keyboard input until timeout (unless an override was set)
+    if overrideKey:
+      key = overrideKey
+      overrideKey = None
+    else:
+      key = stdscr.getch()
+    
     if key == ord('q') or key == ord('Q'):
       quitConfirmed = not CONFIRM_QUIT
       
@@ -498,39 +505,45 @@
         popup.win.box()
         popup.addstr(0, 0, "Page %i Commands:" % (page + 1), util.LABEL_ATTR)
         
+        pageOverrideKeys = ()
+        
         if page == 0:
           graphedStats = panels["graph"].currentDisplay
           if not graphedStats: graphedStats = "none"
-          popup.addfstr(1, 2, "s: graphed stats (<b>%s</b>)" % graphedStats)
-          popup.addfstr(1, 41, "i: graph update interval (<b>%s</b>)" % panels["graph"].updateInterval)
+          popup.addfstr(1, 2, "<b>s</b>: graphed stats (<b>%s</b>)" % graphedStats)
+          popup.addfstr(1, 41, "<b>i</b>: graph update interval (<b>%s</b>)" % panels["graph"].updateInterval)
           popup.addfstr(2, 2, "b: graph bounds (<b>%s</b>)" % graphPanel.BOUND_LABELS[panels["graph"].bounds])
-          popup.addstr(2, 41, "d: file descriptors")
-          popup.addstr(3, 2, "e: change logged events")
+          popup.addfstr(2, 41, "<b>d</b>: file descriptors")
+          popup.addfstr(3, 2, "<b>e</b>: change logged events")
           
           runlevelEventsLabel = "arm and tor"
           if panels["log"].runlevelTypes == logPanel.RUNLEVEL_TOR_ONLY: runlevelEventsLabel = "tor only"
           elif panels["log"].runlevelTypes == logPanel.RUNLEVEL_ARM_ONLY: runlevelEventsLabel = "arm only"
-          popup.addfstr(3, 41, "r: logged runlevels (<b>%s</b>)" % runlevelEventsLabel)
+          popup.addfstr(3, 41, "<b>r</b>: logged runlevels (<b>%s</b>)" % runlevelEventsLabel)
           
           regexLabel = "enabled" if panels["log"].regexFilter else "disabled"
-          popup.addfstr(4, 2, "f: log regex filter (<b>%s</b>)" % regexLabel)
+          popup.addfstr(4, 2, "<b>f</b>: log regex filter (<b>%s</b>)" % regexLabel)
+          
+          pageOverrideKeys = (ord('s'), ord('i'), ord('d'), ord('e'), ord('r'), ord('f'))
         if page == 1:
           popup.addstr(1, 2, "up arrow: scroll up a line")
           popup.addstr(1, 41, "down arrow: scroll down a line")
           popup.addstr(2, 2, "page up: scroll up a page")
           popup.addstr(2, 41, "page down: scroll down a page")
           popup.addstr(3, 2, "enter: connection details")
-          popup.addstr(3, 41, "d: raw consensus descriptor")
+          popup.addfstr(3, 41, "<b>d</b>: raw consensus descriptor")
           
           listingType = connPanel.LIST_LABEL[panels["conn"].listingType].lower()
-          popup.addfstr(4, 2, "l: listed identity (<b>%s</b>)" % listingType)
+          popup.addfstr(4, 2, "<b>l</b>: listed identity (<b>%s</b>)" % listingType)
           
           allowDnsLabel = "allow" if panels["conn"].allowDNS else "disallow"
           popup.addfstr(4, 41, "r: permit DNS resolution (<b>%s</b>)" % allowDnsLabel)
           
-          popup.addstr(5, 2, "s: sort ordering")
-          popup.addstr(5, 41, "c: client circuits")
+          popup.addfstr(5, 2, "<b>s</b>: sort ordering")
+          popup.addfstr(5, 41, "<b>c</b>: client circuits")
           #popup.addfstr(5, 41, "c: toggle cursor (<b>%s</b>)" % ("on" if panels["conn"].isCursorEnabled else "off"))
+          
+          pageOverrideKeys = (ord('d'), ord('l'), ord('s'), ord('c'))
         elif page == 2:
           popup.addstr(1, 2, "up arrow: scroll up a line")
           popup.addstr(1, 41, "down arrow: scroll down a line")
@@ -546,8 +559,10 @@
         popup.addstr(7, 2, "Press any key...")
         popup.refresh()
         
+        # waits for user to hit a key, if it belongs to a command then executes it
         curses.cbreak()
-        stdscr.getch()
+        helpExitKey = stdscr.getch()
+        if helpExitKey in pageOverrideKeys: overrideKey = helpExitKey
         curses.halfdelay(REFRESH_RATE * 10)
         
         setPauseState(panels, isPaused, page)

Modified: arm/trunk/interface/fileDescriptorPopup.py
===================================================================
--- arm/trunk/interface/fileDescriptorPopup.py	2009-11-29 21:02:19 UTC (rev 21061)
+++ arm/trunk/interface/fileDescriptorPopup.py	2009-11-30 04:12:57 UTC (rev 21062)
@@ -27,16 +27,34 @@
       
       # retrieves list of open files, options are:
       # n = no dns lookups, p = by pid, -F = show fields (L = login name, n = opened files)
-      lsofCall = os.popen("lsof -np %s -F Ln 2> /dev/null" % torPid)
-      results = lsofCall.readlines()
-      if len(results) == 0: raise Exception("lsof is unavailable")
+      lsofCall = os.popen3("lsof -np %s -F Ln" % torPid)
+      results = lsofCall[1].readlines()
+      errResults = lsofCall[2].readlines()
+      
+      # checks if lsof was unavailable
+      if "not found" in "".join(errResults):
+        raise Exception("error: lsof is unavailable")
+      
+      # if we didn't get any results then tor's probably closed (keep defaults)
+      if len(results) == 0: return
+      
       torUser = results[1][1:]
       results = results[2:] # skip first couple lines (pid listing and user)
       
       # splits descriptors into buckets according to their type
       descriptors = [entry[1:].strip() for entry in results] # strips off first character (always an 'n')
       
+      # checks if read failed due to permission issues
+      isPermissionDenied = True
       for desc in descriptors:
+        if "Permission denied" not in desc:
+          isPermissionDenied = False
+          break
+      
+      if isPermissionDenied:
+        raise Exception("lsof error: Permission denied")
+      
+      for desc in descriptors:
         if os.path.exists(desc): self.fdFile.append(desc)
         elif desc[0] != "/" and ":" in desc: self.fdConn.append(desc)
         else: self.fdMisc.append(desc)
@@ -57,13 +75,15 @@
         # uses ulimit to estimate (-H is for hard limit, which is what tor uses)
         ulimitCall = os.popen("ulimit -Hn 2> /dev/null")
         results = ulimitCall.readlines()
-        if len(results) == 0: raise Exception("ulimit is unavailable")
+        if len(results) == 0: raise Exception("error: ulimit is unavailable")
         self.fdLimit = int(results[0])
     except Exception, exc:
       # problem arose in calling or parsing lsof or ulimit calls
-      self.errorMsg = "error: " + str(exc)
+      self.errorMsg = str(exc)
     finally:
-      lsofCall.close()
+      lsofCall[0].close()
+      lsofCall[1].close()
+      lsofCall[2].close()
       if ulimitCall: ulimitCall.close()
   
   def handleKey(self, key, height):
@@ -94,7 +114,7 @@
       for entry in properties.fdFile + properties.fdConn + properties.fdMisc:
         popupWidth = max(popupWidth, len(entry) + 4)
       
-      popupHeight = len(properties.fdFile) + len(properties.fdConn) + len(properties.fdMisc)
+      popupHeight = len(properties.fdFile) + len(properties.fdConn) + len(properties.fdMisc) + 4
     
     popup._resetBounds()
     popup.height = popupHeight
@@ -128,7 +148,7 @@
   else:
     # text with file descriptor count and limit
     fdCount = len(properties.fdFile) + len(properties.fdConn) + len(properties.fdMisc)
-    fdCountPer = 100 * fdCount / properties.fdLimit
+    fdCountPer = 100 * fdCount / max(properties.fdLimit, 1)
     
     statsColor = "green"
     if fdCountPer >= 90: statsColor = "red"
@@ -139,7 +159,8 @@
     
     # provides a progress bar reflecting the stats
     barWidth = popup.maxX - len(countMsg) - 6 # space between "[ ]" in progress bar
-    barProgress = max(1, barWidth * fdCountPer / 100) # filled cells
+    barProgress = barWidth * fdCountPer / 100 # filled cells
+    if fdCount > 0: barProgress = max(1, barProgress) # ensures one cell is filled unless really zero
     popup.addstr(1, len(countMsg) + 3, "[", curses.A_BOLD)
     popup.addstr(1, len(countMsg) + 4, " " * barProgress, curses.A_STANDOUT | util.getColor(statsColor))
     popup.addstr(1, len(countMsg) + 4 + barWidth, "]", curses.A_BOLD)

Modified: arm/trunk/interface/headerPanel.py
===================================================================
--- arm/trunk/interface/headerPanel.py	2009-11-29 21:02:19 UTC (rev 21061)
+++ arm/trunk/interface/headerPanel.py	2009-11-30 04:12:57 UTC (rev 21062)
@@ -43,6 +43,7 @@
     self.conn = conn                # Tor control port connection
     self.isPaused = False
     self.isWide = False             # doubles up parameters to shorten section if room's available
+    self.rightParamX = 0            # offset used for doubled up parameters
     self.lastUpdate = -1            # time last stats was retrived
     self._updateParams()
   
@@ -52,6 +53,7 @@
     
     self._resetBounds()
     self.isWide = self.maxX >= MIN_DUAL_ROW_WIDTH
+    self.rightParamX = max(self.maxX / 2, 75) if self.isWide else 0
     self.height = 4 if self.isWide else 6
     
     util.Panel.recreate(self, stdscr, startY, maxX)
@@ -70,13 +72,36 @@
         
         self.clear()
         
-        # Line 1
-        self.addstr(0, 0, "arm - %s (%s %s)" % (self.vals["sys-name"], self.vals["sys-os"], self.vals["sys-version"]))
+        # Line 1 (system and tor version information)
+        systemNameLabel = "arm - %s " % self.vals["sys-name"]
+        systemVersionLabel = "%s %s" % (self.vals["sys-os"], self.vals["sys-version"])
         
+        # wraps systemVersionLabel in parentheses and truncates if too long
+        versionLabelMaxWidth = 40 - len(systemNameLabel)
+        if len(systemNameLabel) > 40:
+          # we only have room for the system name label
+          systemNameLabel = systemNameLabel[:39] + "..."
+          systemVersionLabel = ""
+        elif len(systemVersionLabel) > versionLabelMaxWidth:
+          # not enough room to show full version
+          systemVersionLabel = "(%s...)" % systemVersionLabel[:versionLabelMaxWidth - 3].strip()
+        else:
+          # enough room for everything
+          systemVersionLabel = "(%s)" % systemVersionLabel
+        
+        self.addstr(0, 0, "%s%s" % (systemNameLabel, systemVersionLabel))
+        
         versionStatus = self.vals["status/version/current"]
         versionColor = VERSION_STATUS_COLORS[versionStatus] if versionStatus in VERSION_STATUS_COLORS else "white"
-        self.addfstr(0, 43, "Tor %s (<%s>%s</%s>)" % (self.vals["version"], versionColor, versionStatus, versionColor))
         
+        # truncates torVersionLabel if too long
+        torVersionLabel = self.vals["version"]
+        versionLabelMaxWidth =  (self.rightParamX if self.isWide else self.maxX) - 51 - len(versionStatus)
+        if len(torVersionLabel) > versionLabelMaxWidth:
+          torVersionLabel = torVersionLabel[:versionLabelMaxWidth - 1].strip() + "-"
+        
+        self.addfstr(0, 43, "Tor %s (<%s>%s</%s>)" % (torVersionLabel, versionColor, versionStatus, versionColor))
+        
         # Line 2 (authentication label red if open, green if credentials required)
         dirPortLabel = "Dir Port: %s, " % self.vals["DirPort"] if self.vals["DirPort"] != "0" else ""
         
@@ -89,14 +114,14 @@
         self.addfstr(1, 0, "%s<%s>%s</%s>): %s" % (labelStart, controlPortAuthColor, controlPortAuthLabel, controlPortAuthColor, self.vals["ControlPort"]))
         
         # Line 3 (system usage info) - line 1 right if wide
-        y, x = 0 if self.isWide else 2, 75 if self.isWide else 0
+        y, x = (0, self.rightParamX) if self.isWide else (2, 0)
         self.addstr(y, x, "cpu: %s%%" % self.vals["%cpu"])
         self.addstr(y, x + 13, "mem: %s (%s%%)" % (util.getSizeLabel(int(self.vals["rss"]) * 1024), self.vals["%mem"]))
         self.addstr(y, x + 34, "pid: %s" % (self.vals["pid"] if self.vals["etime"] else ""))
         self.addstr(y, x + 47, "uptime: %s" % self.vals["etime"])
         
         # Line 4 (fingerprint) - line 2 right if wide
-        y, x = 1 if self.isWide else 3, 75 if self.isWide else 0
+        y, x = (1, self.rightParamX) if self.isWide else (3, 0)
         self.addstr(y, x, "fingerprint: %s" % self.vals["fingerprint"])
         
         # Line 5 (flags) - line 3 left if wide
@@ -108,7 +133,7 @@
         if len(self.vals["flags"]) > 0: flagLine = flagLine[:-2]
         self.addfstr(2 if self.isWide else 4, 0, flagLine)
         
-        # Line 3 right (exit policy) - not present if not wide
+        # Line 3 right (exit policy) - only present if wide
         if self.isWide:
           exitPolicy = self.vals["ExitPolicy"]
           
@@ -130,7 +155,7 @@
             policies[i] = policy
           exitPolicy = ", ".join(policies)
           
-          self.addfstr(2, 75, "exit policy: %s" % exitPolicy)
+          self.addfstr(2, self.rightParamX, "exit policy: %s" % exitPolicy)
         
         self.refresh()
       finally:



More information about the tor-commits mailing list