Fix typo.
[portage.git] / pym / portage / dbapi / bintree.py
1 # Copyright 1998-2007 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
3 # $Id$
4
5 __all__ = ["bindbapi", "binarytree"]
6
7 from portage.cache.mappings import slot_dict_class
8 from portage.dep import isvalidatom, isjustname, dep_getkey, match_from_list
9 from portage.dbapi.virtual import fakedbapi
10 from portage.exception import InvalidPackageName, \
11         PermissionDenied, PortageException
12 from portage.output import green, EOutput
13 from portage.util import ensure_dirs, normalize_path, writemsg, writemsg_stdout
14 from portage.versions import best, catpkgsplit, catsplit
15 from portage.update import update_dbentries
16
17 from portage import dep_expand, listdir, _check_distfile, _movefile
18
19 import portage.xpak, portage.getbinpkg
20
21 import os, errno, stat
22 import re
23 from itertools import chain, izip
24
25 class bindbapi(fakedbapi):
26         _known_keys = frozenset(list(fakedbapi._known_keys) + \
27                 ["CHOST", "repository", "USE"])
28         def __init__(self, mybintree=None, **kwargs):
29                 fakedbapi.__init__(self, **kwargs)
30                 self.bintree = mybintree
31                 self.move_ent = mybintree.move_ent
32                 self.cpvdict={}
33                 self.cpdict={}
34                 # Selectively cache metadata in order to optimize dep matching.
35                 self._aux_cache_keys = set(
36                         ["CHOST", "DEPEND", "EAPI", "IUSE", "KEYWORDS",
37                         "LICENSE", "PDEPEND", "PROPERTIES", "PROVIDE",
38                         "RDEPEND", "repository", "RESTRICT", "SLOT", "USE"])
39                 self._aux_cache_slot_dict = slot_dict_class(self._aux_cache_keys)
40                 self._aux_cache = {}
41
42         def match(self, *pargs, **kwargs):
43                 if self.bintree and not self.bintree.populated:
44                         self.bintree.populate()
45                 return fakedbapi.match(self, *pargs, **kwargs)
46
47         def aux_get(self, mycpv, wants):
48                 if self.bintree and not self.bintree.populated:
49                         self.bintree.populate()
50                 cache_me = False
51                 if not self._known_keys.intersection(
52                         wants).difference(self._aux_cache_keys):
53                         aux_cache = self._aux_cache.get(mycpv)
54                         if aux_cache is not None:
55                                 return [aux_cache.get(x, "") for x in wants]
56                         cache_me = True
57                 mysplit = mycpv.split("/")
58                 mylist = []
59                 tbz2name = mysplit[1]+".tbz2"
60                 if not self.bintree._remotepkgs or \
61                         not self.bintree.isremote(mycpv):
62                         tbz2_path = self.bintree.getname(mycpv)
63                         if not os.path.exists(tbz2_path):
64                                 raise KeyError(mycpv)
65                         getitem = portage.xpak.tbz2(tbz2_path).getfile
66                 else:
67                         getitem = self.bintree._remotepkgs[mycpv].get
68                 mydata = {}
69                 mykeys = wants
70                 if cache_me:
71                         mykeys = self._aux_cache_keys.union(wants)
72                 for x in mykeys:
73                         myval = getitem(x)
74                         # myval is None if the key doesn't exist
75                         # or the tbz2 is corrupt.
76                         if myval:
77                                 mydata[x] = " ".join(myval.split())
78                 if "EAPI" in mykeys:
79                         if not mydata.setdefault("EAPI", "0"):
80                                 mydata["EAPI"] = "0"
81                 if cache_me:
82                         aux_cache = self._aux_cache_slot_dict()
83                         for x in self._aux_cache_keys:
84                                 aux_cache[x] = mydata.get(x, "")
85                         self._aux_cache[mycpv] = aux_cache
86                 return [mydata.get(x, "") for x in wants]
87
88         def aux_update(self, cpv, values):
89                 if not self.bintree.populated:
90                         self.bintree.populate()
91                 tbz2path = self.bintree.getname(cpv)
92                 if not os.path.exists(tbz2path):
93                         raise KeyError(cpv)
94                 mytbz2 = portage.xpak.tbz2(tbz2path)
95                 mydata = mytbz2.get_data()
96                 mydata.update(values)
97                 for k, v in mydata.items():
98                         if not v:
99                                 del mydata[k]
100                 mytbz2.recompose_mem(portage.xpak.xpak_mem(mydata))
101                 self.bintree.inject(cpv)
102
103         def cp_list(self, *pargs, **kwargs):
104                 if not self.bintree.populated:
105                         self.bintree.populate()
106                 return fakedbapi.cp_list(self, *pargs, **kwargs)
107
108         def cp_all(self):
109                 if not self.bintree.populated:
110                         self.bintree.populate()
111                 return fakedbapi.cp_all(self)
112
113         def cpv_all(self):
114                 if not self.bintree.populated:
115                         self.bintree.populate()
116                 return fakedbapi.cpv_all(self)
117
118 class binarytree(object):
119         "this tree scans for a list of all packages available in PKGDIR"
120         def __init__(self, root, pkgdir, virtual=None, settings=None, clone=None):
121                 if clone:
122                         writemsg("binartree.__init__(): deprecated " + \
123                                 "use of clone parameter\n", noiselevel=-1)
124                         # XXX This isn't cloning. It's an instance of the same thing.
125                         self.root = clone.root
126                         self.pkgdir = clone.pkgdir
127                         self.dbapi = clone.dbapi
128                         self.populated = clone.populated
129                         self.tree = clone.tree
130                         self.remotepkgs = clone.remotepkgs
131                         self.invalids = clone.invalids
132                         self.settings = clone.settings
133                 else:
134                         self.root = root
135                         #self.pkgdir=settings["PKGDIR"]
136                         self.pkgdir = normalize_path(pkgdir)
137                         self.dbapi = bindbapi(self, settings=settings)
138                         self.update_ents = self.dbapi.update_ents
139                         self.move_slot_ent = self.dbapi.move_slot_ent
140                         self.populated = 0
141                         self.tree = {}
142                         self._remote_has_index = False
143                         self._remote_base_uri = None
144                         self._remotepkgs = None # remote metadata indexed by cpv
145                         self.remotepkgs = {}  # indexed by tbz2 name (deprecated)
146                         self.invalids = []
147                         self.settings = settings
148                         self._pkg_paths = {}
149                         self._populating = False
150                         self._all_directory = os.path.isdir(
151                                 os.path.join(self.pkgdir, "All"))
152                         self._pkgindex_version = 0
153                         self._pkgindex_hashes = ["MD5","SHA1"]
154                         self._pkgindex_file = os.path.join(self.pkgdir, "Packages")
155                         self._pkgindex_keys = self.dbapi._aux_cache_keys.copy()
156                         self._pkgindex_keys.update(["CPV", "MTIME", "SIZE"])
157                         self._pkgindex_aux_keys = \
158                                 ["CHOST", "DEPEND", "DESCRIPTION", "EAPI",
159                                 "IUSE", "KEYWORDS", "LICENSE", "PDEPEND", "PROPERTIES",
160                                 "PROVIDE", "RDEPEND", "repository", "SLOT", "USE"]
161                         self._pkgindex_aux_keys = list(self._pkgindex_aux_keys)
162                         self._pkgindex_use_evaluated_keys = \
163                                 ("LICENSE", "RDEPEND", "DEPEND",
164                                 "PDEPEND", "PROPERTIES", "PROVIDE")
165                         self._pkgindex_header_keys = set(["ACCEPT_KEYWORDS", "CBUILD",
166                                 "CHOST", "CONFIG_PROTECT", "CONFIG_PROTECT_MASK", "FEATURES",
167                                 "GENTOO_MIRRORS", "INSTALL_MASK", "SYNC", "USE"])
168                         self._pkgindex_default_pkg_data = {
169                                 "DEPEND"  : "",
170                                 "EAPI"    : "0",
171                                 "IUSE"    : "",
172                                 "KEYWORDS": "",
173                                 "LICENSE" : "",
174                                 "PATH"    : "",
175                                 "PDEPEND" : "",
176                                 "PROPERTIES" : "",
177                                 "PROVIDE" : "",
178                                 "RDEPEND" : "",
179                                 "RESTRICT": "",
180                                 "SLOT"    : "0",
181                                 "USE"     : ""
182                         }
183                         self._pkgindex_inherited_keys = ["CHOST", "repository"]
184                         self._pkgindex_default_header_data = {
185                                 "repository":""
186                         }
187                         self._pkgindex_translated_keys = (
188                                 ("DESCRIPTION"   ,   "DESC"),
189                                 ("repository"    ,   "REPO"),
190                         )
191
192                         self._pkgindex_allowed_pkg_keys = set(chain(
193                                 self._pkgindex_keys,
194                                 self._pkgindex_aux_keys,
195                                 self._pkgindex_hashes,
196                                 self._pkgindex_default_pkg_data,
197                                 self._pkgindex_inherited_keys,
198                                 self._pkgindex_default_header_data,
199                                 chain(*self._pkgindex_translated_keys)
200                         ))
201
202         def move_ent(self, mylist):
203                 if not self.populated:
204                         self.populate()
205                 origcp = mylist[1]
206                 newcp = mylist[2]
207                 # sanity check
208                 for cp in [origcp, newcp]:
209                         if not (isvalidatom(cp) and isjustname(cp)):
210                                 raise InvalidPackageName(cp)
211                 origcat = origcp.split("/")[0]
212                 mynewcat = newcp.split("/")[0]
213                 origmatches=self.dbapi.cp_list(origcp)
214                 moves = 0
215                 if not origmatches:
216                         return moves
217                 for mycpv in origmatches:
218
219                         mycpsplit = catpkgsplit(mycpv)
220                         mynewcpv = newcp + "-" + mycpsplit[2]
221                         if mycpsplit[3] != "r0":
222                                 mynewcpv += "-" + mycpsplit[3]
223                         myoldpkg = mycpv.split("/")[1]
224                         mynewpkg = mynewcpv.split("/")[1]
225
226                         if (mynewpkg != myoldpkg) and os.path.exists(self.getname(mynewcpv)):
227                                 writemsg("!!! Cannot update binary: Destination exists.\n",
228                                         noiselevel=-1)
229                                 writemsg("!!! "+mycpv+" -> "+mynewcpv+"\n", noiselevel=-1)
230                                 continue
231
232                         tbz2path = self.getname(mycpv)
233                         if os.path.exists(tbz2path) and not os.access(tbz2path,os.W_OK):
234                                 writemsg("!!! Cannot update readonly binary: "+mycpv+"\n",
235                                         noiselevel=-1)
236                                 continue
237
238                         moves += 1
239                         mytbz2 = portage.xpak.tbz2(tbz2path)
240                         mydata = mytbz2.get_data()
241                         updated_items = update_dbentries([mylist], mydata)
242                         mydata.update(updated_items)
243                         mydata["PF"] = mynewpkg + "\n"
244                         mydata["CATEGORY"] = mynewcat+"\n"
245                         if mynewpkg != myoldpkg:
246                                 ebuild_data = mydata.get(myoldpkg+".ebuild")
247                                 if ebuild_data is not None:
248                                         mydata[mynewpkg+".ebuild"] = ebuild_data
249                                         del mydata[myoldpkg+".ebuild"]
250                         mytbz2.recompose_mem(portage.xpak.xpak_mem(mydata))
251
252                         self.dbapi.cpv_remove(mycpv)
253                         del self._pkg_paths[mycpv]
254                         new_path = self.getname(mynewcpv)
255                         self._pkg_paths[mynewcpv] = os.path.join(
256                                 *new_path.split(os.path.sep)[-2:])
257                         if new_path != mytbz2:
258                                 self._ensure_dir(os.path.dirname(new_path))
259                                 _movefile(tbz2path, new_path, mysettings=self.settings)
260                                 self._remove_symlink(mycpv)
261                                 if new_path.split(os.path.sep)[-2] == "All":
262                                         self._create_symlink(mynewcpv)
263                         self.inject(mynewcpv)
264
265                 return moves
266
267         def _remove_symlink(self, cpv):
268                 """Remove a ${PKGDIR}/${CATEGORY}/${PF}.tbz2 symlink and also remove
269                 the ${PKGDIR}/${CATEGORY} directory if empty.  The file will not be
270                 removed if os.path.islink() returns False."""
271                 mycat, mypkg = catsplit(cpv)
272                 mylink = os.path.join(self.pkgdir, mycat, mypkg + ".tbz2")
273                 if os.path.islink(mylink):
274                         """Only remove it if it's really a link so that this method never
275                         removes a real package that was placed here to avoid a collision."""
276                         os.unlink(mylink)
277                 try:
278                         os.rmdir(os.path.join(self.pkgdir, mycat))
279                 except OSError, e:
280                         if e.errno not in (errno.ENOENT,
281                                 errno.ENOTEMPTY, errno.EEXIST):
282                                 raise
283                         del e
284
285         def _create_symlink(self, cpv):
286                 """Create a ${PKGDIR}/${CATEGORY}/${PF}.tbz2 symlink (and
287                 ${PKGDIR}/${CATEGORY} directory, if necessary).  Any file that may
288                 exist in the location of the symlink will first be removed."""
289                 mycat, mypkg = catsplit(cpv)
290                 full_path = os.path.join(self.pkgdir, mycat, mypkg + ".tbz2")
291                 self._ensure_dir(os.path.dirname(full_path))
292                 try:
293                         os.unlink(full_path)
294                 except OSError, e:
295                         if e.errno != errno.ENOENT:
296                                 raise
297                         del e
298                 os.symlink(os.path.join("..", "All", mypkg + ".tbz2"), full_path)
299
300         def prevent_collision(self, cpv):
301                 """Make sure that the file location ${PKGDIR}/All/${PF}.tbz2 is safe to
302                 use for a given cpv.  If a collision will occur with an existing
303                 package from another category, the existing package will be bumped to
304                 ${PKGDIR}/${CATEGORY}/${PF}.tbz2 so that both can coexist."""
305                 if not self._all_directory:
306                         return
307
308                 # Copy group permissions for new directories that
309                 # may have been created.
310                 for path in ("All", catsplit(cpv)[0]):
311                         path = os.path.join(self.pkgdir, path)
312                         self._ensure_dir(path)
313                         if not os.access(path, os.W_OK):
314                                 raise PermissionDenied("access('%s', W_OK)" % path)
315
316                 full_path = self.getname(cpv)
317                 if "All" == full_path.split(os.path.sep)[-2]:
318                         return
319                 """Move a colliding package if it exists.  Code below this point only
320                 executes in rare cases."""
321                 mycat, mypkg = catsplit(cpv)
322                 myfile = mypkg + ".tbz2"
323                 mypath = os.path.join("All", myfile)
324                 dest_path = os.path.join(self.pkgdir, mypath)
325
326                 try:
327                         st = os.lstat(dest_path)
328                 except OSError:
329                         st = None
330                 else:
331                         if stat.S_ISLNK(st.st_mode):
332                                 st = None
333                                 try:
334                                         os.unlink(dest_path)
335                                 except OSError:
336                                         if os.path.exists(dest_path):
337                                                 raise
338
339                 if st is not None:
340                         # For invalid packages, other_cat could be None.
341                         other_cat = portage.xpak.tbz2(dest_path).getfile("CATEGORY")
342                         if other_cat:
343                                 other_cat = other_cat.strip()
344                                 other_cpv = other_cat + "/" + mypkg
345                                 self._move_from_all(other_cpv)
346                                 self.inject(other_cpv)
347                 self._move_to_all(cpv)
348
349         def _ensure_dir(self, path):
350                 """
351                 Create the specified directory. Also, copy gid and group mode
352                 bits from self.pkgdir if possible.
353                 @param cat_dir: Absolute path of the directory to be created.
354                 @type cat_dir: String
355                 """
356                 try:
357                         pkgdir_st = os.stat(self.pkgdir)
358                 except OSError:
359                         ensure_dirs(path)
360                         return
361                 pkgdir_gid = pkgdir_st.st_gid
362                 pkgdir_grp_mode = 02070 & pkgdir_st.st_mode
363                 try:
364                         ensure_dirs(path, gid=pkgdir_gid, mode=pkgdir_grp_mode, mask=0)
365                 except PortageException:
366                         if not os.path.isdir(path):
367                                 raise
368
369         def _move_to_all(self, cpv):
370                 """If the file exists, move it.  Whether or not it exists, update state
371                 for future getname() calls."""
372                 mycat, mypkg = catsplit(cpv)
373                 myfile = mypkg + ".tbz2"
374                 self._pkg_paths[cpv] = os.path.join("All", myfile)
375                 src_path = os.path.join(self.pkgdir, mycat, myfile)
376                 try:
377                         mystat = os.lstat(src_path)
378                 except OSError, e:
379                         mystat = None
380                 if mystat and stat.S_ISREG(mystat.st_mode):
381                         self._ensure_dir(os.path.join(self.pkgdir, "All"))
382                         dest_path = os.path.join(self.pkgdir, "All", myfile)
383                         _movefile(src_path, dest_path, mysettings=self.settings)
384                         self._create_symlink(cpv)
385                         self.inject(cpv)
386
387         def _move_from_all(self, cpv):
388                 """Move a package from ${PKGDIR}/All/${PF}.tbz2 to
389                 ${PKGDIR}/${CATEGORY}/${PF}.tbz2 and update state from getname calls."""
390                 self._remove_symlink(cpv)
391                 mycat, mypkg = catsplit(cpv)
392                 myfile = mypkg + ".tbz2"
393                 mypath = os.path.join(mycat, myfile)
394                 dest_path = os.path.join(self.pkgdir, mypath)
395                 self._ensure_dir(os.path.dirname(dest_path))
396                 src_path = os.path.join(self.pkgdir, "All", myfile)
397                 _movefile(src_path, dest_path, mysettings=self.settings)
398                 self._pkg_paths[cpv] = mypath
399
400         def populate(self, getbinpkgs=0, getbinpkgsonly=0):
401                 "populates the binarytree"
402                 if self._populating:
403                         return
404                 from portage.locks import lockfile, unlockfile
405                 pkgindex_lock = None
406                 try:
407                         if os.access(self.pkgdir, os.W_OK):
408                                 pkgindex_lock = lockfile(self._pkgindex_file,
409                                         wantnewlockfile=1)
410                         self._populating = True
411                         self._populate(getbinpkgs, getbinpkgsonly)
412                 finally:
413                         if pkgindex_lock:
414                                 unlockfile(pkgindex_lock)
415                         self._populating = False
416
417         def _populate(self, getbinpkgs=0, getbinpkgsonly=0):
418                 if (not os.path.isdir(self.pkgdir) and not getbinpkgs):
419                         return 0
420
421                 if True:
422                         pkg_paths = {}
423                         self._pkg_paths = pkg_paths
424                         dirs = listdir(self.pkgdir, dirsonly=True, EmptyOnError=True)
425                         if "All" in dirs:
426                                 dirs.remove("All")
427                         dirs.sort()
428                         dirs.insert(0, "All")
429                         pkgindex = self._new_pkgindex()
430                         pf_index = None
431                         try:
432                                 f = open(self._pkgindex_file)
433                         except EnvironmentError:
434                                 pass
435                         else:
436                                 try:
437                                         pkgindex.read(f)
438                                 finally:
439                                         f.close()
440                                         del f
441                         if not self._pkgindex_version_supported(pkgindex):
442                                 pkgindex = self._new_pkgindex()
443                         header = pkgindex.header
444                         metadata = {}
445                         for d in pkgindex.packages:
446                                 metadata[d["CPV"]] = d
447                         update_pkgindex = False
448                         for mydir in dirs:
449                                 for myfile in listdir(os.path.join(self.pkgdir, mydir)):
450                                         if not myfile.endswith(".tbz2"):
451                                                 continue
452                                         mypath = os.path.join(mydir, myfile)
453                                         full_path = os.path.join(self.pkgdir, mypath)
454                                         s = os.lstat(full_path)
455                                         if stat.S_ISLNK(s.st_mode):
456                                                 continue
457
458                                         # Validate data from the package index and try to avoid
459                                         # reading the xpak if possible.
460                                         if mydir != "All":
461                                                 possibilities = None
462                                                 d = metadata.get(mydir+"/"+myfile[:-5])
463                                                 if d:
464                                                         possibilities = [d]
465                                         else:
466                                                 if pf_index is None:
467                                                         pf_index = {}
468                                                         for mycpv in metadata:
469                                                                 mycat, mypf = catsplit(mycpv)
470                                                                 pf_index.setdefault(
471                                                                         mypf, []).append(metadata[mycpv])
472                                                 possibilities = pf_index.get(myfile[:-5])
473                                         if possibilities:
474                                                 match = None
475                                                 for d in possibilities:
476                                                         try:
477                                                                 if long(d["MTIME"]) != long(s.st_mtime):
478                                                                         continue
479                                                         except (KeyError, ValueError):
480                                                                 continue
481                                                         try:
482                                                                 if long(d["SIZE"]) != long(s.st_size):
483                                                                         continue
484                                                         except (KeyError, ValueError):
485                                                                 continue
486                                                         if not self._pkgindex_keys.difference(d):
487                                                                 match = d
488                                                                 break
489                                                 if match:
490                                                         mycpv = match["CPV"]
491                                                         if mycpv in pkg_paths:
492                                                                 # discard duplicates (All/ is preferred)
493                                                                 continue
494                                                         pkg_paths[mycpv] = mypath
495                                                         # update the path if the package has been moved
496                                                         oldpath = d.get("PATH")
497                                                         if oldpath and oldpath != mypath:
498                                                                 update_pkgindex = True
499                                                         if mypath != mycpv + ".tbz2":
500                                                                 d["PATH"] = mypath
501                                                                 if not oldpath:
502                                                                         update_pkgindex = True
503                                                         else:
504                                                                 d.pop("PATH", None)
505                                                                 if oldpath:
506                                                                         update_pkgindex = True
507                                                         self.dbapi.cpv_inject(mycpv)
508                                                         if not self.dbapi._aux_cache_keys.difference(d):
509                                                                 aux_cache = self.dbapi._aux_cache_slot_dict()
510                                                                 for k in self.dbapi._aux_cache_keys:
511                                                                         aux_cache[k] = d[k]
512                                                                 self.dbapi._aux_cache[mycpv] = aux_cache
513                                                         continue
514                                         if not os.access(full_path, os.R_OK):
515                                                 writemsg("!!! Permission denied to read " + \
516                                                         "binary package: '%s'\n" % full_path,
517                                                         noiselevel=-1)
518                                                 self.invalids.append(myfile[:-5])
519                                                 continue
520                                         mytbz2 = portage.xpak.tbz2(full_path)
521                                         # For invalid packages, mycat could be None.
522                                         mycat = mytbz2.getfile("CATEGORY")
523                                         mypf = mytbz2.getfile("PF")
524                                         slot = mytbz2.getfile("SLOT")
525                                         mypkg = myfile[:-5]
526                                         if not mycat or not mypf or not slot:
527                                                 #old-style or corrupt package
528                                                 writemsg("\n!!! Invalid binary package: '%s'\n" % full_path,
529                                                         noiselevel=-1)
530                                                 missing_keys = []
531                                                 if not mycat:
532                                                         missing_keys.append("CATEGORY")
533                                                 if not mypf:
534                                                         missing_keys.append("PF")
535                                                 if not slot:
536                                                         missing_keys.append("SLOT")
537                                                 msg = []
538                                                 if missing_keys:
539                                                         missing_keys.sort()
540                                                         msg.append("Missing metadata key(s): %s." % \
541                                                                 ", ".join(missing_keys))
542                                                 msg.append(" This binary package is not " + \
543                                                         "recoverable and should be deleted.")
544                                                 from textwrap import wrap
545                                                 for line in wrap("".join(msg), 72):
546                                                         writemsg("!!! %s\n" % line, noiselevel=-1)
547                                                 self.invalids.append(mypkg)
548                                                 continue
549                                         mycat = mycat.strip()
550                                         slot = slot.strip()
551                                         if mycat != mydir and mydir != "All":
552                                                 continue
553                                         if mypkg != mypf.strip():
554                                                 continue
555                                         mycpv = mycat + "/" + mypkg
556                                         if mycpv in pkg_paths:
557                                                 # All is first, so it's preferred.
558                                                 continue
559                                         if not self.dbapi._category_re.match(mycat):
560                                                 writemsg(("!!! Binary package has an " + \
561                                                         "unrecognized category: '%s'\n") % full_path,
562                                                         noiselevel=-1)
563                                                 writemsg(("!!! '%s' has a category that is not" + \
564                                                         " listed in %setc/portage/categories\n") % \
565                                                         (mycpv, self.settings["PORTAGE_CONFIGROOT"]),
566                                                         noiselevel=-1)
567                                                 continue
568                                         pkg_paths[mycpv] = mypath
569                                         self.dbapi.cpv_inject(mycpv)
570                                         update_pkgindex = True
571                                         d = metadata.get(mycpv, {})
572                                         if d:
573                                                 try:
574                                                         if long(d["MTIME"]) != long(s.st_mtime):
575                                                                 d.clear()
576                                                 except (KeyError, ValueError):
577                                                         d.clear()
578                                         if d:
579                                                 try:
580                                                         if long(d["SIZE"]) != long(s.st_size):
581                                                                 d.clear()
582                                                 except (KeyError, ValueError):
583                                                         d.clear()
584
585                                         d["CPV"] = mycpv
586                                         d["SLOT"] = slot
587                                         d["MTIME"] = str(long(s.st_mtime))
588                                         d["SIZE"] = str(s.st_size)
589
590                                         d.update(izip(self._pkgindex_aux_keys,
591                                                 self.dbapi.aux_get(mycpv, self._pkgindex_aux_keys)))
592                                         try:
593                                                 self._eval_use_flags(mycpv, d)
594                                         except portage.exception.InvalidDependString:
595                                                 writemsg("!!! Invalid binary package: '%s'\n" % \
596                                                         self.getname(mycpv), noiselevel=-1)
597                                                 self.dbapi.cpv_remove(mycpv)
598                                                 del pkg_paths[mycpv]
599
600                                         # record location if it's non-default
601                                         if mypath != mycpv + ".tbz2":
602                                                 d["PATH"] = mypath
603                                         else:
604                                                 d.pop("PATH", None)
605                                         metadata[mycpv] = d
606                                         if not self.dbapi._aux_cache_keys.difference(d):
607                                                 aux_cache = self.dbapi._aux_cache_slot_dict()
608                                                 for k in self.dbapi._aux_cache_keys:
609                                                         aux_cache[k] = d[k]
610                                                 self.dbapi._aux_cache[mycpv] = aux_cache
611
612                         for cpv in list(metadata):
613                                 if cpv not in pkg_paths:
614                                         del metadata[cpv]
615
616                         # Do not bother to write the Packages index if $PKGDIR/All/ exists
617                         # since it will provide no benefit due to the need to read CATEGORY
618                         # from xpak.
619                         if update_pkgindex and os.access(self.pkgdir, os.W_OK):
620                                 del pkgindex.packages[:]
621                                 pkgindex.packages.extend(metadata.itervalues())
622                                 self._update_pkgindex_header(pkgindex.header)
623                                 from portage.util import atomic_ofstream
624                                 f = atomic_ofstream(self._pkgindex_file)
625                                 try:
626                                         pkgindex.write(f)
627                                 finally:
628                                         f.close()
629
630                 if getbinpkgs and not self.settings["PORTAGE_BINHOST"]:
631                         writemsg("!!! PORTAGE_BINHOST unset, but use is requested.\n",
632                                 noiselevel=-1)
633
634                 if getbinpkgs and \
635                         "PORTAGE_BINHOST" in self.settings and \
636                         not self._remotepkgs:
637
638                         base_url = self.settings["PORTAGE_BINHOST"]
639                         from portage.const import CACHE_PATH
640                         from urlparse import urlparse
641                         urldata = urlparse(base_url)
642                         pkgindex_file = os.path.join(CACHE_PATH, "binhost",
643                                 urldata[1] + urldata[2], "Packages")
644                         pkgindex = self._new_pkgindex()
645                         try:
646                                 f = open(pkgindex_file)
647                                 try:
648                                         pkgindex.read(f)
649                                 finally:
650                                         f.close()
651                         except EnvironmentError, e:
652                                 if e.errno != errno.ENOENT:
653                                         raise
654                         local_timestamp = pkgindex.header.get("TIMESTAMP", None)
655                         import urllib, urlparse
656                         rmt_idx = self._new_pkgindex()
657                         try:
658                                 # urlparse.urljoin() only works correctly with recognized
659                                 # protocols and requires the base url to have a trailing
660                                 # slash, so join manually...
661                                 f = urllib.urlopen(base_url.rstrip("/") + "/Packages")
662                                 try:
663                                         rmt_idx.readHeader(f)
664                                         remote_timestamp = rmt_idx.header.get("TIMESTAMP", None)
665                                         if not remote_timestamp:
666                                                 # no timestamp in the header, something's wrong
667                                                 pkgindex = None
668                                         else:
669                                                 if not self._pkgindex_version_supported(rmt_idx):
670                                                         writemsg("\n\n!!! Binhost package index version" + \
671                                                         " is not supported: '%s'\n" % \
672                                                         rmt_idx.header.get("VERSION"), noiselevel=-1)
673                                                         pkgindex = None
674                                                 elif local_timestamp != remote_timestamp:
675                                                         rmt_idx.readBody(f)
676                                                         pkgindex = rmt_idx
677                                 finally:
678                                         f.close()
679                         except EnvironmentError, e:
680                                 writemsg("\n\n!!! Error fetching binhost package" + \
681                                         " info from '%s'\n" % base_url)
682                                 writemsg("!!! %s\n\n" % str(e))
683                                 del e
684                                 pkgindex = None
685                         if pkgindex is rmt_idx:
686                                 pkgindex.modified = False # don't update the header
687                                 from portage.util import atomic_ofstream, ensure_dirs
688                                 ensure_dirs(os.path.dirname(pkgindex_file))
689                                 f = atomic_ofstream(pkgindex_file)
690                                 try:
691                                         pkgindex.write(f)
692                                 finally:
693                                         f.close()
694                         if pkgindex:
695                                 self._remotepkgs = {}
696                                 for d in pkgindex.packages:
697                                         self._remotepkgs[d["CPV"]] = d
698                                 self._remote_has_index = True
699                                 self._remote_base_uri = pkgindex.header.get("URI", base_url)
700                                 self.remotepkgs = {}
701                                 for cpv in self._remotepkgs:
702                                         self.dbapi.cpv_inject(cpv)
703                                 self.populated = 1
704                                 if getbinpkgsonly:
705                                         # Remote package instances override local package
706                                         # if they are not identical.
707                                         hash_names = ["SIZE"] + self._pkgindex_hashes
708                                         for cpv, local_metadata in metadata.iteritems():
709                                                 remote_metadata = self._remotepkgs.get(cpv)
710                                                 if remote_metadata is None:
711                                                         continue
712                                                 # Use digests to compare identity.
713                                                 identical = True
714                                                 for hash_name in hash_names:
715                                                         local_value = local_metadata.get(hash_name)
716                                                         if local_value is None:
717                                                                 continue
718                                                         remote_value = remote_metadata.get(hash_name)
719                                                         if remote_value is None:
720                                                                 continue
721                                                         if local_value != remote_value:
722                                                                 identical = False
723                                                                 break
724                                                 if identical:
725                                                         del self._remotepkgs[cpv]
726                                 else:
727                                         # Local package instances override remote instances.
728                                         for cpv in metadata:
729                                                 self._remotepkgs.pop(cpv, None)
730                                 return
731                         self._remotepkgs = {}
732                         try:
733                                 chunk_size = long(self.settings["PORTAGE_BINHOST_CHUNKSIZE"])
734                                 if chunk_size < 8:
735                                         chunk_size = 8
736                         except (ValueError, KeyError):
737                                 chunk_size = 3000
738                         writemsg_stdout("\n")
739                         writemsg_stdout(
740                                 green("Fetching bininfo from ") + \
741                                 re.sub(r'//(.+):.+@(.+)/', r'//\1:*password*@\2/', base_url) + "\n")
742                         self.remotepkgs = portage.getbinpkg.dir_get_metadata(
743                                 self.settings["PORTAGE_BINHOST"], chunk_size=chunk_size)
744                         #writemsg(green("  -- DONE!\n\n"))
745
746                         for mypkg in self.remotepkgs.keys():
747                                 if "CATEGORY" not in self.remotepkgs[mypkg]:
748                                         #old-style or corrupt package
749                                         writemsg("!!! Invalid remote binary package: "+mypkg+"\n",
750                                                 noiselevel=-1)
751                                         del self.remotepkgs[mypkg]
752                                         continue
753                                 mycat = self.remotepkgs[mypkg]["CATEGORY"].strip()
754                                 fullpkg = mycat+"/"+mypkg[:-5]
755
756                                 if fullpkg in metadata:
757                                         # When using this old protocol, comparison with the remote
758                                         # package isn't supported, so the local package is always
759                                         # preferred even if getbinpkgsonly is enabled.
760                                         continue
761
762                                 if not self.dbapi._category_re.match(mycat):
763                                         writemsg(("!!! Remote binary package has an " + \
764                                                 "unrecognized category: '%s'\n") % fullpkg,
765                                                 noiselevel=-1)
766                                         writemsg(("!!! '%s' has a category that is not" + \
767                                                 " listed in %setc/portage/categories\n") % \
768                                                 (fullpkg, self.settings["PORTAGE_CONFIGROOT"]),
769                                                 noiselevel=-1)
770                                         continue
771                                 mykey = dep_getkey(fullpkg)
772                                 try:
773                                         # invalid tbz2's can hurt things.
774                                         #print "cpv_inject("+str(fullpkg)+")"
775                                         self.dbapi.cpv_inject(fullpkg)
776                                         remote_metadata = self.remotepkgs[mypkg]
777                                         for k, v in remote_metadata.items():
778                                                 remote_metadata[k] = v.strip()
779                                         self._remotepkgs[fullpkg] = remote_metadata
780                                         #print "  -- Injected"
781                                 except SystemExit, e:
782                                         raise
783                                 except:
784                                         writemsg("!!! Failed to inject remote binary package:"+str(fullpkg)+"\n",
785                                                 noiselevel=-1)
786                                         del self.remotepkgs[mypkg]
787                                         continue
788                 self.populated=1
789
790         def inject(self, cpv, filename=None):
791                 """Add a freshly built package to the database.  This updates
792                 $PKGDIR/Packages with the new package metadata (including MD5).
793                 @param cpv: The cpv of the new package to inject
794                 @type cpv: string
795                 @param filename: File path of the package to inject, or None if it's
796                         already in the location returned by getname()
797                 @type filename: string
798                 @rtype: None
799                 """
800                 mycat, mypkg = catsplit(cpv)
801                 if not self.populated:
802                         self.populate()
803                 if filename is None:
804                         full_path = self.getname(cpv)
805                 else:
806                         full_path = filename
807                 try:
808                         s = os.stat(full_path)
809                 except OSError, e:
810                         if e.errno != errno.ENOENT:
811                                 raise
812                         del e
813                         writemsg("!!! Binary package does not exist: '%s'\n" % full_path,
814                                 noiselevel=-1)
815                         return
816                 mytbz2 = portage.xpak.tbz2(full_path)
817                 slot = mytbz2.getfile("SLOT")
818                 if slot is None:
819                         writemsg("!!! Invalid binary package: '%s'\n" % full_path,
820                                 noiselevel=-1)
821                         return
822                 slot = slot.strip()
823                 self.dbapi.cpv_inject(cpv)
824                 self.dbapi._aux_cache.pop(cpv, None)
825
826                 # Reread the Packages index (in case it's been changed by another
827                 # process) and then updated it, all while holding a lock.
828                 from portage.locks import lockfile, unlockfile
829                 pkgindex_lock = None
830                 try:
831                         pkgindex_lock = lockfile(self._pkgindex_file,
832                                 wantnewlockfile=1)
833                         if filename is not None:
834                                 new_filename = self.getname(cpv)
835                                 self._ensure_dir(os.path.dirname(new_filename))
836                                 _movefile(filename, new_filename, mysettings=self.settings)
837                         if self._all_directory and \
838                                 self.getname(cpv).split(os.path.sep)[-2] == "All":
839                                 self._create_symlink(cpv)
840                         pkgindex = self._new_pkgindex()
841                         try:
842                                 f = open(self._pkgindex_file)
843                         except EnvironmentError:
844                                 pass
845                         else:
846                                 try:
847                                         pkgindex.read(f)
848                                 finally:
849                                         f.close()
850                                         del f
851                         if not self._pkgindex_version_supported(pkgindex):
852                                 pkgindex = self._new_pkgindex()
853
854                         try:
855                                 d = self._pkgindex_entry(cpv)
856                         except portage.exception.InvalidDependString:
857                                 writemsg("!!! Invalid binary package: '%s'\n" % \
858                                         self.getname(cpv), noiselevel=-1)
859                                 self.dbapi.cpv_remove(cpv)
860                                 del self._pkg_paths[cpv]
861                                 return
862
863                         # If found, remove package(s) with duplicate path.
864                         for i in xrange(len(pkgindex.packages) - 1, -1, -1):
865                                 d2 = pkgindex.packages[i]
866                                 if d2["CPV"] != cpv:
867                                         continue
868                                 if d2.get("PATH", "") == d.get("PATH", ""):
869                                         del pkgindex.packages[i]
870                         pkgindex.packages.append(d)
871
872                         self._update_pkgindex_header(pkgindex.header)
873                         from portage.util import atomic_ofstream
874                         f = atomic_ofstream(os.path.join(self.pkgdir, "Packages"))
875                         try:
876                                 pkgindex.write(f)
877                         finally:
878                                 f.close()
879                 finally:
880                         if pkgindex_lock:
881                                 unlockfile(pkgindex_lock)
882
883                 if self._remotepkgs is not None:
884                         # When a remote package is downloaded and injected,
885                         # update state so self.isremote() returns False.
886                         self._remotepkgs.pop(cpv, None)
887
888         def _pkgindex_entry(self, cpv):
889                 """
890                 Performs checksums and evaluates USE flag conditionals.
891                 Raises InvalidDependString if necessary.
892                 @rtype: dict
893                 @returns: a dict containing entry for the give cpv.
894                 """
895
896                 pkg_path = self.getname(cpv)
897                 from portage.checksum import perform_multiple_checksums
898
899                 d = dict(izip(self._pkgindex_aux_keys,
900                         self.dbapi.aux_get(cpv, self._pkgindex_aux_keys)))
901
902                 d.update(perform_multiple_checksums(
903                         pkg_path, hashes=self._pkgindex_hashes))
904
905                 d["CPV"] = cpv
906                 st = os.stat(pkg_path)
907                 d["MTIME"] = str(long(st.st_mtime))
908                 d["SIZE"] = str(st.st_size)
909
910                 rel_path = self._pkg_paths[cpv]
911                 # record location if it's non-default
912                 if rel_path != cpv + ".tbz2":
913                         d["PATH"] = rel_path
914
915                 self._eval_use_flags(cpv, d)
916                 return d
917
918         def _new_pkgindex(self):
919                 return portage.getbinpkg.PackageIndex(
920                         allowed_pkg_keys=self._pkgindex_allowed_pkg_keys,
921                         default_header_data=self._pkgindex_default_header_data,
922                         default_pkg_data=self._pkgindex_default_pkg_data,
923                         inherited_keys=self._pkgindex_inherited_keys,
924                         translated_keys=self._pkgindex_translated_keys)
925
926         def _update_pkgindex_header(self, header):
927                 portdir = normalize_path(os.path.realpath(self.settings["PORTDIR"]))
928                 profiles_base = os.path.join(portdir, "profiles") + os.path.sep
929                 if self.settings.profile_path:
930                         profile_path = normalize_path(
931                                 os.path.realpath(self.settings.profile_path))
932                         if profile_path.startswith(profiles_base):
933                                 profile_path = profile_path[len(profiles_base):]
934                         header["PROFILE"] = profile_path
935                 header["VERSION"] = str(self._pkgindex_version)
936                 base_uri = self.settings.get("PORTAGE_BINHOST_HEADER_URI")
937                 if base_uri:
938                         header["URI"] = base_uri
939                 else:
940                         header.pop("URI", None)
941                 for k in self._pkgindex_header_keys:
942                         v = self.settings.get(k, None)
943                         if v:
944                                 header[k] = v
945                         else:
946                                 header.pop(k, None)
947
948         def _pkgindex_version_supported(self, pkgindex):
949                 version = pkgindex.header.get("VERSION")
950                 if version:
951                         try:
952                                 if int(version) <= self._pkgindex_version:
953                                         return True
954                         except ValueError:
955                                 pass
956                 return False
957
958         def _eval_use_flags(self, cpv, metadata):
959                 use = metadata["USE"].split()
960                 raw_use = use
961                 iuse = set(f.lstrip("-+") for f in metadata["IUSE"].split())
962                 use = [f for f in use if f in iuse]
963                 use.sort()
964                 metadata["USE"] = " ".join(use)
965                 from portage.dep import paren_reduce, use_reduce, \
966                         paren_normalize, paren_enclose
967                 for k in self._pkgindex_use_evaluated_keys:
968                         try:
969                                 deps = paren_reduce(metadata[k])
970                                 deps = use_reduce(deps, uselist=raw_use)
971                                 deps = paren_normalize(deps)
972                                 deps = paren_enclose(deps)
973                         except portage.exception.InvalidDependString, e:
974                                 writemsg("%s: %s\n" % (k, str(e)),
975                                         noiselevel=-1)
976                                 raise
977                         metadata[k] = deps
978
979         def exists_specific(self, cpv):
980                 if not self.populated:
981                         self.populate()
982                 return self.dbapi.match(
983                         dep_expand("="+cpv, mydb=self.dbapi, settings=self.settings))
984
985         def dep_bestmatch(self, mydep):
986                 "compatibility method -- all matches, not just visible ones"
987                 if not self.populated:
988                         self.populate()
989                 writemsg("\n\n", 1)
990                 writemsg("mydep: %s\n" % mydep, 1)
991                 mydep = dep_expand(mydep, mydb=self.dbapi, settings=self.settings)
992                 writemsg("mydep: %s\n" % mydep, 1)
993                 mykey = dep_getkey(mydep)
994                 writemsg("mykey: %s\n" % mykey, 1)
995                 mymatch = best(match_from_list(mydep,self.dbapi.cp_list(mykey)))
996                 writemsg("mymatch: %s\n" % mymatch, 1)
997                 if mymatch is None:
998                         return ""
999                 return mymatch
1000
1001         def getname(self, pkgname):
1002                 """Returns a file location for this package.  The default location is
1003                 ${PKGDIR}/All/${PF}.tbz2, but will be ${PKGDIR}/${CATEGORY}/${PF}.tbz2
1004                 in the rare event of a collision.  The prevent_collision() method can
1005                 be called to ensure that ${PKGDIR}/All/${PF}.tbz2 is available for a
1006                 specific cpv."""
1007                 if not self.populated:
1008                         self.populate()
1009                 mycpv = pkgname
1010                 mypath = self._pkg_paths.get(mycpv, None)
1011                 if mypath:
1012                         return os.path.join(self.pkgdir, mypath)
1013                 mycat, mypkg = catsplit(mycpv)
1014                 if self._all_directory:
1015                         mypath = os.path.join("All", mypkg + ".tbz2")
1016                         if mypath in self._pkg_paths.values():
1017                                 mypath = os.path.join(mycat, mypkg + ".tbz2")
1018                 else:
1019                         mypath = os.path.join(mycat, mypkg + ".tbz2")
1020                 self._pkg_paths[mycpv] = mypath # cache for future lookups
1021                 return os.path.join(self.pkgdir, mypath)
1022
1023         def isremote(self, pkgname):
1024                 """Returns true if the package is kept remotely and it has not been
1025                 downloaded (or it is only partially downloaded)."""
1026                 if self._remotepkgs is None or pkgname not in self._remotepkgs:
1027                         return False
1028                 # Presence in self._remotepkgs implies that it's remote. When a
1029                 # package is downloaded, state is updated by self.inject().
1030                 return True
1031
1032         def get_use(self, pkgname):
1033                 writemsg("deprecated use of binarytree.get_use()," + \
1034                         " use dbapi.aux_get() instead", noiselevel=-1)
1035                 return self.dbapi.aux_get(pkgname, ["USE"])[0].split()
1036
1037         def gettbz2(self, pkgname):
1038                 """Fetches the package from a remote site, if necessary.  Attempts to
1039                 resume if the file appears to be partially downloaded."""
1040                 print "Fetching '"+str(pkgname)+"'"
1041                 tbz2_path = self.getname(pkgname)
1042                 tbz2name = os.path.basename(tbz2_path)
1043                 resume = False
1044                 if os.path.exists(tbz2_path):
1045                         if (tbz2name not in self.invalids):
1046                                 return
1047                         else:
1048                                 resume = True
1049                                 writemsg("Resuming download of this tbz2, but it is possible that it is corrupt.\n",
1050                                         noiselevel=-1)
1051                 
1052                 mydest = os.path.dirname(self.getname(pkgname))
1053                 self._ensure_dir(mydest)
1054                 from urlparse import urlparse
1055                 # urljoin doesn't work correctly with unrecognized protocols like sftp
1056                 if self._remote_has_index:
1057                         rel_url = self._remotepkgs[pkgname].get("PATH")
1058                         if not rel_url:
1059                                 rel_url = pkgname+".tbz2"
1060                         url = self._remote_base_uri.rstrip("/") + "/" + rel_url.lstrip("/")
1061                 else:
1062                         url = self.settings["PORTAGE_BINHOST"].rstrip("/") + "/" + tbz2name
1063                 protocol = urlparse(url)[0]
1064                 fcmd_prefix = "FETCHCOMMAND"
1065                 if resume:
1066                         fcmd_prefix = "RESUMECOMMAND"
1067                 fcmd = self.settings.get(fcmd_prefix + "_" + protocol.upper())
1068                 if not fcmd:
1069                         fcmd = self.settings.get(fcmd_prefix)
1070                 success = portage.getbinpkg.file_get(url, mydest, fcmd=fcmd)
1071                 if not success:
1072                         try:
1073                                 os.unlink(self.getname(pkgname))
1074                         except OSError:
1075                                 pass
1076                         raise portage.exception.FileNotFound(mydest)
1077                 self.inject(pkgname)
1078
1079         def _load_pkgindex(self):
1080                 pkgindex = self._new_pkgindex()
1081                 try:
1082                         f = open(self._pkgindex_file)
1083                 except EnvironmentError:
1084                         pass
1085                 else:
1086                         try:
1087                                 pkgindex.read(f)
1088                         finally:
1089                                 f.close()
1090                 return pkgindex
1091
1092         def digestCheck(self, pkg):
1093                 """
1094                 Verify digests for the given package and raise DigestException
1095                 if verification fails.
1096                 @rtype: bool
1097                 @returns: True if digests could be located, False otherwise.
1098                 """
1099                 cpv = pkg
1100                 if not isinstance(cpv, basestring):
1101                         cpv = pkg.cpv
1102                         pkg = None
1103
1104                 pkg_path = self.getname(cpv)
1105                 metadata = None
1106                 if self._remotepkgs is None or cpv not in self._remotepkgs:
1107                         for d in self._load_pkgindex().packages:
1108                                 if d["CPV"] == cpv:
1109                                         metadata = d
1110                                         break
1111                 else:
1112                         metadata = self._remotepkgs[cpv]
1113                 if metadata is None:
1114                         return False
1115
1116                 digests = {}
1117                 from portage.checksum import hashfunc_map, verify_all
1118                 for k in hashfunc_map:
1119                         v = metadata.get(k)
1120                         if not v:
1121                                 continue
1122                         digests[k] = v
1123
1124                 if "SIZE" in metadata:
1125                         try:
1126                                 digests["size"] = int(metadata["SIZE"])
1127                         except ValueError:
1128                                 writemsg("!!! Malformed SIZE attribute in remote " + \
1129                                 "metadata for '%s'\n" % cpv)
1130
1131                 if not digests:
1132                         return False
1133
1134                 eout = EOutput()
1135                 eout.quiet = self.settings.get("PORTAGE_QUIET") == "1"
1136                 ok, st = _check_distfile(pkg_path, digests, eout, show_errors=0)
1137                 if not ok:
1138                         ok, reason = verify_all(pkg_path, digests)
1139                         if not ok:
1140                                 raise portage.exception.DigestException(
1141                                         (pkg_path,) + tuple(reason))
1142
1143                 return True
1144
1145         def getslot(self, mycatpkg):
1146                 "Get a slot for a catpkg; assume it exists."
1147                 myslot = ""
1148                 try:
1149                         myslot = self.dbapi.aux_get(mycatpkg,["SLOT"])[0]
1150                 except SystemExit, e:
1151                         raise
1152                 except Exception, e:
1153                         pass
1154                 return myslot