Today I have written a program to filter out common library
dependencies of the three programs exposing this bug, i.e. cheese, vlc
and firefox-esr. For one library depending upon another only the top
level dependency is outputted:
elm@system76-18:~/projects/pydeps$ ./fetchupd.py commdep cheese vlc
common dependencies:
libasound2-data all
libevdev2 amd64
libblkid1 amd64
libaa1 amd64
libdrm-common all
libasound2 amd64
libmtdev1 amd64
libmount1 amd64
libaom0 amd64
libinput10 amd64
libdrm2 amd64
libglib2.0-0 amd64
libgcc1 amd64
libass9 amd64
libvorbisfile3 amd64
libc6 amd64
libgpm2 amd64
elm@system76-18:~/projects/pydeps$ ./fetchupd.py commdep cheese firefox-esr
common dependencies:
libncursesw6 amd64
libatk1.0-0 amd64
libasound2-data all
libasound2 amd64
libice6 amd64
lsb-base all
x11-common all
libncurses6 amd64
libatk1.0-data all
libc6 amd64
libgcc1 amd64
elm@system76-18:~/projects/pydeps$ ./fetchupd.py commdep vlc firefox-esr
common dependencies:
libwayland-client0 amd64
libdbus-1-3 amd64
libsystemd0 amd64
xkb-data all
libxkbcommon0 amd64
libgcc1 amd64
libxcb-util0 amd64
libc6 amd64
elm@system76-18:~/projects/pydeps$ ./fetchupd.py commdep vlc firefox-esr
cheese
common dependencies:
libdbus-1-3 amd64
libxkbcommon0 amd64
libgcc1 amd64
libwayland-client0 amd64
xkb-data all
libc6 amd64
libsystemd0 amd64
elm@system76-18:~/projects/pydeps$ ./fetchupd.py commdep cheese guvcview
common dependencies:
libcap2 amd64
libasound2-data all
libavcodec58 amd64
libaom0 amd64
libusb-1.0-0 amd64
libc6 amd64
libgcc1 amd64
libatk1.0-0 amd64
libogg0 amd64
libasound2 amd64
libatk1.0-data all
However I still do not know which library is causing the fault. A
possible candidate would be libaom0 which implements an internet video
codec. However I would suppose that the hardware uses a different
protocol. Additionally libaom0 and libatk1.0 are also used by guvcview
which works well. I do not know whether libinput10 only implements input
devices like mice and keyboards or whether things like webcams are also
supported. Another possible candidate would be libatk1.0. Unfortunately
I do not really know what these libraries do or whether the bug is
evoked by a totally different component. Only you, the developers or
maintainers of cheese will know so please have a look and tell me about it!
#!/usr/bin/python3
# -*- coding: utf-8
# vim: expandtab;
import gzip, bz2, lzma, urllib.request, urllib.error, hashlib;
from itertools import count; from functools import reduce;
import sys, os, re;
repos = [ ("http://security.debian.org/debian-security", "stable/updates", ["main"] ),
("http://deb.debian.org/debian", "stable", ["main"] ) ];
agent="apt";
architectures=["amd64"];
blocksize=8192;
max_retries=3;
doCache = True; dryRun = False; topOnly = False;
def get_file(url):
headers = { 'User-Agent': agent }
req = urllib.request.Request(url, headers = headers)
resp = urllib.request.urlopen(req)
return resp;
def get_file_content_cached(url):
cachefile = url.translate({ord(c):'_' for c in ":/"})+".cache"
try:
with open(cachefile,'rb') as rfd:
return rfd.read();
except Exception as ex:
data = get_file(url).read();
try:
with open(cachefile,'wb') as wfd:
wfd.write(data);
except Exception as ex:
print(ex);
return data;
def print_urllib_error(url,e):
if type(e) == urllib.error.URLError:
print("error downloading %s: %s" % ( url, e.reason.strerror ), file=sys.stderr );
elif type(e) == urllib.error.HTTPError:
print("error downloading %s: %i %s" % ( url, e.code, e.msg ), file=sys.stderr );
else:
print("unknown error downloading %s: %s" % ( url, str(e) ), file=sys.stderr );
sys.exit(2);
def get_file_content(url):
try:
if doCache: return get_file_content_cached(url);
return get_file(url).read();
except Exception as e:
print_urllib_error(url,e);
def save_file(url,filename):
try:
resp = get_file(url);
h = hashlib.sha256();
with open(filename,'wb') as wfd:
block = resp.read(blocksize);
while block:
wfd.write(block);
h.update(block);
block = resp.read(blocksize);
return h.hexdigest();
except Exception as e:
print_urllib_error(url,e);
def find(elm,lis):
i = 0;
for e in lis:
if e == elm: return i;
i += 1;
return i;
def sgn(val):
return -1 if val < 0 else ( 1 if val > 0 else 0 );
hash_prefs = ['SHA512','SHA384','SHA256','SHA224','SHA1','MD5'];
good_hash_prefs = 3;
def get_hash(content,hashtype):
if hashtype=='SHA256':
h = hashlib.sha256();
elif hashtype=='SHA512':
h = hashlib.sha512();
elif hashtype=='MD5SUM' or hashtype=='MD5':
h = hashlib.md5();
elif hashtype=='SHA1':
h = hashlib.sha1();
elif hashtype=='SHA384':
h = hashlib.sha384();
elif hashtype=='SHA224':
h = hashlib.sha224();
else:
raise Exception("hash of type %s unknown!" % hashtype);
h.update(content);
return h.hexdigest();
has_ver_sep = re.compile("[+~.:-]");
#is_ver = re.compile("[0-9+~.:-]*");
#print("letters:",a[:ia],b[:ib]);
#print("numbers:",a[:ia],b[:ib]);
def numidx(s,search4num):
i = 0;
if search4num:
for c in s:
if '0' <= c and c <= '9': break;
i += 1;
else:
for c in s:
if c < '0' or '9' < c: break;
i += 1;
return i;
def cmp_ver(a,b):
def do_cmp_ver(a,b,seplis):
if not seplis or ( len(seplis) <= 4 and not has_ver_sep.search(a) and not has_ver_sep.search(b)):
while len(a) > 0 and len(b) > 0:
ia = numidx(a,True); ib = numidx(b,True);
if a[:ia] != b[:ib]:
return -1 if a[:ia] < b[:ib] else +1;
a = a[ia:]; b = b[ib:];
ia = numidx(a,False); ib = numidx(b,False);
a_int = int(a[:ia]) if ia!=0 else -1;
b_int = int(b[:ib]) if ib!=0 else -1;
if a_int != b_int:
return -1 if a_int < b_int else +1;
a = a[ia:]; b = b[ib:];
if a == b: return 0;
if a == '': return -1;
return +1;
splitby = seplis.pop();
a = a.split(splitby); b = b.split(splitby); maxlen = max(len(a),len(b));
# chr(254): 1:3.1+dfsg-8+deb10u2~ "<<" 1:3.1+dfsg-8+deb10u2
if splitby == ':':
if len(a) < maxlen: a = [''] * (maxlen-len(a)) + a;
if len(b) < maxlen: b = [''] * (maxlen-len(b)) + b;
elif splitby == '~':
if len(a) < maxlen: a = a + [chr(254)] * (maxlen-len(a));
if len(b) < maxlen: b = b + [chr(254)] * (maxlen-len(b));
else:
if len(a) < maxlen: a = a + [''] * (maxlen-len(a));
if len(b) < maxlen: b = b + [''] * (maxlen-len(b));
for tok_a, tok_b in zip( a, b ):
res = do_cmp_ver( tok_a, tok_b, seplis.copy() )
if res != 0: return res;
return 0;
return do_cmp_ver(a,b,['.','-','+','~',':']);
def do_cmp_ver(cmp_type,a,b):
res = cmp_ver(a,b);
if res == 0 and cmp_type in ['=','<=','>=']: return True;
elif res < 0 and cmp_type in [ '<=', '<<' ]: return True;
elif res > 0 and cmp_type in [ '>=', '>>' ]: return True;
return False;
#print(cmp_ver( "12:13.15+16", "12:13.15+17" ));
#print(cmp_ver( "2.2", "1:2.6.1-1" ));
#print(cmp_ver( "1:3.1+dfsg-8+deb10u2~", "1:3.1+dfsg-8+deb10u2" )); # -1
#print(cmp_ver( "1:3.1+dfsg-8+deb10u2~1", "1:3.1+dfsg-8+deb10u2" )); # -1
#print(cmp_ver( "1.18-3", "1.18.1-4" ));
#print(cmp_ver( "1.18", "1.18.1" ));
#print(cmp_ver( "1.18-3", "1.18-4" ));
#sys.exit(0);
def conc_url(lis):
url="";
for tok in lis:
url += tok;
if not url.endswith('/'): url += '/';
return url[:-1];
sumline = re.compile("^ *(?P<sum>[0-9a-fA-F]*) +(?P<size>[0-9]+) +(?P<filename>.*)$");
pkgs = dict();
class package:
def __init__(self,pname,ver,arch,repoidx,filename,sha256,size,depends,depends_or,conflicts):
self.pname = pname; self.ver = ver; self.arch=arch; self.repoidx = repoidx; self.filename=filename; self.sha256=sha256; self.size=size;
self.depends=depends; self.depends_or=depends_or; self.conflicts=conflicts; self.dependents = [];
def __hash__(self): return hash( ( self.pname, self.ver, self.arch, self.repoidx ) ); # sets shall only use __hash__ and __eq__
def __repr__(self):
return "(ver=%s,arch=%s,repo=%i)" % (self.ver,self.arch,self.repoidx);
# igitt, python3!
def __cmp__(self,other):
result = cmp_ver(self.ver,other.ver);
if result != 0: return result;
result = self.repoidx - other.repoidx;
if result != 0: return -sgn(result); # descending sort order
result = find(self.arch,architectures) - find(other.arch,architectures);
return -sgn(result); # descending sort order
def __lt__(self,other): return self.__cmp__(other) < 0; # the only that shall be used for sorting
def __le__(self,other): return self.__cmp__(other) <= 0;
def __eq__(self,other): return self.pname == other.pname and self.ver == other.ver and self.arch == other.arch and self.repoidx == other.repoidx;
def __ne__(self,other): return not self.__eq__(other);
def __ge__(self,other): return self.__cmp__(other) >= 0;
def __gt__(self,other): return self.__cmp__(other) > 0;
# ~~~~~~~~~~~~~~ actual part of the program ~~~~~~~~~~~~~~
instfile = None;
justprn = []; tryupd = {}; updseq = []; dependents = {}; # tryupd is index by (pname,arch), dependents only by pname
def arch_is_compatible(arch1,arch2):
return arch1 == arch2 or arch1 == 'all' or arch2 == 'all';
def get_with_arch_like(pkghash,pname,arch):
inst = pkghash.get((pname,arch),None);
if inst: return (inst,arch);
if arch != 'all':
return ( pkghash.get((pname,'all'),None), 'all' );
for arch in architectures:
inst = pkghash.get((pname,arch),None);
if inst: return (inst,arch);
return (None,None);
def create_tryupd_entry( pname, compat_with_arch ):
global tryupd;
availables = []; base_arch = None;
for a in pkgs.get(pname,[]):
if ( not base_arch and arch_is_compatible( a.arch, compat_with_arch ) ) or ( a.arch == base_arch ):
availables.append(a); base_arch = a.arch;
if not availables:
return (None,None);
base = [ -1, availables ]; assert(base_arch); # base must not be a tuple to keep base[0] assignable
tryupd[(pname,base_arch)] = base;
return ( base, base_arch );
def get_tryupd_entry( provides, compat_with_arch ):
global tryupd, what_provides;
baselis = [];
for pname in [ provides ] + what_provides.get(provides,[]):
( base, base_arch ) = get_with_arch_like( tryupd, pname, compat_with_arch );
if base:
if base[0] != -1: availables = [ base[1][base[0]] ];
else: availables = base[1];
else:
( base, base_arch ) = create_tryupd_entry( pname, compat_with_arch );
if base != None: availables = base[1];
if base != None:
baselis.append( ( base, availables ) );
return baselis;
def get_installed(pname):
found = None;
for try_arch in architectures + ['all']:
found = installed.get((pname,try_arch),None);
if found: return found;
return found;
def upd( candidate, parent_candidate, always_visit ):
global tryupd, justprn, dependents;
remember_idx = [];
all_installs = [];
depts = dependents.get(candidate.pname,[]);
if depts: always_visit.add(candidate); # in case of dt_* own update will be performed as part of a new call by dt_xx instead of this call
did_upd_dependent = False;
#print(candidate.pname,list(depts)[:10]);
for dt_pname, dt_arch in depts:
if parent_candidate and dt_pname == parent_candidate.pname and dt_arch == parent_candidate.arch: continue;
# first see if that package is already to be updated
dt_good = True; # set to false on unresolvable conflict
pname_arch = (dt_pname,dt_arch);
base = tryupd.get(pname_arch);
if base == None or base[0] != -1:
#print("newly visited",candidate.pname,dt_pname);
if base == None:
inst = installed.get(pname_arch);
if not inst:
# that should not happen: either already installed or when created by an update a base record must already be here
print("warning: strange dependent ignored:",dt_pname,file=sys.stderr);
continue;
else:
inst = base[1][base[0]]
dep_lis = inst.depends.get(candidate.pname,[]) or [];
for cmpop, ver in dep_lis:
if not do_cmp_ver( cmpop, candidate.ver, ver ):
print("update of %s would break dependency of %s; can not update" % (candidate.pname,dt_pname),file=sys.stderr);
dt_good = False;
break;
else:
result = False;
for i, pkg in zip(count(),base[1]):
remember_idx.append((base,base[0]));
#print(candidate.pname,"->",pkg.pname);
base[0] = i; ( result, installs ) = upd( pkg, candidate, always_visit );
if result:
did_upd_dependent = True;
all_installs += installs;
break;
if not result:
print("no update candidate for %s; can not update %s" % (dt_pname,candidate.pname),file=sys.stderr);
dt_good = False;
if not dt_good:
for base, prev_idx in remember_idx:
base[0] = prev_idx;
return ( False, [] );
if did_upd_dependent:
base = tryupd.get((candidate.pname,candidate.arch));
assert(base and base[0] != -1 and base[1][base[0]] == candidate);
return ( True, all_installs );
if depts: always_visit.remove(candidate);
#print("done");
choices = []; # dependency fullfillment choices for packages
# and choice lists
for dep_pname, dep_lis in candidate.depends.items():
baselis = get_tryupd_entry( dep_pname, candidate.arch );
# base=baselis[i]: base[0] is idx for remember_idx into base[1]-list
# base[1] is list of availabel candidates
# if idx in base[0] is set: then availables has only one entry; otherwise it equals base[1]
if not baselis:
print("no arch-deps for", dep_pname, candidate.arch, file=sys.stderr );
return False;
satisfying = [];
for base, availables in baselis:
for i, a in zip(count(max(0,base[0])),availables):
dep_fulfilled = True;
for cmpop, ver in dep_lis or []:
if not do_cmp_ver( cmpop, a.ver, ver ):
dep_fulfilled = False;
break;
if dep_fulfilled:
satisfying.append((base,i,a));
if base[0] == -1: # not yet chosen idx, will be chosen by this upd-call
remember_idx.append((base,base[0]));
choices.append(satisfying); # whole base list is added to get reference instead of value for base[0];
if not satisfying and dep_pname not in justprn:
print("no deps for", dep_pname, dep_lis, availables, "~ by ~", candidate.pname, candidate.ver, file=sys.stderr );
#if len(pb) > 1:
# print(candidate.pname,dep_pname,pb,file=sys.stderr);
justprn.append(dep_pname);
return ( False, [] );
# or choice lists; select one out of each sub-list
for or_lis in candidate.depends_or:
this_select = [];
# put in front: already installed packet
for ( dep_pname, cmpop, ver ), i in zip(or_lis,count()):
if get_installed(dep_pname):
if i: or_lis = [ ( dep_pname, cmpop, ver ) ] + or_lis[:i] + or_lis[i+1:];
if i: print(or_lis);
break;
# convert into choices format and select packages where version fits
for dep_pname, cmpop, ver in or_lis:
baselis = get_tryupd_entry( dep_pname, candidate.arch );
if not baselis: continue;
for base, availables in baselis:
for i, a in zip(count(max(0,base[0])),availables):
if cmpop == None or do_cmp_ver( cmpop, a.ver, ver ):
this_select.append((base,i,a));
if base[0] == -1: # not yet chosen idx, will be chosen by this upd-call
remember_idx.append((base,base[0]));
if not this_select:
print("no deps available for or-list %s" % repr(or_lis));
return ( False, [] );
choices.append(this_select);
#print(candidate.pname);
for this_select in choices:
result = True; # we have already asserted that there is at least one choice; if base[0]>=0 (will be updated) or if it is already installed then result shall be True
for base, i, pkg in this_select:
qq = pkg in always_visit;
if qq: assert( base[1][base[0]] == pkg );
if base[0] == -1 or qq:
base[0] = i;
if pkg.repoidx >= 0: # -1 ... already installed
( result, installs ) = upd( pkg, candidate, always_visit );
if result: all_installs += installs;
break;
if not result:
for base, prev_idx in remember_idx:
base[0] = prev_idx;
return ( False, [] );
do_with = 'i'; # install, dpkg -i
if get_installed(candidate.pname): do_with = 'u'; # dpkg --update-avail
all_installs.append((do_with,candidate));
for dep_pname in candidate.depends.keys():
dts = dependents.get(dep_pname,None);
if dts: dts.add((candidate.pname,candidate.arch)); #print(inst.pname,inst.dependents,pname_arch[0]);
else: dependents[dep_pname] = set([ (candidate.pname,candidate.arch) ]);
return ( True, all_installs );
instFile = None; dldFile = None; sha256File = None;
def get_packages_4_update(instfile_param):
global tryupd, updseq, topOnly; global already_selected, already_selected_at_top;
global instfile, repos, verbose; instfile = instfile_param;
already_selected = set(); already_selected_at_top = set();
def get_updates(pname_arch,inst_pkg,first_level):
global tryupd, updseq, topOnly; global already_selected, already_selected_at_top;
if pname_arch in already_selected:
if pname_arch in already_selected_at_top: # removed from here: if not first_level and ...
updseq.remove(pname_arch);
if topOnly:
already_selected_at_top.remove(pname_arch);
else:
updseq.append(pname_arch);
#print("deleted:",pname_arch);
return;
already_selected.add(pname_arch);
availables = pkgs.get(pname_arch[0],[]);
choices = []; deps = set();
for a in availables:
if arch_is_compatible(inst_pkg.arch,a.arch) and cmp_ver(inst_pkg.ver,a.ver) < 0:
choices.append(a);
for dep_pname in a.depends:
deps.add(dep_pname);
if choices:
tryupd[pname_arch] = [ -1, choices ];
#print(first_level,pname_arch,deps);
if first_level or not topOnly:
updseq.append(pname_arch);
already_selected_at_top.add(pname_arch);
for dep_pname in deps:
( inst, arch ) = get_with_arch_like( installed, dep_pname, pname_arch[1] );
if inst:
#assert(pname_arch not in inst.dependents);
get_updates((dep_pname,arch),inst,False);
else: print("not installed:",dep_pname,file=sys.stderr);
#print("trying to update",pname_arch[0]);
for pname_arch, inst_pkg in installed.items():
get_updates( pname_arch, inst_pkg, True );
already_selected = None; already_selected_at_top = None;
#updseq.reverse(); # for testing downward propagation only
#print(updseq);
all_dlds = []; errdld = 0;
for pname_arch in updseq:
(idx,lis) = tryupd[pname_arch];
if idx==-1:
print("trying to update",pname_arch[0]);
for i, candidate in zip(count(),lis):
tryupd[pname_arch][0] = i;
(result,installs) = upd(candidate,None,set());
if result:
print(repr(installs));
if verbose: print(" "+reduce(lambda x,y: x+"\n "+y,map(lambda inst: repr((inst[0],inst[1].pname,inst[1].ver,inst[1].repoidx)),installs)));
if not dryRun:
for itype, pkg in installs:
r = repos[pkg.repoidx>>3]; urlpraefix = r[0];
dldurl = conc_url([urlpraefix,pkg.filename]); basename = os.path.basename(pkg.filename);
if dldFile: print("torsocks wget -c "+dldurl,file=dldFile);
if instFile:
if itype=='i': print("dpkg -i "+os.path.basename(pkg.filename),file=instFile);
else: print("dpkg --update-avail "+basename,file=instFile);
if sha256File: print(pkg.sha256+" "+basename,file=sha256File);
all_dlds.append((dldurl,basename,pkg.sha256));
break;
else:
tryupd[pname_arch][0] = -1;
else:
print(" already being updated",pname_arch[0]);
if shallDownload:
if verbose: print("starting downloads");
for dldurl, filename, sha256 in all_dlds:
print("downloading",filename,end="");
real_sha256 = save_file(dldurl,filename);
if real_sha256 == sha256: print("\t\tOK");
else: print("\t\tERROR"); errdld+=1;
if errdld: print("%i erroneous downloads" % errdld);
else: print("all dlds ok");
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def calcCommDep(commpkgs):
global installed, already_seen, topmost_deps;
def get_dependencies(pname,pkg):
global installed, already_seen;
pkgdesc = (pname,pkg.arch);
if pkgdesc in already_seen:
return {};
already_seen.add(pkgdesc);
dependencies = {};
for dep_pname in pkg.depends:
for candidate_pname in [ dep_pname ] + what_provides.get(dep_pname,[]):
( dep_pkg, dep_arch ) = get_with_arch_like(installed,candidate_pname,pkg.arch);
if dep_pkg: break;
if not dep_pkg:
print("required package dependency of %s seems not installed: %s." % (pname,dep_pname),file=sys.stderr);
continue;
dependencies[(dep_pname,dep_arch)] = pkg;
new_dependencies = get_dependencies(dep_pname,dep_pkg);
dependencies.update(new_dependencies);
return dependencies;
deps_by_pkg = []; any_pkg_not_found = False;
for pname in commpkgs:
pname_arch = pname.split(':',1);
if len(pname_arch) <= 1:
for candidate_pname in [ pname ] + what_provides.get(pname,[]):
pkg = get_installed(candidate_pname);
if pkg: break;
else:
pkg = installed.get(tuple(pname_arch));
if not pkg:
any_pkg_not_found = True;
print("package '%s' not installed." % pname, file=sys.stderr);
continue;
already_seen = set([]);
deps_by_pkg.append( get_dependencies(pname,pkg) );
def filter_topmost_deps(desc,pkg,topmost):
global installed, topmost_deps, already_seen;
if desc in already_seen:
return;
already_seen.add(desc);
if topmost: topmost_deps.add(desc);
elif desc in topmost_deps: topmost_deps.remove(desc);
for dep_pname in pkg.depends:
( dep_pkg, dep_arch ) = get_with_arch_like(installed,dep_pname,pkg.arch);
if not dep_pkg: continue;
filter_topmost_deps( (dep_pname,dep_arch), dep_pkg, False );
if any_pkg_not_found: print(file=sys.stderr); return;
commdeps = deps_by_pkg[0];
for deps in deps_by_pkg[1:]:
#commdeps = commdeps.intersection(deps);
for pkgdesc in list(commdeps.keys()):
if pkgdesc not in deps:
del commdeps[pkgdesc];
topmost_deps = set([]); already_seen = set([]);
for (desc,pkg) in commdeps.items():
filter_topmost_deps(desc,pkg,True);
print("common dependencies:");
for (pname,arch) in topmost_deps:
print("",pname,arch);
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def addpkg(pkgname,pkg):
global pkgs;
pkglis = pkgs.get(pkgname,[]);
pkglis.append(pkg);
pkgs[pkgname] = pkglis;
def prepare_pkgs_sort_dependents():
global pkgs, dependents;
for name, speclis in pkgs.items():
speclis.sort(reverse=True);
#if name in ['libaom0','libdvbpsi10','apache2','vlc']:
#if name in ['libarchive13']:
#print(name,repr(speclis),speclis[0].depends,speclis[0].conflicts);
#sys.exit(0);
for ( pname, arch ), pkg in installed.items():
for dep_pname in pkg.depends:
dts = dependents.get(dep_pname,None);
if dts: dts.add((pname,arch)); #print(inst.pname,inst.dependents,pname_arch[0]);
else: dependents[dep_pname] = set([ (pname,arch) ]);
#
# output like (list of versions is and-ed; i.e. it may depict a range of versions):
# apache2 {'apache2-bin': [('=', '2.4.38-3+deb10u1')], 'apache2-data': [('=', '2.4.38-3+deb10u1')], 'lsb-base': None, 'mime-support': None, ... }
#
def process_deps(depends,depends_or,s):
def split_pkg_ver(dep):
( dep_pkg, dep_ver ) = ( dep.strip().split(' ',1) + ["()"] )[:2];
dep_pkg = dep_pkg.split(':',1)[0];
dep_ver = dep_ver.strip('()').split(' ',1);
if dep_ver != ['']: dep_ver = tuple(dep_ver);
else: dep_ver = None;
return (dep_pkg,dep_ver);
if s == "": return;
for deplis in s.split(','):
deplis = deplis.split('|');
if len(deplis) == 0:
pass;
elif len(deplis) <= 1:
( dep_pkg, dep_ver ) = split_pkg_ver(deplis[0]);
pkg_dep = depends.get(dep_pkg,None);
if pkg_dep == None:
if dep_ver: depends[dep_pkg] = [ dep_ver ];
else: depends[dep_pkg] = None;
else:
if dep_ver: pkg_dep.append(dep_ver);
else:
deps_or = [];
for dep in deplis:
( dep_pkg, dep_ver ) = split_pkg_ver(dep);
deps_or.append( (dep_pkg,)+dep_ver if dep_ver else (dep_pkg,None,None) );
depends_or.append(deps_or);
what_provides = {};
def register_what_provides(pname,provides_list):
global what_provides;
for provides in provides_list.split(','):
provides = provides.strip();
existing = what_provides.get(provides,None);
if existing: existing.append(pname);
else: what_provides[provides] = [ pname ];
def fetch_repo_desc(repos):
#if not repo.endswith('/'): repo += '/';
repoidx = 0;
for url, subdir, sections in repos:
if len(sections) > 8: print("at most 8 sections allowed per repo!",sys.stderr);
for section in sections[:8]:
baseurl = conc_url([url,'dists',subdir]);
release_file = get_file_content(baseurl+'/Release').decode('latin_1').split('\n');
relfiles = {}; sumtype=None;
for line in release_file:
if not line: continue;
if line[-1]==':':
sumtype=line[:-1];
if sumtype == "size": sumtype = None;
this_pos = find(sumtype,hash_prefs);
mo = sumline.match(line);
if mo and sumtype:
filename = mo.group('filename');
hashsum = mo.group('sum');
if filename not in relfiles: relfiles[filename] = { 'size': int(mo.group('size')), 'hashtype': sumtype, 'hash': hashsum };
else:
prev_sumtype = relfiles[filename]['hashtype'];
prev_pos = find(prev_sumtype,hash_prefs);
if this_pos < prev_pos:
relfiles[filename]['hashtype'] = sumtype;
relfiles[filename]['hash'] = hashsum;
if relfiles[filename]['size'] != int(mo.group('size')):
print("error: Release file states different file sizes for file %s." % filename,file=sys.stderr);
#print(relfiles);
release_file = None;
for architecture in architectures:
idxfile = None;
for suffix in [".bz2",".xz",".gz",""]:
this_idxfile = 'main/binary-'+architecture+'/Packages'+suffix;
if this_idxfile in relfiles:
idxfile = this_idxfile;
break;
if idxfile == None:
print("Indexfile '"+'main/binary-'+architecture+'/Packages(.bz2|.xz|.gz)'+"' not found for repo '%s'!" % baseurl,file=sys.stderr);
sys.exit(2);
goodIdx = False;
for i in range(max_retries):
pkgs_file = get_file_content(baseurl+'/'+idxfile);
file_hash = get_hash( pkgs_file, relfiles[idxfile]['hashtype'] );
if file_hash == relfiles[idxfile]['hash']:
goodIdx = True;
if find(relfiles[idxfile]['hashtype'],hash_prefs) >= good_hash_prefs:
print("warning: unsafe checksum (%s) for file %s." % (relfiles[idxfile]['hashtype'],idxfile),file=sys.stderr);
break;
print("wrong hash for file %s/%s: expected %s, found %s." % (baseurl,idxfile,relfiles[idxfile]['hash'],file_hash) );
if not goodIdx:
sys.exit(2);
if suffix == ".bz2": pkgs_file =bz2.decompress(pkgs_file);
elif suffix == ".xz": pkgs_file = lzma.decompress(pkgs_file);
elif suffix == ".gz": pkgs_file = gzip.decompress(pkgs_file);
pname = None; depends = {}; depends_or = []; conflicts = {};
for line in pkgs_file.decode('latin1').split('\n'):
#print(line);
if line == "" and pname:
addpkg(pname,package(pname,version,arch,repoidx,filename,sha256,size,depends,depends_or,conflicts));
pname = None; version = None; arch = None; filename = None; size = None; sha256 = None; depends = {}; depends_or = []; conflicts = {};
toks = line.split(':',1);
if len(toks) >= 2:
(tag,rest) = toks;
rest = rest.strip(' \t');
if tag == "Package": pname = rest;
elif tag == "Version": version = rest;
elif tag == "Architecture": arch = rest;
elif tag == "Filename": filename = rest;
elif tag == "SHA256": sha256 = rest;
elif tag == "Size": size = rest;
elif tag == "Depends":
process_deps(depends,depends_or,rest);
elif tag == "Conflicts" or tag == "Breaks":
process_deps(conflicts,None,rest);
elif tag == "Provides": register_what_provides(pname,rest);
if pname:
addpkg(pname,package(pname,version,arch,repoidx,filename,sha256,size,depends,depends_or,conflicts));
repoidx += 1;
repoidx = ( repoidx & ~7 ) + 8;
installed = {};
def addinst(pname,version,arch,depends_txt,conflicts_txt):
global installed;
depends = {}; depends_or = []; conflicts = {};
process_deps( depends, depends_or, depends_txt );
process_deps( conflicts, None, conflicts_txt );
pkg = package(pname,version,arch,-1,None,None,None,depends,depends_or,conflicts);
if pname in installed: raise Exception("package %s installed two times!" % pname)
installed[(pname,arch)] = pkg;
addpkg(pname,pkg);
def getinst(idbfd=None):
pname = None; depends_txt = ""; conflicts_txt = ""; provides_txt = "";
with open("/var/lib/dpkg/status","r") as fd:
for line in fd:
line = line.rstrip(' \t\r\n');
if line == "" and pname:
if idbfd != None: print(pname,version,arch,depends_txt,"#",conflicts_txt,"#",provides_txt,file=idbfd);
else: addinst(pname,version,arch,depends_txt,conflicts_txt); register_what_provides(pname,provides_txt);
pname = None; version = None; arch = None; depends_txt = ""; conflicts_txt = ""; provides_txt = "";
toks = line.split(':',1);
if len(toks) >= 2:
(tag,rest) = toks; rest = rest.lstrip(' \t')
if tag == "Package": pname = rest;
elif tag == "Version": version = rest;
elif tag == "Architecture": arch = rest;
elif tag == "Depends": depends_txt = rest;
elif tag == "Conflicts" or tag == "Breaks":
if conflicts_txt: conflicts_txt += ", " + rest;
else: conflicts_txt = rest;
elif tag == "Provides": provides_txt = rest;
if pname:
if idbfd != None: print(pname,version,arch,depends_txt,"#",conflicts_txt,"#",provides_txt,file=idbfd);
else: addinst(pname,version,arch,depends_txt,conflicts_txt); register_what_provides(pname,provides_txt);
def readinstdb(filename):
global installed;
with open(filename,"r") as fd:
for line in fd:
line = line.rstrip(' \t\r\n');
(pname,version,arch,rest) = line.split(' ',3);
try:
(depends_txt,conflicts_txt,provides_txt) = rest.split(' #',2);
except Exception as e:
print("unable to split 3 tags by #:",line,file=sys.stderr);
raise e;
addinst(pname,version,arch,depends_txt,conflicts_txt);
register_what_provides(pname,provides_txt);
if len(sys.argv) >= 3 and sys.argv[1] == "getinst":
if len(sys.argv) > 3: print("spurious params: "+" ".join(sys.argv[3:]),file=sys.stderr);
with os.fdopen(os.open(sys.argv[2],os.O_CREAT|os.O_EXCL|os.O_WRONLY),'w') as idbfd:
getinst(idbfd);
sys.exit(0);
pkglis_file = None; verbose = False; shallDownload = True;
if len(sys.argv) >= 4 and sys.argv[1] == "cmpver":
print(cmp_ver(sys.argv[2],sys.argv[3]));
sys.exit(0);
if len(sys.argv) >= 3 and sys.argv[1] == "commdep":
if sys.argv[2] == "--pkglis":
pkglis_file = sys.argv[3];
readinstdb(pkglis_file);
commpkgs = sys.argv[4:];
else:
getinst();
commpkgs = sys.argv[2:];
calcCommDep(commpkgs);
sys.exit(0);
while len(sys.argv) >= 2:
if sys.argv[1] == "--agent":
agent = sys.argv[2];
sys.argv = sys.argv[:1] + sys.argv[3:];
elif sys.argv[1] == "--pkglis":
pkglis_file = sys.argv[2];
sys.argv = sys.argv[:1] + sys.argv[3:];
elif sys.argv[1] == "--arches":
architectures = sys.argv[2].split(':');
sys.argv = sys.argv[:1] + sys.argv[3:];
elif sys.argv[1] == "--verbose":
verbose = True;
sys.argv = sys.argv[:1] + sys.argv[2:];
elif sys.argv[1] == "--no-download":
shallDownload = False;
sys.argv = sys.argv[:1] + sys.argv[2:];
elif sys.argv[1] == "--upd-repo-only":
repos = repos[:1];
sys.argv = sys.argv[:1] + sys.argv[2:];
elif sys.argv[1] == "--dry-run":
dryRun = True;
sys.argv = sys.argv[:1] + sys.argv[2:];
elif sys.argv[1] == "--top-only":
topOnly = True;
sys.argv = sys.argv[:1] + sys.argv[2:];
else:
break;
agent_strs = { "apt": "Debian APT-HTTP/1.3 (1.8.2)", "wget": "Wget/1.20.1 (linux-gnu)", "mozilla": "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0" }
def main():
if verbose: print("reading installation database",file=sys.stderr);
instfile = sys.stdout;
if pkglis_file:
readinstdb(pkglis_file);
else:
getinst();
if verbose: print("fetching repo descriptions",file=sys.stderr);
fetch_repo_desc(repos);
prepare_pkgs_sort_dependents();
if verbose: print("calculating updates",file=sys.stderr);
get_packages_4_update(instfile);
if len(sys.argv) <= 1 or len(sys.argv) > 4 or sys.argv[1] == "--help":
print("torsocks fetchupd.py [--agent browserid] [--arches amd64:i386] [--pkglis installed.pkgs] [other options] [--upd-repo-only] targetdir",file=sys.stdout);
print(" other options: --no-download --dry-run --top-only (only top level dependencies, no libraries)",file=sys.stdout);
print("fetchupd getinst installed.pkgs",file=sys.stdout);
print("fetchupd cmpver ver1 ver2",file=sys.stdout);
print("fetchupd commdep [--pkglis installed.pkgs] pkg1 pkg2 pkg3:arch3 ...",file=sys.stdout);
print(" change fetchupd.py by hand in order to adjust repos as found in /etc/apt/sources.list",file=sys.stdout);
print(" user agent string may be one of 'apt, 'wget', '' or 'mozilla'; default is 'apt', '' uses the python3 default. You may also specify a full string like 'Debian APT-HTTP/1.3 (1.8.2)'.",file=sys.stdout);
print(" creates scripts with name targetdir/install-updates and targetdir/download-updates and a SHA-file with name targetdir/SHA256SUMS",file=sys.stdout);
print("",file=sys.stdout);
else:
putdir = sys.argv[1];
if( len(sys.argv) > 2 ): subdir = sys.argv[2];
if( len(sys.argv) > 3 ): repo = sys.argv[3];
if( len(sys.argv) > 4 ): agent = sys.argv[4];
agent = agent_strs.get(agent,agent);
if verbose:
print("repos:", len(repos));
if(agent): print("agent:",agent);
print();
prevdir = os.getcwd();
os.chdir(putdir);
try:
if dryRun:
main();
else:
with os.fdopen(os.open("install-updates",os.O_CREAT|os.O_EXCL|os.O_WRONLY,0o777),'w') as this_instfile:
with os.fdopen(os.open("download-updates",os.O_CREAT|os.O_EXCL|os.O_WRONLY,0o777),'w') as this_dldfile:
with open("SHA256SUMS",'a') as this_shafile:
instFile = this_instfile;
dldFile = this_dldfile;
sha256File = this_shafile;
main();
finally:
os.chdir(prevdir);
print();