changes.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. #!/usr/bin/env python
  2. # vim:set et sw=4:
  3. """
  4. Changes class for dak
  5. @contact: Debian FTP Master <ftpmaster@debian.org>
  6. @copyright: 2001 - 2006 James Troup <james@nocrew.org>
  7. @copyright: 2009 Joerg Jaspert <joerg@debian.org>
  8. @copyright: 2009 Mark Hymers <mhy@debian.org>
  9. @license: GNU General Public License version 2 or later
  10. """
  11. # This program is free software; you can redistribute it and/or modify
  12. # it under the terms of the GNU General Public License as published by
  13. # the Free Software Foundation; either version 2 of the License, or
  14. # (at your option) any later version.
  15. # This program is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. # You should have received a copy of the GNU General Public License
  20. # along with this program; if not, write to the Free Software
  21. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  22. ###############################################################################
  23. import os
  24. import stat
  25. import datetime
  26. from cPickle import Unpickler, Pickler
  27. from errno import EPERM
  28. from apt_pkg import TagSection
  29. from utils import open_file, fubar, poolify, deb_extract_control
  30. from config import *
  31. from dbconn import *
  32. ###############################################################################
  33. __all__ = []
  34. ###############################################################################
  35. CHANGESFIELDS_MANDATORY = [ "distribution", "source", "architecture",
  36. "version", "maintainer", "urgency", "fingerprint", "changedby822",
  37. "changedby2047", "changedbyname", "maintainer822", "maintainer2047",
  38. "maintainername", "maintaineremail", "closes", "changes" ]
  39. __all__.append('CHANGESFIELDS_MANDATORY')
  40. CHANGESFIELDS_OPTIONAL = [ "changed-by", "filecontents", "format",
  41. "process-new note", "adv id", "distribution-version", "sponsoremail" ]
  42. __all__.append('CHANGESFIELDS_OPTIONAL')
  43. CHANGESFIELDS_FILES = [ "package", "version", "architecture", "type", "size",
  44. "md5sum", "sha1sum", "sha256sum", "component", "location id",
  45. "source package", "source version", "maintainer", "dbtype", "files id",
  46. "new", "section", "priority", "othercomponents", "pool name",
  47. "original component" ]
  48. __all__.append('CHANGESFIELDS_FILES')
  49. CHANGESFIELDS_DSC = [ "source", "version", "maintainer", "fingerprint",
  50. "uploaders", "bts changelog", "dm-upload-allowed" ]
  51. __all__.append('CHANGESFIELDS_DSC')
  52. CHANGESFIELDS_DSCFILES_MANDATORY = [ "size", "md5sum" ]
  53. __all__.append('CHANGESFIELDS_DSCFILES_MANDATORY')
  54. CHANGESFIELDS_DSCFILES_OPTIONAL = [ "files id" ]
  55. __all__.append('CHANGESFIELDS_DSCFILES_OPTIONAL')
  56. CHANGESFIELDS_ORIGFILES = [ "id", "location" ]
  57. __all__.append('CHANGESFIELDS_ORIGFILES')
  58. ###############################################################################
  59. class Changes(object):
  60. """ Convenience wrapper to carry around all the package information """
  61. def __init__(self, **kwds):
  62. self.reset()
  63. def reset(self):
  64. self.changes_file = ""
  65. self.changes = {}
  66. self.dsc = {}
  67. self.files = {}
  68. self.dsc_files = {}
  69. self.orig_files = {}
  70. def file_summary(self):
  71. # changes["distribution"] may not exist in corner cases
  72. # (e.g. unreadable changes files)
  73. if not self.changes.has_key("distribution") or not \
  74. isinstance(self.changes["distribution"], dict):
  75. self.changes["distribution"] = {}
  76. byhand = False
  77. new = False
  78. summary = ""
  79. override_summary = ""
  80. for name, entry in sorted(self.files.items()):
  81. if entry.has_key("byhand"):
  82. byhand = True
  83. summary += name + " byhand\n"
  84. elif entry.has_key("new"):
  85. new = True
  86. summary += "(new) %s %s %s\n" % (name, entry["priority"], entry["section"])
  87. if entry.has_key("othercomponents"):
  88. summary += "WARNING: Already present in %s distribution.\n" % (entry["othercomponents"])
  89. if entry["type"] == "deb":
  90. deb_fh = open_file(name)
  91. summary += TagSection(deb_extract_control(deb_fh))["Description"] + '\n'
  92. deb_fh.close()
  93. else:
  94. entry["pool name"] = poolify(self.changes.get("source", ""), entry["component"])
  95. destination = entry["pool name"] + name
  96. summary += name + "\n to " + destination + "\n"
  97. if not entry.has_key("type"):
  98. entry["type"] = "unknown"
  99. if entry["type"] in ["deb", "udeb", "dsc"]:
  100. # (queue/unchecked), there we have override entries already, use them
  101. # (process-new), there we dont have override entries, use the newly generated ones.
  102. override_prio = entry.get("override priority", entry["priority"])
  103. override_sect = entry.get("override section", entry["section"])
  104. override_summary += "%s - %s %s\n" % (name, override_prio, override_sect)
  105. return (byhand, new, summary, override_summary)
  106. def check_override(self):
  107. """
  108. Checks override entries for validity.
  109. Returns an empty string if there are no problems
  110. or the text of a warning if there are
  111. """
  112. summary = ""
  113. # Abandon the check if it's a non-sourceful upload
  114. if not self.changes["architecture"].has_key("source"):
  115. return summary
  116. for name, entry in sorted(self.files.items()):
  117. if not entry.has_key("new") and entry["type"] == "deb":
  118. if entry["section"] != "-":
  119. if entry["section"].lower() != entry["override section"].lower():
  120. summary += "%s: package says section is %s, override says %s.\n" % (name,
  121. entry["section"],
  122. entry["override section"])
  123. if entry["priority"] != "-":
  124. if entry["priority"] != entry["override priority"]:
  125. summary += "%s: package says priority is %s, override says %s.\n" % (name,
  126. entry["priority"],
  127. entry["override priority"])
  128. return summary
  129. @session_wrapper
  130. def remove_known_changes(self, session=None):
  131. session.delete(get_dbchange(self.changes_file, session))
  132. def mark_missing_fields(self):
  133. """add "missing" in fields which we will require for the known_changes table"""
  134. for key in ['urgency', 'maintainer', 'fingerprint', 'changed-by' ]:
  135. if (not self.changes.has_key(key)) or (not self.changes[key]):
  136. self.changes[key]='missing'
  137. def __get_file_from_pool(self, filename, entry, session, logger):
  138. cnf = Config()
  139. if cnf.has_key("Dinstall::SuiteSuffix"):
  140. component = cnf["Dinstall::SuiteSuffix"] + entry["component"]
  141. else:
  142. component = entry["component"]
  143. poolname = poolify(entry["source"], component)
  144. l = get_location(cnf["Dir::Pool"], component, session=session)
  145. found, poolfile = check_poolfile(os.path.join(poolname, filename),
  146. entry['size'],
  147. entry["md5sum"],
  148. l.location_id,
  149. session=session)
  150. if found is None:
  151. if logger is not None:
  152. logger.log(["E: Found multiple files for pool (%s) for %s" % (filename, component)])
  153. return None
  154. elif found is False and poolfile is not None:
  155. if logger is not None:
  156. logger.log(["E: md5sum/size mismatch for %s in pool" % (filename)])
  157. return None
  158. else:
  159. if poolfile is None:
  160. if logger is not None:
  161. logger.log(["E: Could not find %s in pool" % (filename)])
  162. return None
  163. else:
  164. return poolfile
  165. @session_wrapper
  166. def add_known_changes(self, dirpath, in_queue=None, session=None, logger=None):
  167. """add "missing" in fields which we will require for the known_changes table"""
  168. cnf = Config()
  169. changesfile = os.path.join(dirpath, self.changes_file)
  170. filetime = datetime.datetime.fromtimestamp(os.path.getctime(changesfile))
  171. self.mark_missing_fields()
  172. multivalues = {}
  173. for key in ("distribution", "architecture", "binary"):
  174. if isinstance(self.changes[key], dict):
  175. multivalues[key] = " ".join(self.changes[key].keys())
  176. else:
  177. multivalues[key] = self.changes[key]
  178. chg = DBChange()
  179. chg.changesname = self.changes_file
  180. chg.seen = filetime
  181. chg.in_queue_id = in_queue
  182. chg.source = self.changes["source"]
  183. chg.binaries = multivalues["binary"]
  184. chg.architecture = multivalues["architecture"]
  185. chg.version = self.changes["version"]
  186. chg.distribution = multivalues["distribution"]
  187. chg.urgency = self.changes["urgency"]
  188. chg.maintainer = self.changes["maintainer"]
  189. chg.fingerprint = self.changes["fingerprint"]
  190. chg.changedby = self.changes["changed-by"]
  191. chg.date = self.changes["date"]
  192. session.add(chg)
  193. files = []
  194. for chg_fn, entry in self.files.items():
  195. try:
  196. f = open(os.path.join(dirpath, chg_fn))
  197. cpf = ChangePendingFile()
  198. cpf.filename = chg_fn
  199. cpf.size = entry['size']
  200. cpf.md5sum = entry['md5sum']
  201. if entry.has_key('sha1sum'):
  202. cpf.sha1sum = entry['sha1sum']
  203. else:
  204. f.seek(0)
  205. cpf.sha1sum = apt_pkg.sha1sum(f)
  206. if entry.has_key('sha256sum'):
  207. cpf.sha256sum = entry['sha256sum']
  208. else:
  209. f.seek(0)
  210. cpf.sha256sum = apt_pkg.sha256sum(f)
  211. session.add(cpf)
  212. files.append(cpf)
  213. f.close()
  214. except IOError:
  215. # Can't find the file, try to look it up in the pool
  216. poolfile = self.__get_file_from_pool(chg_fn, entry, session)
  217. if poolfile:
  218. chg.poolfiles.append(poolfile)
  219. chg.files = files
  220. # Add files referenced in .dsc, but not included in .changes
  221. for name, entry in self.dsc_files.items():
  222. if self.files.has_key(name):
  223. continue
  224. entry['source'] = self.changes['source']
  225. poolfile = self.__get_file_from_pool(name, entry, session, logger)
  226. if poolfile:
  227. chg.poolfiles.append(poolfile)
  228. session.commit()
  229. chg = session.query(DBChange).filter_by(changesname = self.changes_file).one();
  230. return chg
  231. def unknown_files_fields(self, name):
  232. return sorted(list( set(self.files[name].keys()) -
  233. set(CHANGESFIELDS_FILES)))
  234. def unknown_changes_fields(self):
  235. return sorted(list( set(self.changes.keys()) -
  236. set(CHANGESFIELDS_MANDATORY + CHANGESFIELDS_OPTIONAL)))
  237. def unknown_dsc_fields(self):
  238. return sorted(list( set(self.dsc.keys()) -
  239. set(CHANGESFIELDS_DSC)))
  240. def unknown_dsc_files_fields(self, name):
  241. return sorted(list( set(self.dsc_files[name].keys()) -
  242. set(CHANGESFIELDS_DSCFILES_MANDATORY + CHANGESFIELDS_DSCFILES_OPTIONAL)))
  243. def str_files(self):
  244. r = []
  245. for name, entry in self.files.items():
  246. r.append(" %s:" % (name))
  247. for i in CHANGESFIELDS_FILES:
  248. if entry.has_key(i):
  249. r.append(" %s: %s" % (i.capitalize(), entry[i]))
  250. xfields = self.unknown_files_fields(name)
  251. if len(xfields) > 0:
  252. r.append("files[%s] still has following unrecognised keys: %s" % (name, ", ".join(xfields)))
  253. return r
  254. def str_changes(self):
  255. r = []
  256. for i in CHANGESFIELDS_MANDATORY:
  257. val = self.changes[i]
  258. if isinstance(val, list):
  259. val = " ".join(val)
  260. elif isinstance(val, dict):
  261. val = " ".join(val.keys())
  262. r.append(' %s: %s' % (i.capitalize(), val))
  263. for i in CHANGESFIELDS_OPTIONAL:
  264. if self.changes.has_key(i):
  265. r.append(' %s: %s' % (i.capitalize(), self.changes[i]))
  266. xfields = self.unknown_changes_fields()
  267. if len(xfields) > 0:
  268. r.append("Warning: changes still has the following unrecognised fields: %s" % ", ".join(xfields))
  269. return r
  270. def str_dsc(self):
  271. r = []
  272. for i in CHANGESFIELDS_DSC:
  273. if self.dsc.has_key(i):
  274. r.append(' %s: %s' % (i.capitalize(), self.dsc[i]))
  275. xfields = self.unknown_dsc_fields()
  276. if len(xfields) > 0:
  277. r.append("Warning: dsc still has the following unrecognised fields: %s" % ", ".join(xfields))
  278. return r
  279. def str_dsc_files(self):
  280. r = []
  281. for name, entry in self.dsc_files.items():
  282. r.append(" %s:" % (name))
  283. for i in CHANGESFIELDS_DSCFILES_MANDATORY:
  284. r.append(" %s: %s" % (i.capitalize(), entry[i]))
  285. for i in CHANGESFIELDS_DSCFILES_OPTIONAL:
  286. if entry.has_key(i):
  287. r.append(" %s: %s" % (i.capitalize(), entry[i]))
  288. xfields = self.unknown_dsc_files_fields(name)
  289. if len(xfields) > 0:
  290. r.append("dsc_files[%s] still has following unrecognised keys: %s" % (name, ", ".join(xfields)))
  291. return r
  292. def __str__(self):
  293. r = []
  294. r.append(" Changes:")
  295. r += self.str_changes()
  296. r.append("")
  297. r.append(" Dsc:")
  298. r += self.str_dsc()
  299. r.append("")
  300. r.append(" Files:")
  301. r += self.str_files()
  302. r.append("")
  303. r.append(" Dsc Files:")
  304. r += self.str_dsc_files()
  305. return "\n".join(r)
  306. __all__.append('Changes')