From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from lists.gentoo.org (pigeon.gentoo.org [208.92.234.80]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by finch.gentoo.org (Postfix) with ESMTPS id A1653138334 for ; Fri, 4 Oct 2019 05:01:45 +0000 (UTC) Received: from pigeon.gentoo.org (localhost [127.0.0.1]) by pigeon.gentoo.org (Postfix) with SMTP id 67601E0878; Fri, 4 Oct 2019 05:01:43 +0000 (UTC) Received: from mail-io1-xd43.google.com (mail-io1-xd43.google.com [IPv6:2607:f8b0:4864:20::d43]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by pigeon.gentoo.org (Postfix) with ESMTPS id 460ACE0878 for ; Fri, 4 Oct 2019 05:01:43 +0000 (UTC) Received: by mail-io1-xd43.google.com with SMTP id c6so10697292ioo.13 for ; Thu, 03 Oct 2019 22:01:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gentoo-org.20150623.gappssmtp.com; s=20150623; h=mime-version:references:in-reply-to:from:date:message-id:subject:to :cc; bh=Fquh2MSsxtcn5eh7OLmcVlJrPgDcWgyE4T/TDYb3udE=; b=2TbJwDXgKh5FGUpkuzLn4y+CujWLg9Ct0XJ7K1P0RsKEOy4xJwvXh0g/mTETPn4snj 8FWv6tYAOYiK4QI01mEyNixFpqdSCVI0Jn+3pQmEH7/Ppp7ov/Eu3k+kkLrdQCdV5Sv+ 5itPaNatY+D3rrCf77oy3fD5bl1uzAWJXP7lJcQuST7eaLeZqw+jF36VTWA/2NZHPJ74 o7DNDqXtB9vMpX7kFUeud3QOC+DvBYIuRsRLfWsnxvO35FPkvPveNqMXiUK0/Amg4tX2 1C6qBVF5xrfVvU1rcgonKBcqJLBIsk3WafpcHE2xJNFwoJVyubDUS6nuyuwDxk1436oz A8oQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:references:in-reply-to:from:date :message-id:subject:to:cc; bh=Fquh2MSsxtcn5eh7OLmcVlJrPgDcWgyE4T/TDYb3udE=; b=aTlgf1Eas4ibPvRb5kjKga/TO1L3i7yKbjj9Hya5XNEY5uZ9wvtLrwxJsXCWAhTXiJ zay1ogy2zvcsGeQqUPxIo6CsBgM6UFqDgDtmw3C037ZyDg6eF4TFViQq+9G6fAhziAGC mwQi/sn86AtGjspU9lQb6R4UXIUcLhfLsaAcWmFoum0Wy8Tbh05oUs6wN/PpSk1JZGLr c5rvEpBONcEBNSNgJTPXFdPZhi1036DgUIMtbLfTtgPh3Q2MT9xmKxrmQTVK0P6MTfeG fHlEh+oexb0BKHOxJHaNT/UD1lP6qrmC/zyatNqjD9rQwfFqxLZuASXqub+0ss6Aj4PK AYHw== X-Gm-Message-State: APjAAAVJQMfal04DucGaLYIcEo2L0nYXIpQ9ntuUON6c0DjqKinKQ40h g3uYn9qq3a6MQA201oAm3radfnCSbqe7pFLDHf0yvl4V X-Google-Smtp-Source: APXvYqynExcGQBQcLTaaJ9WYEaz6q5J9+zHxwg+JpS16wPqQtIJp6BYyo7SYEC2wIHUS2EsuU8qXwMdzXEc2jgNbhes= X-Received: by 2002:a92:9fdb:: with SMTP id z88mr14796652ilk.38.1570165302080; Thu, 03 Oct 2019 22:01:42 -0700 (PDT) Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-Id: Gentoo Linux mail X-BeenThere: gentoo-portage-dev@lists.gentoo.org Reply-to: gentoo-portage-dev@lists.gentoo.org X-Auto-Response-Suppress: DR, RN, NRN, OOF, AutoReply MIME-Version: 1.0 References: <20191003163632.7231-1-mgorny@gentoo.org> In-Reply-To: <20191003163632.7231-1-mgorny@gentoo.org> From: Alec Warner Date: Thu, 3 Oct 2019 22:01:31 -0700 Message-ID: Subject: Re: [gentoo-portage-dev] [PATCH v2] fetch: Support GLEP 75 mirror structure To: gentoo-portage-dev@lists.gentoo.org Cc: =?UTF-8?B?TWljaGHFgiBHw7Nybnk=?= Content-Type: multipart/alternative; boundary="00000000000011d97005940e995e" X-Archives-Salt: 1ee4eb1a-d8de-4baf-b2e9-685562716239 X-Archives-Hash: f3a0aeac86b2a64b23a03e00f774e110 --00000000000011d97005940e995e Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable On Thu, Oct 3, 2019 at 9:37 AM Micha=C5=82 G=C3=B3rny w= rote: > Add a support for the subset of GLEP 75 needed by Gentoo Infra. This > includes fetching and parsing layout.conf, and support for flat layout > and filename-hash layout with cutoffs being multiplies of 4. > > Bug: https://bugs.gentoo.org/646898 > Signed-off-by: Micha=C5=82 G=C3=B3rny > --- > lib/portage/package/ebuild/fetch.py | 139 +++++++++++++++++++++++++++- > 1 file changed, 135 insertions(+), 4 deletions(-) > > Changes in v2: switched to a more classy layout to make the code > reusable in emirrordist. > > diff --git a/lib/portage/package/ebuild/fetch.py > b/lib/portage/package/ebuild/fetch.py > index 227bf45ae..18e3d390a 100644 > --- a/lib/portage/package/ebuild/fetch.py > +++ b/lib/portage/package/ebuild/fetch.py > @@ -7,12 +7,15 @@ __all__ =3D ['fetch'] > > import errno > import io > +import itertools > +import json > import logging > import random > import re > import stat > import sys > import tempfile > +import time > > from collections import OrderedDict > > @@ -27,14 +30,17 @@ portage.proxy.lazyimport.lazyimport(globals(), > 'portage.package.ebuild.doebuild:doebuild_environment,' + \ > '_doebuild_spawn', > 'portage.package.ebuild.prepare_build_dirs:prepare_build_dirs', > + > 'portage.util.configparser:SafeConfigParser,read_configs,NoOptionError', > + 'portage.util._urlopen:urlopen', > ) > > from portage import os, selinux, shutil, _encodings, \ > _movefile, _shell_quote, _unicode_encode > from portage.checksum import (get_valid_checksum_keys, perform_md5, > verify_all, > - _filter_unaccelarated_hashes, _hash_filter, _apply_hash_filter) > + _filter_unaccelarated_hashes, _hash_filter, _apply_hash_filter, > + checksum_str) > from portage.const import BASH_BINARY, CUSTOM_MIRRORS_FILE, \ > - GLOBAL_CONFIG_PATH > + GLOBAL_CONFIG_PATH, CACHE_PATH > from portage.data import portage_gid, portage_uid, secpass, > userpriv_groups > from portage.exception import FileNotFound, OperationNotPermitted, \ > PortageException, TryAgain > @@ -253,6 +259,130 @@ _size_suffix_map =3D { > 'Y' : 80, > } > > + > +class FlatLayout(object): > + def get_path(self, filename): > + return filename > + > + > +class FilenameHashLayout(object): > + def __init__(self, algo, cutoffs): > + self.algo =3D algo > + self.cutoffs =3D [int(x) for x in cutoffs.split(':')] > + > + def get_path(self, filename): > + fnhash =3D checksum_str(filename.encode('utf8'), self.alg= o) > + ret =3D '' > + for c in self.cutoffs: > + assert c % 4 =3D=3D 0 > I'm not quite sure what this assert is doing. I'm not super in favor of asserts (I'd rather see an exception like raise FooError("..."), but if you are going to use it please use something like: assert c %4 =3D=3D 0, "Some description of why we put this assert here so i= f it fires we can do something useful." + c =3D c // 4 > + ret +=3D fnhash[:c] + '/' > + fnhash =3D fnhash[c:] > + return ret + filename > + > + > +class MirrorLayoutConfig(object): > + """ > + Class to read layout.conf from a mirror. > + """ > + > + def __init__(self): > + self.structure =3D () > + > + def read_from_file(self, f): > + cp =3D SafeConfigParser() > + read_configs(cp, [f]) > + vals =3D [] > + for i in itertools.count(): > + try: > + vals.append(tuple(cp.get('structure', '%d= ' > % i).split())) > + except NoOptionError: > + break > + self.structure =3D tuple(vals) > + > + def serialize(self): > + return self.structure > + > + def deserialize(self, data): > + self.structure =3D data > + > + @staticmethod > + def validate_structure(val): > + if val =3D=3D ('flat',): > + return True > + if val[0] =3D=3D 'filename-hash' and len(val) =3D=3D 3: > + if val[1] not in get_valid_checksum_keys(): > + return False > + # validate cutoffs > + for c in val[2].split(':'): > + try: > + c =3D int(c) > + except ValueError: > + break > + else: > + if c % 4 !=3D 0: > + break > + else: > + return True > + return False > + return False > + > + def get_best_supported_layout(self): > + for val in self.structure: > + if self.validate_structure(val): > + if val[0] =3D=3D 'flat': > + return FlatLayout() > + elif val[0] =3D=3D 'filename-hash': > + return FilenameHashLayout(val[1], > val[2]) > + else: > + # fallback > + return FlatLayout() > + > + > +def get_mirror_url(mirror_url, filename, eroot): > + """ > + Get correct fetch URL for a given file, accounting for mirror > + layout configuration. > + > + @param mirror_url: Base URL to the mirror (without '/distfiles') > + @param filename: Filename to fetch > + @param eroot: EROOT to use for the cache file > + @return: Full URL to fetch > + """ > + > + mirror_conf =3D MirrorLayoutConfig() > + > + cache_file =3D os.path.join(eroot, CACHE_PATH, > 'mirror-metadata.json') > + try: > + with open(cache_file, 'r') as f: > + cache =3D json.load(f) > + except (IOError, ValueError): > + cache =3D {} > + > + ts, data =3D cache.get(mirror_url, (0, None)) > + # refresh at least daily > + if ts >=3D time.time() - 86400: > + mirror_conf.deserialize(data) > + else: > + try: > + f =3D urlopen(mirror_url + '/distfiles/layout.con= f') > + try: > + data =3D io.StringIO(f.read().decode('utf= 8')) > + finally: > + f.close() > + > + mirror_conf.read_from_file(data) > + except IOError: > + pass > + > + cache[mirror_url] =3D (time.time(), mirror_conf.serialize= ()) > + with open(cache_file, 'w') as f: > + json.dump(cache, f) > + > + return (mirror_url + "/distfiles/" + > + > mirror_conf.get_best_supported_layout().get_path(filename)) > + > + > def fetch(myuris, mysettings, listonly=3D0, fetchonly=3D0, > locks_in_subdir=3D".locks", use_locks=3D1, try_mirrors=3D1, diges= ts=3DNone, > allow_missing_digests=3DTrue): > @@ -434,8 +564,9 @@ def fetch(myuris, mysettings, listonly=3D0, fetchonly= =3D0, > for myfile, myuri in file_uri_tuples: > if myfile not in filedict: > filedict[myfile]=3D[] > - for y in range(0,len(locations)): > - > filedict[myfile].append(locations[y]+"/distfiles/"+myfile) > + for l in locations: > + filedict[myfile].append(get_mirror_url(l, > myfile, > + mysettings["EROOT"])) > if myuri is None: > continue > if myuri[:9]=3D=3D"mirror://": > -- > 2.23.0 > > > --00000000000011d97005940e995e Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: base64 PGRpdiBkaXI9Imx0ciI+PGRpdiBkaXI9Imx0ciI+PGJyPjwvZGl2Pjxicj48ZGl2IGNsYXNzPSJn bWFpbF9xdW90ZSI+PGRpdiBkaXI9Imx0ciIgY2xhc3M9ImdtYWlsX2F0dHIiPk9uIFRodSwgT2N0 IDMsIDIwMTkgYXQgOTozNyBBTSBNaWNoYcWCIEfDs3JueSAmbHQ7PGEgaHJlZj0ibWFpbHRvOm1n b3JueUBnZW50b28ub3JnIj5tZ29ybnlAZ2VudG9vLm9yZzwvYT4mZ3Q7IHdyb3RlOjxicj48L2Rp dj48YmxvY2txdW90ZSBjbGFzcz0iZ21haWxfcXVvdGUiIHN0eWxlPSJtYXJnaW46MHB4IDBweCAw cHggMC44ZXg7Ym9yZGVyLWxlZnQ6MXB4IHNvbGlkIHJnYigyMDQsMjA0LDIwNCk7cGFkZGluZy1s ZWZ0OjFleCI+QWRkIGEgc3VwcG9ydCBmb3IgdGhlIHN1YnNldCBvZiBHTEVQIDc1IG5lZWRlZCBi eSBHZW50b28gSW5mcmEuwqAgVGhpczxicj4NCmluY2x1ZGVzIGZldGNoaW5nIGFuZCBwYXJzaW5n IGxheW91dC5jb25mLCBhbmQgc3VwcG9ydCBmb3IgZmxhdCBsYXlvdXQ8YnI+DQphbmQgZmlsZW5h bWUtaGFzaCBsYXlvdXQgd2l0aCBjdXRvZmZzIGJlaW5nIG11bHRpcGxpZXMgb2YgNC48YnI+DQo8 YnI+DQpCdWc6IDxhIGhyZWY9Imh0dHBzOi8vYnVncy5nZW50b28ub3JnLzY0Njg5OCIgcmVsPSJu b3JlZmVycmVyIiB0YXJnZXQ9Il9ibGFuayI+aHR0cHM6Ly9idWdzLmdlbnRvby5vcmcvNjQ2ODk4 PC9hPjxicj4NClNpZ25lZC1vZmYtYnk6IE1pY2hhxYIgR8Ozcm55ICZsdDs8YSBocmVmPSJtYWls dG86bWdvcm55QGdlbnRvby5vcmciIHRhcmdldD0iX2JsYW5rIj5tZ29ybnlAZ2VudG9vLm9yZzwv YT4mZ3Q7PGJyPg0KLS0tPGJyPg0KwqBsaWIvcG9ydGFnZS9wYWNrYWdlL2VidWlsZC9mZXRjaC5w eSB8IDEzOSArKysrKysrKysrKysrKysrKysrKysrKysrKystPGJyPg0KwqAxIGZpbGUgY2hhbmdl ZCwgMTM1IGluc2VydGlvbnMoKyksIDQgZGVsZXRpb25zKC0pPGJyPg0KPGJyPg0KQ2hhbmdlcyBp biB2Mjogc3dpdGNoZWQgdG8gYSBtb3JlIGNsYXNzeSBsYXlvdXQgdG8gbWFrZSB0aGUgY29kZTxi cj4NCnJldXNhYmxlIGluIGVtaXJyb3JkaXN0Ljxicj4NCjxicj4NCmRpZmYgLS1naXQgYS9saWIv cG9ydGFnZS9wYWNrYWdlL2VidWlsZC9mZXRjaC5weSBiL2xpYi9wb3J0YWdlL3BhY2thZ2UvZWJ1 aWxkL2ZldGNoLnB5PGJyPg0KaW5kZXggMjI3YmY0NWFlLi4xOGUzZDM5MGEgMTAwNjQ0PGJyPg0K LS0tIGEvbGliL3BvcnRhZ2UvcGFja2FnZS9lYnVpbGQvZmV0Y2gucHk8YnI+DQorKysgYi9saWIv cG9ydGFnZS9wYWNrYWdlL2VidWlsZC9mZXRjaC5weTxicj4NCkBAIC03LDEyICs3LDE1IEBAIF9f YWxsX18gPSBbJiMzOTtmZXRjaCYjMzk7XTxicj4NCjxicj4NCsKgaW1wb3J0IGVycm5vPGJyPg0K wqBpbXBvcnQgaW88YnI+DQoraW1wb3J0IGl0ZXJ0b29sczxicj4NCitpbXBvcnQganNvbjxicj4N CsKgaW1wb3J0IGxvZ2dpbmc8YnI+DQrCoGltcG9ydCByYW5kb208YnI+DQrCoGltcG9ydCByZTxi cj4NCsKgaW1wb3J0IHN0YXQ8YnI+DQrCoGltcG9ydCBzeXM8YnI+DQrCoGltcG9ydCB0ZW1wZmls ZTxicj4NCitpbXBvcnQgdGltZTxicj4NCjxicj4NCsKgZnJvbSBjb2xsZWN0aW9ucyBpbXBvcnQg T3JkZXJlZERpY3Q8YnI+DQo8YnI+DQpAQCAtMjcsMTQgKzMwLDE3IEBAIHBvcnRhZ2UucHJveHku bGF6eWltcG9ydC5sYXp5aW1wb3J0KGdsb2JhbHMoKSw8YnI+DQrCoCDCoCDCoCDCoCAmIzM5O3Bv cnRhZ2UucGFja2FnZS5lYnVpbGQuZG9lYnVpbGQ6ZG9lYnVpbGRfZW52aXJvbm1lbnQsJiMzOTsg KyBcPGJyPg0KwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgJiMzOTtfZG9lYnVpbGRfc3Bhd24mIzM5 Oyw8YnI+DQrCoCDCoCDCoCDCoCAmIzM5O3BvcnRhZ2UucGFja2FnZS5lYnVpbGQucHJlcGFyZV9i dWlsZF9kaXJzOnByZXBhcmVfYnVpbGRfZGlycyYjMzk7LDxicj4NCivCoCDCoCDCoCDCoCYjMzk7 cG9ydGFnZS51dGlsLmNvbmZpZ3BhcnNlcjpTYWZlQ29uZmlnUGFyc2VyLHJlYWRfY29uZmlncyxO b09wdGlvbkVycm9yJiMzOTssPGJyPg0KK8KgIMKgIMKgIMKgJiMzOTtwb3J0YWdlLnV0aWwuX3Vy bG9wZW46dXJsb3BlbiYjMzk7LDxicj4NCsKgKTxicj4NCjxicj4NCsKgZnJvbSBwb3J0YWdlIGlt cG9ydCBvcywgc2VsaW51eCwgc2h1dGlsLCBfZW5jb2RpbmdzLCBcPGJyPg0KwqAgwqAgwqAgwqAg X21vdmVmaWxlLCBfc2hlbGxfcXVvdGUsIF91bmljb2RlX2VuY29kZTxicj4NCsKgZnJvbSBwb3J0 YWdlLmNoZWNrc3VtIGltcG9ydCAoZ2V0X3ZhbGlkX2NoZWNrc3VtX2tleXMsIHBlcmZvcm1fbWQ1 LCB2ZXJpZnlfYWxsLDxicj4NCi3CoCDCoCDCoCDCoF9maWx0ZXJfdW5hY2NlbGFyYXRlZF9oYXNo ZXMsIF9oYXNoX2ZpbHRlciwgX2FwcGx5X2hhc2hfZmlsdGVyKTxicj4NCivCoCDCoCDCoCDCoF9m aWx0ZXJfdW5hY2NlbGFyYXRlZF9oYXNoZXMsIF9oYXNoX2ZpbHRlciwgX2FwcGx5X2hhc2hfZmls dGVyLDxicj4NCivCoCDCoCDCoCDCoGNoZWNrc3VtX3N0cik8YnI+DQrCoGZyb20gcG9ydGFnZS5j b25zdCBpbXBvcnQgQkFTSF9CSU5BUlksIENVU1RPTV9NSVJST1JTX0ZJTEUsIFw8YnI+DQotwqAg wqAgwqAgwqBHTE9CQUxfQ09ORklHX1BBVEg8YnI+DQorwqAgwqAgwqAgwqBHTE9CQUxfQ09ORklH X1BBVEgsIENBQ0hFX1BBVEg8YnI+DQrCoGZyb20gcG9ydGFnZS5kYXRhIGltcG9ydCBwb3J0YWdl X2dpZCwgcG9ydGFnZV91aWQsIHNlY3Bhc3MsIHVzZXJwcml2X2dyb3Vwczxicj4NCsKgZnJvbSBw b3J0YWdlLmV4Y2VwdGlvbiBpbXBvcnQgRmlsZU5vdEZvdW5kLCBPcGVyYXRpb25Ob3RQZXJtaXR0 ZWQsIFw8YnI+DQrCoCDCoCDCoCDCoCBQb3J0YWdlRXhjZXB0aW9uLCBUcnlBZ2Fpbjxicj4NCkBA IC0yNTMsNiArMjU5LDEzMCBAQCBfc2l6ZV9zdWZmaXhfbWFwID0gezxicj4NCsKgIMKgIMKgIMKg ICYjMzk7WSYjMzk7IDogODAsPGJyPg0KwqB9PGJyPg0KPGJyPg0KKzxicj4NCitjbGFzcyBGbGF0 TGF5b3V0KG9iamVjdCk6PGJyPg0KK8KgIMKgIMKgIMKgZGVmIGdldF9wYXRoKHNlbGYsIGZpbGVu YW1lKTo8YnI+DQorwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqByZXR1cm4gZmlsZW5hbWU8YnI+DQor PGJyPg0KKzxicj4NCitjbGFzcyBGaWxlbmFtZUhhc2hMYXlvdXQob2JqZWN0KTo8YnI+DQorwqAg wqAgwqAgwqBkZWYgX19pbml0X18oc2VsZiwgYWxnbywgY3V0b2Zmcyk6PGJyPg0KK8KgIMKgIMKg IMKgIMKgIMKgIMKgIMKgc2VsZi5hbGdvID0gYWxnbzxicj4NCivCoCDCoCDCoCDCoCDCoCDCoCDC oCDCoHNlbGYuY3V0b2ZmcyA9IFtpbnQoeCkgZm9yIHggaW4gY3V0b2Zmcy5zcGxpdCgmIzM5Ozom IzM5OyldPGJyPg0KKzxicj4NCivCoCDCoCDCoCDCoGRlZiBnZXRfcGF0aChzZWxmLCBmaWxlbmFt ZSk6PGJyPg0KK8KgIMKgIMKgIMKgIMKgIMKgIMKgIMKgZm5oYXNoID0gY2hlY2tzdW1fc3RyKGZp bGVuYW1lLmVuY29kZSgmIzM5O3V0ZjgmIzM5OyksIHNlbGYuYWxnbyk8YnI+DQorwqAgwqAgwqAg wqAgwqAgwqAgwqAgwqByZXQgPSAmIzM5OyYjMzk7PGJyPg0KK8KgIMKgIMKgIMKgIMKgIMKgIMKg IMKgZm9yIGMgaW4gc2VsZi5jdXRvZmZzOjxicj4NCivCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDC oCDCoCDCoCDCoGFzc2VydCBjICUgNCA9PSAwPGJyPjwvYmxvY2txdW90ZT48ZGl2Pjxicj48L2Rp dj48ZGl2PkkmIzM5O20gbm90IHF1aXRlIHN1cmUgd2hhdCB0aGlzIGFzc2VydCBpcyBkb2luZy4g SSYjMzk7bSBub3Qgc3VwZXIgaW4gZmF2b3Igb2YgYXNzZXJ0cyAoSSYjMzk7ZCByYXRoZXIgc2Vl IGFuIGV4Y2VwdGlvbiBsaWtlIHJhaXNlIEZvb0Vycm9yKCZxdW90Oy4uLiZxdW90OyksIGJ1dCBp ZiB5b3UgYXJlIGdvaW5nIHRvIHVzZSBpdCBwbGVhc2UgdXNlIHNvbWV0aGluZyBsaWtlOjwvZGl2 PjxkaXY+PGJyPjwvZGl2PjxkaXY+YXNzZXJ0IGMgJTQgPT0gMCwgJnF1b3Q7U29tZSBkZXNjcmlw dGlvbiBvZiB3aHkgd2UgcHV0IHRoaXMgYXNzZXJ0IGhlcmUgc28gaWYgaXQgZmlyZXMgd2UgY2Fu IGRvIHNvbWV0aGluZyB1c2VmdWwuJnF1b3Q7PC9kaXY+PGRpdj48YnI+PC9kaXY+PGJsb2NrcXVv dGUgY2xhc3M9ImdtYWlsX3F1b3RlIiBzdHlsZT0ibWFyZ2luOjBweCAwcHggMHB4IDAuOGV4O2Jv cmRlci1sZWZ0OjFweCBzb2xpZCByZ2IoMjA0LDIwNCwyMDQpO3BhZGRpbmctbGVmdDoxZXgiPg0K K8KgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgYyA9IGMgLy8gNDxicj4NCivCoCDC oCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoHJldCArPSBmbmhhc2hbOmNdICsgJiMzOTsv JiMzOTs8YnI+DQorwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqBmbmhhc2ggPSBm bmhhc2hbYzpdPGJyPg0KK8KgIMKgIMKgIMKgIMKgIMKgIMKgIMKgcmV0dXJuIHJldCArIGZpbGVu YW1lPGJyPg0KKzxicj4NCis8YnI+DQorY2xhc3MgTWlycm9yTGF5b3V0Q29uZmlnKG9iamVjdCk6 PGJyPg0KK8KgIMKgIMKgIMKgJnF1b3Q7JnF1b3Q7JnF1b3Q7PGJyPg0KK8KgIMKgIMKgIMKgQ2xh c3MgdG8gcmVhZCBsYXlvdXQuY29uZiBmcm9tIGEgbWlycm9yLjxicj4NCivCoCDCoCDCoCDCoCZx dW90OyZxdW90OyZxdW90Ozxicj4NCis8YnI+DQorwqAgwqAgwqAgwqBkZWYgX19pbml0X18oc2Vs Zik6PGJyPg0KK8KgIMKgIMKgIMKgIMKgIMKgIMKgIMKgc2VsZi5zdHJ1Y3R1cmUgPSAoKTxicj4N Cis8YnI+DQorwqAgwqAgwqAgwqBkZWYgcmVhZF9mcm9tX2ZpbGUoc2VsZiwgZik6PGJyPg0KK8Kg IMKgIMKgIMKgIMKgIMKgIMKgIMKgY3AgPSBTYWZlQ29uZmlnUGFyc2VyKCk8YnI+DQorwqAgwqAg wqAgwqAgwqAgwqAgwqAgwqByZWFkX2NvbmZpZ3MoY3AsIFtmXSk8YnI+DQorwqAgwqAgwqAgwqAg wqAgwqAgwqAgwqB2YWxzID0gW108YnI+DQorwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqBmb3IgaSBp biBpdGVydG9vbHMuY291bnQoKTo8YnI+DQorwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAg wqAgwqB0cnk6PGJyPg0KK8KgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKg IMKgIMKgdmFscy5hcHBlbmQodHVwbGUoY3AuZ2V0KCYjMzk7c3RydWN0dXJlJiMzOTssICYjMzk7 JWQmIzM5OyAlIGkpLnNwbGl0KCkpKTxicj4NCivCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDC oCDCoCDCoGV4Y2VwdCBOb09wdGlvbkVycm9yOjxicj4NCivCoCDCoCDCoCDCoCDCoCDCoCDCoCDC oCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoGJyZWFrPGJyPg0KK8KgIMKgIMKgIMKgIMKgIMKgIMKg IMKgc2VsZi5zdHJ1Y3R1cmUgPSB0dXBsZSh2YWxzKTxicj4NCis8YnI+DQorwqAgwqAgwqAgwqBk ZWYgc2VyaWFsaXplKHNlbGYpOjxicj4NCivCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoHJldHVybiBz ZWxmLnN0cnVjdHVyZTxicj4NCis8YnI+DQorwqAgwqAgwqAgwqBkZWYgZGVzZXJpYWxpemUoc2Vs ZiwgZGF0YSk6PGJyPg0KK8KgIMKgIMKgIMKgIMKgIMKgIMKgIMKgc2VsZi5zdHJ1Y3R1cmUgPSBk YXRhPGJyPg0KKzxicj4NCivCoCDCoCDCoCDCoEBzdGF0aWNtZXRob2Q8YnI+DQorwqAgwqAgwqAg wqBkZWYgdmFsaWRhdGVfc3RydWN0dXJlKHZhbCk6PGJyPg0KK8KgIMKgIMKgIMKgIMKgIMKgIMKg IMKgaWYgdmFsID09ICgmIzM5O2ZsYXQmIzM5OywpOjxicj4NCivCoCDCoCDCoCDCoCDCoCDCoCDC oCDCoCDCoCDCoCDCoCDCoHJldHVybiBUcnVlPGJyPg0KK8KgIMKgIMKgIMKgIMKgIMKgIMKgIMKg aWYgdmFsWzBdID09ICYjMzk7ZmlsZW5hbWUtaGFzaCYjMzk7IGFuZCBsZW4odmFsKSA9PSAzOjxi cj4NCivCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoGlmIHZhbFsxXSBub3QgaW4g Z2V0X3ZhbGlkX2NoZWNrc3VtX2tleXMoKTo8YnI+DQorwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAg wqAgwqAgwqAgwqAgwqAgwqAgwqAgwqByZXR1cm4gRmFsc2U8YnI+DQorwqAgwqAgwqAgwqAgwqAg wqAgwqAgwqAgwqAgwqAgwqAgwqAjIHZhbGlkYXRlIGN1dG9mZnM8YnI+DQorwqAgwqAgwqAgwqAg wqAgwqAgwqAgwqAgwqAgwqAgwqAgwqBmb3IgYyBpbiB2YWxbMl0uc3BsaXQoJiMzOTs6JiMzOTsp Ojxicj4NCivCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoHRy eTo8YnI+DQorwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAg wqAgwqAgwqAgwqBjID0gaW50KGMpPGJyPg0KK8KgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKg IMKgIMKgIMKgIMKgIMKgIMKgZXhjZXB0IFZhbHVlRXJyb3I6PGJyPg0KK8KgIMKgIMKgIMKgIMKg IMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgYnJlYWs8YnI+DQor wqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqBlbHNlOjxicj4N CivCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDC oCDCoGlmIGMgJSA0ICE9IDA6PGJyPg0KK8KgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKg IMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgYnJlYWs8YnI+DQorwqAgwqAg wqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqBlbHNlOjxicj4NCivCoCDCoCDCoCDCoCDCoCDC oCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoHJldHVybiBUcnVlPGJyPg0KK8KgIMKgIMKg IMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgcmV0dXJuIEZhbHNlPGJyPg0KK8KgIMKgIMKgIMKg IMKgIMKgIMKgIMKgcmV0dXJuIEZhbHNlPGJyPg0KKzxicj4NCivCoCDCoCDCoCDCoGRlZiBnZXRf YmVzdF9zdXBwb3J0ZWRfbGF5b3V0KHNlbGYpOjxicj4NCivCoCDCoCDCoCDCoCDCoCDCoCDCoCDC oGZvciB2YWwgaW4gc2VsZi5zdHJ1Y3R1cmU6PGJyPg0KK8KgIMKgIMKgIMKgIMKgIMKgIMKgIMKg IMKgIMKgIMKgIMKgaWYgc2VsZi52YWxpZGF0ZV9zdHJ1Y3R1cmUodmFsKTo8YnI+DQorwqAgwqAg wqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqBpZiB2YWxbMF0gPT0gJiMz OTtmbGF0JiMzOTs6PGJyPg0KK8KgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKg IMKgIMKgIMKgIMKgIMKgIMKgIMKgcmV0dXJuIEZsYXRMYXlvdXQoKTxicj4NCivCoCDCoCDCoCDC oCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoGVsaWYgdmFsWzBdID09ICYjMzk7 ZmlsZW5hbWUtaGFzaCYjMzk7Ojxicj4NCivCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDC oCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoHJldHVybiBGaWxlbmFtZUhhc2hMYXlvdXQodmFs WzFdLCB2YWxbMl0pPGJyPg0KK8KgIMKgIMKgIMKgIMKgIMKgIMKgIMKgZWxzZTo8YnI+DQorwqAg wqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAjIGZhbGxiYWNrPGJyPg0KK8KgIMKgIMKg IMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgcmV0dXJuIEZsYXRMYXlvdXQoKTxicj4NCis8YnI+ DQorPGJyPg0KK2RlZiBnZXRfbWlycm9yX3VybChtaXJyb3JfdXJsLCBmaWxlbmFtZSwgZXJvb3Qp Ojxicj4NCivCoCDCoCDCoCDCoCZxdW90OyZxdW90OyZxdW90Ozxicj4NCivCoCDCoCDCoCDCoEdl dCBjb3JyZWN0IGZldGNoIFVSTCBmb3IgYSBnaXZlbiBmaWxlLCBhY2NvdW50aW5nIGZvciBtaXJy b3I8YnI+DQorwqAgwqAgwqAgwqBsYXlvdXQgY29uZmlndXJhdGlvbi48YnI+DQorPGJyPg0KK8Kg IMKgIMKgIMKgQHBhcmFtIG1pcnJvcl91cmw6IEJhc2UgVVJMIHRvIHRoZSBtaXJyb3IgKHdpdGhv dXQgJiMzOTsvZGlzdGZpbGVzJiMzOTspPGJyPg0KK8KgIMKgIMKgIMKgQHBhcmFtIGZpbGVuYW1l OiBGaWxlbmFtZSB0byBmZXRjaDxicj4NCivCoCDCoCDCoCDCoEBwYXJhbSBlcm9vdDogRVJPT1Qg dG8gdXNlIGZvciB0aGUgY2FjaGUgZmlsZTxicj4NCivCoCDCoCDCoCDCoEByZXR1cm46IEZ1bGwg VVJMIHRvIGZldGNoPGJyPg0KK8KgIMKgIMKgIMKgJnF1b3Q7JnF1b3Q7JnF1b3Q7PGJyPg0KKzxi cj4NCivCoCDCoCDCoCDCoG1pcnJvcl9jb25mID0gTWlycm9yTGF5b3V0Q29uZmlnKCk8YnI+DQor PGJyPg0KK8KgIMKgIMKgIMKgY2FjaGVfZmlsZSA9IG9zLnBhdGguam9pbihlcm9vdCwgQ0FDSEVf UEFUSCwgJiMzOTttaXJyb3ItbWV0YWRhdGEuanNvbiYjMzk7KTxicj4NCivCoCDCoCDCoCDCoHRy eTo8YnI+DQorwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqB3aXRoIG9wZW4oY2FjaGVfZmlsZSwgJiMz OTtyJiMzOTspIGFzIGY6PGJyPg0KK8KgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKg Y2FjaGUgPSBqc29uLmxvYWQoZik8YnI+DQorwqAgwqAgwqAgwqBleGNlcHQgKElPRXJyb3IsIFZh bHVlRXJyb3IpOjxicj4NCivCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoGNhY2hlID0ge308YnI+DQor PGJyPg0KK8KgIMKgIMKgIMKgdHMsIGRhdGEgPSBjYWNoZS5nZXQobWlycm9yX3VybCwgKDAsIE5v bmUpKTxicj4NCivCoCDCoCDCoCDCoCMgcmVmcmVzaCBhdCBsZWFzdCBkYWlseTxicj4NCivCoCDC oCDCoCDCoGlmIHRzICZndDs9IHRpbWUudGltZSgpIC0gODY0MDA6PGJyPg0KK8KgIMKgIMKgIMKg IMKgIMKgIMKgIMKgbWlycm9yX2NvbmYuZGVzZXJpYWxpemUoZGF0YSk8YnI+DQorwqAgwqAgwqAg wqBlbHNlOjxicj4NCivCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoHRyeTo8YnI+DQorwqAgwqAgwqAg wqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqBmID0gdXJsb3BlbihtaXJyb3JfdXJsICsgJiMzOTsv ZGlzdGZpbGVzL2xheW91dC5jb25mJiMzOTspPGJyPg0KK8KgIMKgIMKgIMKgIMKgIMKgIMKgIMKg IMKgIMKgIMKgIMKgdHJ5Ojxicj4NCivCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDC oCDCoCDCoCDCoCDCoGRhdGEgPSBpby5TdHJpbmdJTyhmLnJlYWQoKS5kZWNvZGUoJiMzOTt1dGY4 JiMzOTspKTxicj4NCivCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoGZpbmFsbHk6 PGJyPg0KK8KgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgZi5j bG9zZSgpPGJyPg0KKzxicj4NCivCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoG1p cnJvcl9jb25mLnJlYWRfZnJvbV9maWxlKGRhdGEpPGJyPg0KK8KgIMKgIMKgIMKgIMKgIMKgIMKg IMKgZXhjZXB0IElPRXJyb3I6PGJyPg0KK8KgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKg IMKgcGFzczxicj4NCis8YnI+DQorwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqBjYWNoZVttaXJyb3Jf dXJsXSA9ICh0aW1lLnRpbWUoKSwgbWlycm9yX2NvbmYuc2VyaWFsaXplKCkpPGJyPg0KK8KgIMKg IMKgIMKgIMKgIMKgIMKgIMKgd2l0aCBvcGVuKGNhY2hlX2ZpbGUsICYjMzk7dyYjMzk7KSBhcyBm Ojxicj4NCivCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoGpzb24uZHVtcChjYWNo ZSwgZik8YnI+DQorPGJyPg0KK8KgIMKgIMKgIMKgcmV0dXJuIChtaXJyb3JfdXJsICsgJnF1b3Q7 L2Rpc3RmaWxlcy8mcXVvdDsgKzxicj4NCivCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDC oCDCoG1pcnJvcl9jb25mLmdldF9iZXN0X3N1cHBvcnRlZF9sYXlvdXQoKS5nZXRfcGF0aChmaWxl bmFtZSkpPGJyPg0KKzxicj4NCis8YnI+DQrCoGRlZiBmZXRjaChteXVyaXMsIG15c2V0dGluZ3Ms IGxpc3Rvbmx5PTAsIGZldGNob25seT0wLDxicj4NCsKgIMKgIMKgIMKgIGxvY2tzX2luX3N1YmRp cj0mcXVvdDsubG9ja3MmcXVvdDssIHVzZV9sb2Nrcz0xLCB0cnlfbWlycm9ycz0xLCBkaWdlc3Rz PU5vbmUsPGJyPg0KwqAgwqAgwqAgwqAgYWxsb3dfbWlzc2luZ19kaWdlc3RzPVRydWUpOjxicj4N CkBAIC00MzQsOCArNTY0LDkgQEAgZGVmIGZldGNoKG15dXJpcywgbXlzZXR0aW5ncywgbGlzdG9u bHk9MCwgZmV0Y2hvbmx5PTAsPGJyPg0KwqAgwqAgwqAgwqAgZm9yIG15ZmlsZSwgbXl1cmkgaW4g ZmlsZV91cmlfdHVwbGVzOjxicj4NCsKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIGlmIG15ZmlsZSBu b3QgaW4gZmlsZWRpY3Q6PGJyPg0KwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAg ZmlsZWRpY3RbbXlmaWxlXT1bXTxicj4NCi3CoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDC oCDCoGZvciB5IGluIHJhbmdlKDAsbGVuKGxvY2F0aW9ucykpOjxicj4NCi3CoCDCoCDCoCDCoCDC oCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoGZpbGVkaWN0W215ZmlsZV0uYXBwZW5k KGxvY2F0aW9uc1t5XSsmcXVvdDsvZGlzdGZpbGVzLyZxdW90OytteWZpbGUpPGJyPg0KK8KgIMKg IMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgZm9yIGwgaW4gbG9jYXRpb25zOjxicj4NCivC oCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoGZpbGVkaWN0W215 ZmlsZV0uYXBwZW5kKGdldF9taXJyb3JfdXJsKGwsIG15ZmlsZSw8YnI+DQorwqAgwqAgwqAgwqAg wqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAg wqBteXNldHRpbmdzWyZxdW90O0VST09UJnF1b3Q7XSkpPGJyPg0KwqAgwqAgwqAgwqAgwqAgwqAg wqAgwqAgaWYgbXl1cmkgaXMgTm9uZTo8YnI+DQrCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDC oCDCoCDCoCBjb250aW51ZTxicj4NCsKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIGlmIG15dXJpWzo5 XT09JnF1b3Q7bWlycm9yOi8vJnF1b3Q7Ojxicj4NCi0tIDxicj4NCjIuMjMuMDxicj4NCjxicj4N Cjxicj4NCjwvYmxvY2txdW90ZT48L2Rpdj48L2Rpdj4NCg== --00000000000011d97005940e995e--