summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKyle Keen <keenerd@gmail.com>2016-01-31 17:06:49 -0500
committerKyle Keen <keenerd@gmail.com>2016-01-31 17:06:49 -0500
commit805ed84f6c476333973e91d9f8059434297ce28f (patch)
tree588d8e90dd9a16ba5ca80f8d0f15041862a57af0
parent2546d2edb1c57b8554e70e6aedb84b1636d01542 (diff)
downloadnamcap-805ed84f6c476333973e91d9f8059434297ce28f.tar.gz
namcap-805ed84f6c476333973e91d9f8059434297ce28f.zip
Add py_mtime rule
Signed-off-by: Kyle Keen <keenerd@gmail.com>
-rw-r--r--Namcap/package.py28
-rw-r--r--Namcap/rules/__init__.py1
-rw-r--r--Namcap/rules/py_mtime.py129
-rw-r--r--namcap-tags3
4 files changed, 161 insertions, 0 deletions
diff --git a/Namcap/package.py b/Namcap/package.py
index cb5da94..308d0ce 100644
--- a/Namcap/package.py
+++ b/Namcap/package.py
@@ -24,6 +24,7 @@ import sys
import subprocess
import re
import collections
+import gzip
import pyalpm
_pyalpm_version_tuple = tuple(int(n) for n in pyalpm.version().split('.'))
@@ -261,4 +262,31 @@ def lookup_provider(pkgname, db):
if pkgname in pkg.provides:
return pkg
+def mtree_line(line):
+ "returns head, {key:value}"
+ # todo, un-hex the escaped chars
+ head,_,kvs = line.partition(' ')
+ kvs = dict(kv.split('=') for kv in kvs.split(' '))
+ return head, kvs
+
+def load_mtree(tar):
+ "takes a tar object, returns (path, {attributes})"
+ if '.MTREE' not in tar.getnames():
+ raise StopIteration
+ zfile = tar.extractfile('.MTREE')
+ text = gzip.open(zfile).read().decode("utf-8")
+ defaults = {}
+ for line in text.split('\n'):
+ if not line:
+ continue
+ if line.startswith('#'):
+ continue
+ head, kvs = mtree_line(line)
+ if head == '/set':
+ defaults = kvs
+ attr = {}
+ attr.update(defaults)
+ attr.update(kvs)
+ yield head, attr
+
# vim: set ts=4 sw=4 noet:
diff --git a/Namcap/rules/__init__.py b/Namcap/rules/__init__.py
index a1b6775..6c8c708 100644
--- a/Namcap/rules/__init__.py
+++ b/Namcap/rules/__init__.py
@@ -42,6 +42,7 @@ from . import (
missingbackups,
perllocal,
permissions,
+ py_mtime,
rpath,
scrollkeeper,
shebangdepends,
diff --git a/Namcap/rules/py_mtime.py b/Namcap/rules/py_mtime.py
new file mode 100644
index 0000000..ee91b32
--- /dev/null
+++ b/Namcap/rules/py_mtime.py
@@ -0,0 +1,129 @@
+#
+# namcap rules - py_mtime
+# Copyright (C) 2013 Kyle Keen <keener@gmail.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+"""
+Check for py timestamps that are ahead of pyc/pyo timestamps
+"""
+
+import os
+from Namcap.package import load_mtree
+from Namcap.ruleclass import *
+
+def _quick_filter(names):
+ "can this package be skipped outright"
+ if not names:
+ return True
+ found_py = any(n.endswith('.py') for n in names)
+ found_pyc = any(n.endswith('.pyc') for n in names)
+ found_pyo = any(n.endswith('.pyo') for n in names)
+ if found_py and found_pyc:
+ return False
+ if found_py and found_pyo:
+ return False
+ return True
+
+def _tar_timestamps(tar):
+ "takes a tar object"
+ return dict((m.name, m.mtime) for m in tar.getmembers())
+
+def _mtree_timestamps(tar):
+ "takes a tar object"
+ return dict((h, a['time']) for h,a in load_mtree(tar) if 'time' in a)
+
+def _generic_timestamps(tar):
+ "works for mtree and tar"
+ if '.MTREE' in tar.getnames():
+ return _mtree_timestamps(tar)
+ return _tar_timestamps(tar)
+
+def _try_mtree(tar):
+ "returns True if good, False if bad, None if N/A"
+ if '.MTREE' not in tar.getnames():
+ return None
+ stamps = _mtree_timestamps(tar)
+ if _quick_filter(stamps.keys()):
+ return True
+ return not _mtime_filter(stamps)
+
+def _try_tar(tar):
+ "returns True if good, False if bad"
+ names = tar.getnames()
+ if _quick_filter(names):
+ return True
+ mtimes = _tar_timestamps(tar)
+ return not _mtime_filter(mtimes)
+
+def _split_all(path):
+ "like os.path.split but splits every directory"
+ p2 = path
+ dirs = []
+ while p2 and p2 != '/':
+ p2,p3 = os.path.split(p2)
+ dirs.insert(0, p3)
+ #dirs.insert(0, '/')
+ return dirs
+
+def _source_py(path):
+ "given a pyc/pyo, return the source path"
+ if not path.endswith('.pyc') and not path.endswith('.pyo'):
+ return None
+ path = path[:-1]
+ # handle py2
+ if '__pycache__' not in path:
+ return path
+ # handle py3
+ splitup = _split_all(path)
+ if splitup[-2] != '__pycache__':
+ return None
+ splitup.pop(-2)
+ f = splitup[-1]
+ f = f.split('.')
+ f.pop(-2)
+ splitup[-1] = '.'.join(f)
+ return os.path.join(*splitup)
+
+def _mtime_filter(mtimes):
+ "return list of bad py file names"
+ bad = []
+ for name, mt2 in mtimes.items():
+ if not name.endswith('.pyc') and not name.endswith('.pyo'):
+ continue
+ source_name = _source_py(name)
+ if source_name not in mtimes:
+ continue
+ mt1 = mtimes[source_name]
+ if mt1 > mt2:
+ bad.append(source_name)
+ return bad
+
+class package(TarballRule):
+ name = "py_mtime"
+ description = "Check for py timestamps that are ahead of pyc/pyo timestamps"
+ def analyze(self, pkginfo, tar):
+ mtree_status = _try_mtree(tar)
+ tar_status = _try_tar(tar)
+ if mtree_status == False and tar_status:
+ # mtree only
+ self.warning = [('py-mtime-mtree-warning', ())]
+ elif not tar_status:
+ # tar or both
+ self.errors = [('py-mtime-tar-error', ())]
+ self.infos = [('py-mtime-file-name %s', f[1:]) for f in _mtime_filter(_generic_timestamps(tar))]
+
+# vim: set ts=4 sw=4 noet:
diff --git a/namcap-tags b/namcap-tags
index e5656f4..a5c348c 100644
--- a/namcap-tags
+++ b/namcap-tags
@@ -67,6 +67,9 @@ perllocal-pod-present %s :: perllocal.pod found in %s.
pkgname-in-description :: Description should not contain the package name.
potential-non-fhs-info-page %s :: Potential non-FHS info page (%s) found.
potential-non-fhs-man-page %s :: Potential non-FHS man page (%s) found.
+py-mtime-mtree-warning :: Found .py file unoticeably newer than associated .pyc/pyo.
+py-mtime-tar-error :: Found .py file newer than associated .pyc/pyo.
+py-mtime-file-name %s :: Python script (%s) is newer than associated .pyc/pyo.
script-link-detected %s in %s :: Script link detected (%s) in file %s
scrollkeeper-dir-exists %s :: Scrollkeeper directory exists (%s). Remember to not run scrollkeeper till post_{install,upgrade,remove}.
site-ruby :: Found usr/lib/ruby/site_ruby in package, usr/lib/ruby/vendor_ruby should be used instead.