16 def initialize(name, type, attribs = {})
19 @attribs = attribs.dup
24 return @attribs['enabled'].to_i != 0
28 return @attribs['autorefresh'].to_i != 0
32 chksum = Solv::Chksum.new(Solv::REPOKEY_TYPE_SHA256)
37 def calc_cookie_file(filename)
38 chksum = Solv::Chksum.new(Solv::REPOKEY_TYPE_SHA256)
40 chksum.add_stat(filename)
44 def cachepath(ext = nil)
45 path = @name.sub(/^\./, '_')
46 path += ext ? "_#{ext}.solvx" : '.solv'
47 return '/var/cache/solv/' + path.gsub(/\//, '_')
51 @handle = pool.add_repo(@name)
52 @handle.appdata = self
53 @handle.priority = 99 - @attribs['priority'].to_i if @attribs['priority']
54 dorefresh = autorefresh?
57 s = File.stat(cachepath)
58 dorefresh = false if s && Time.now - s.mtime < @attribs['metadata_expire'].to_i
59 rescue SystemCallError
63 if !dorefresh && usecachedrepo(nil)
64 puts "repo: '#{@name}' cached"
67 return load_if_changed()
70 def load_ext(repodata)
78 def download(file, uncompress, chksum, markincomplete = false)
79 url = @attribs['baseurl']
81 puts "%{@name}: no baseurl"
84 url = url.sub(/\/$/, '') + "/#{file}"
85 f = Tempfile.new('rbsolv')
87 st = system('curl', '-f', '-s', '-L', '-o', "/dev/fd/" + f.fileno.to_s, '--', url)
88 return nil if f.stat.size == 0 && (st || !chksum)
90 puts "#{file}: download error #{$? >> 8}"
91 @incomplete = true if markincomplete
95 fchksum = Solv::Chksum.new(chksum.type)
96 fchksum.add_fd(f.fileno)
97 if !fchksum.matches(chksum)
98 puts "#{file}: checksum error"
99 @incomplete = true if markincomplete
104 return Solv::xfopen_dup(file, f.fileno)
106 return Solv::xfopen_dup('', f.fileno)
110 def usecachedrepo(ext, mark = false)
111 cookie = ext ? @extcookie : @cookie
113 repopath = cachepath(ext)
114 f = File.new(repopath, "r")
115 f.sysseek(-32, IO::SEEK_END)
116 fcookie = f.sysread(32)
117 return false if fcookie.length != 32
118 return false if cookie && fcookie != cookie
119 if !ext && @type != 'system'
120 f.sysseek(-32 * 2, IO::SEEK_END)
121 fextcookie = f.sysread(32)
122 return false if fextcookie.length != 32
124 f.sysseek(0, IO::SEEK_SET)
125 f = Solv::xfopen_dup('', f.fileno)
126 flags = ext ? Solv::Repo::REPO_USE_LOADING|Solv::Repo::REPO_EXTEND_SOLVABLES : 0
127 flags |= Solv::Repo::REPO_LOCALPOOL if ext && ext != 'DL'
128 if ! @handle.add_solv(f, flags)
133 @cookie = fcookie unless ext
134 @extcookie = fextcookie if !ext && @type != 'system'
137 File::utime(now, now, repopath) if mark
138 rescue SystemCallError
141 rescue SystemCallError
148 chksum = Solv::Chksum.new(Solv::REPOKEY_TYPE_SHA256)
152 chksum.add(s.dev.to_s);
153 chksum.add(s.ino.to_s);
154 chksum.add(s.size.to_s);
155 chksum.add(s.mtime.to_s);
157 @extcookie = chksum.raw()
158 @extcookie[0] = 1 if @extcookie[0] == 0
161 def writecachedrepo(ext, info = nil)
163 Dir::mkdir("/var/cache/solv", 0755) unless FileTest.directory?("/var/cache/solv")
164 f = Tempfile.new('.newsolv-', '/var/cache/solv')
166 sf = Solv::xfopen_dup('', f.fileno)
172 @handle.write_first_repodata(sf)
175 f.sysseek(0, IO::SEEK_END)
176 if @type != 'system' && !ext
177 genextcookie(f) unless @extcookie
178 f.syswrite(@extcookie)
180 f.syswrite(ext ? @extcookie : @cookie)
182 File.rename(f.path, cachepath(ext))
185 rescue SystemCallError
192 class Repo_rpmmd < Repo_generic
195 di = @handle.Dataiterator(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_TYPE, what, Solv::Dataiterator::SEARCH_STRING)
196 di.prepend_keyname(Solv::REPOSITORY_REPOMD)
199 filename = d.pool.lookup_str(Solv::SOLVID_POS, Solv::REPOSITORY_REPOMD_LOCATION)
201 checksum = d.pool.lookup_checksum(Solv::SOLVID_POS, Solv::REPOSITORY_REPOMD_CHECKSUM)
203 puts "no #{filename} checksum!"
206 return filename, checksum
212 print "rpmmd repo '#{@name}: "
213 f = download("repodata/repomd.xml", false, nil, nil)
215 puts "no repomd.xml file, skipped"
220 @cookie = calc_cookie_fp(f)
221 if usecachedrepo(nil, true)
226 @handle.add_repomdxml(f, 0)
229 filename, filechksum = find('primary')
231 f = download(filename, true, filechksum, true)
233 @handle.add_rpmmd(f, nil, 0)
236 return false if @incomplete
238 filename, filechksum = find('updateinfo')
240 f = download(filename, true, filechksum, true)
242 @handle.add_updateinfoxml(f, 0)
247 writecachedrepo(nil) unless @incomplete
248 @handle.create_stubs()
252 def add_ext(repodata, what, ext)
253 filename, filechksum = find(what)
254 filename, filechksum = find('prestodelta') if !filename && what == 'deltainfo'
255 return unless filename
256 h = repodata.new_handle()
257 repodata.set_poolstr(h, Solv::REPOSITORY_REPOMD_TYPE, what)
258 repodata.set_str(h, Solv::REPOSITORY_REPOMD_LOCATION, filename)
259 repodata.set_checksum(h, Solv::REPOSITORY_REPOMD_CHECKSUM, filechksum)
261 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOSITORY_DELTAINFO)
262 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_FLEXARRAY)
264 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::SOLVABLE_FILELIST)
265 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_DIRSTRARRAY)
267 repodata.add_flexarray(Solv::SOLVID_META, Solv::REPOSITORY_EXTERNAL, h)
271 repodata = @handle.add_repodata(0)
272 add_ext(repodata, 'deltainfo', 'DL')
273 add_ext(repodata, 'filelists', 'FL')
274 repodata.internalize()
277 def load_ext(repodata)
278 repomdtype = repodata.lookup_str(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_TYPE)
279 if repomdtype == 'filelists'
281 elsif repomdtype == 'deltainfo'
286 print "[#{@name}:#{ext}: "
288 if usecachedrepo(ext)
293 filename = repodata.lookup_str(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_LOCATION)
294 filechksum = repodata.lookup_checksum(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_CHECKSUM)
295 f = download(filename, true, filechksum)
296 return false unless f
298 @handle.add_rpmmd(f, 'FL', Solv::Repo::REPO_USE_LOADING|Solv::Repo::REPO_EXTEND_SOLVABLES)
300 @handle.add_deltainfoxml(f, Solv::Repo::REPO_USE_LOADING)
303 writecachedrepo(ext, repodata)
309 class Repo_susetags < Repo_generic
312 di = @handle.Dataiterator(Solv::SOLVID_META, Solv::SUSETAGS_FILE_NAME, what, Solv::Dataiterator::SEARCH_STRING)
313 di.prepend_keyname(Solv::SUSETAGS_FILE)
316 checksum = d.pool.lookup_checksum(Solv::SOLVID_POS, Solv::SUSETAGS_FILE_CHECKSUM)
317 return what, checksum
323 print "susetags repo '#{@name}: "
324 f = download("content", false, nil, nil)
326 puts "no content file, skipped"
331 @cookie = calc_cookie_fp(f)
332 if usecachedrepo(nil, true)
337 @handle.add_content(f, 0)
340 defvendorid = @handle.lookup_id(Solv::SOLVID_META, Solv::SUSETAGS_DEFAULTVENDOR)
341 descrdir = @handle.lookup_str(Solv::SOLVID_META, Solv::SUSETAGS_DESCRDIR)
342 descrdir = "suse/setup/descr" unless descrdir
343 (filename, filechksum) = find('packages.gz')
344 (filename, filechksum) = find('packages') unless filename
346 f = download("#{descrdir}/#{filename}", true, filechksum, true)
348 @handle.add_susetags(f, defvendorid, nil, Solv::Repo::REPO_NO_INTERNALIZE|Solv::Repo::SUSETAGS_RECORD_SHARES)
350 (filename, filechksum) = find('packages.en.gz')
351 (filename, filechksum) = find('packages.en') unless filename
353 f = download("#{descrdir}/#{filename}", true, filechksum, true)
355 @handle.add_susetags(f, defvendorid, nil, Solv::Repo::REPO_NO_INTERNALIZE|Solv::Repo::REPO_REUSE_REPODATA|Solv::Repo::REPO_EXTEND_SOLVABLES)
359 @handle.internalize()
363 writecachedrepo(nil) unless @incomplete
364 @handle.create_stubs()
369 repodata = @handle.add_repodata(0)
370 repodata.internalize()
375 class Repo_unknown < Repo_generic
377 puts "unsupported repo '#{@name}: skipped"
382 class Repo_system < Repo_generic
384 @handle = pool.add_repo(@name)
385 @handle.appdata = self
386 pool.installed = @handle
387 print "rpm database: "
388 @cookie = calc_cookie_file("/var/lib/rpm/Packages")
389 if usecachedrepo(nil)
394 @handle.add_products("/etc/products.d", Solv::Repo::REPO_NO_INTERNALIZE)
395 @handle.add_rpmdb(nil, Solv::Repo::REPO_REUSE_REPODATA)
403 def depglob(pool, name, globname, globdep)
404 id = pool.str2id(name, false)
407 providers = pool.providers(id)
408 if globname && providers.find {|s| s.nameid == id }
409 return [ pool.Job(Solv::Job::SOLVER_SOLVABLE_NAME, id) ]
412 puts "[using capability match for '#{name}']" if globname && globdep
413 return [ pool.Job(Solv::Job::SOLVER_SOLVABLE_PROVIDES, id) ]
416 return [] unless name =~ /[\[*?]/;
419 for d in pool.Dataiterator(0, Solv::SOLVABLE_NAME, name, Solv::Dataiterator::SEARCH_GLOB)
421 idmatches[s.nameid] = 1 if s.installable?
424 return idmatches.keys.sort.collect { |id| pool.Job(Solv::Job::SOLVER_SOLVABLE_NAME, id) }
428 idmatches = pool.matchprovidingids(name, Solv::Dataiterator::SEARCH_GLOB);
430 puts "[using capability match for '#{name}']"
431 return idmatches.sort.collect { |id| pool.Job(Solv::Job::SOLVER_SOLVABLE_PROVIDES, id) }
437 def mkjobs_filelist(pool, cmd, arg)
438 type = Solv::Dataiterator::SEARCH_STRING
439 type = Solv::Dataiterator::SEARCH_GLOB if arg =~ /[\[*?]/
441 di = pool.installed.Dataiterator(0, Solv::SOLVABLE_FILELIST, arg, type | Solv::Dataiterator::SEARCH_FILES|Solv::Dataiterator::SEARCH_COMPLETE_FILELIST)
443 di = pool.Dataiterator(0, Solv::SOLVABLE_FILELIST, arg, type | Solv::Dataiterator::SEARCH_FILES|Solv::Dataiterator::SEARCH_COMPLETE_FILELIST)
448 next unless s && s.installable?
452 return [] if matches.empty?
453 puts "[using file list match for '#{arg}'"
454 if matches.length > 1
455 return [ pool.Job(Solv::Job::SOLVER_SOLVABLE_ONE_OF, pool.towhatprovides(matches)) ]
457 return [ pool.Job(Solv::Job::SOLVER_SOLVABLE | Solv::Job::SOLVER_NOAUTOSET, matches[0]) ]
461 def mkjobs(pool, cmd, arg)
463 jobs = mkjobs_filelist(pool, cmd, arg)
464 return jobs unless jobs.empty?
466 return depglob(pool, arg, true, true)
471 cmd = 'list' if cmd == 'li'
472 cmd = 'install' if cmd == 'in'
473 cmd = 'erase' if cmd == 'rm'
474 cmd = 'verify' if cmd == 've'
475 cmd = 'search' if cmd == 'se'
478 for reposdir in [ '/etc/zypp/repos.d' ] do
479 next unless FileTest.directory?(reposdir)
480 for reponame in Dir["#{reposdir}/*.repo"].sort do
481 cfg = IniFile.new(reponame)
482 cfg.each_section do |ali|
483 repoattr = { 'alias' => ali, 'enabled' => 0, 'priority' => 99, 'autorefresh' => 1, 'type' => 'rpm-md', 'metadata_expire' => 900}
484 repoattr.update(cfg[ali])
485 if repoattr['type'] == 'rpm-md'
486 repo = Repo_rpmmd.new(ali, 'repomd', repoattr)
487 elsif repoattr['type'] == 'yast2'
488 repo = Repo_susetags.new(ali, 'susetags', repoattr)
490 repo = Repo_unknown.new(ali, 'unknown', repoattr)
497 pool = Solv::Pool.new()
498 # require 'sys/uname' ; sysarch = Sys::Uname.machine
499 sysarch = `uname -p`.strip
500 pool.setarch(sysarch)
502 pool.set_loadcallback { |repodata|
503 repo = repodata.repo.appdata
504 repo ? repo.load_ext(repodata) : false
507 sysrepo = Repo_system.new('@System', 'system')
510 repo.load(pool) if repo.enabled?
515 for di in pool.Dataiterator(0, Solv::SOLVABLE_NAME, args[0], Solv::Dataiterator::SEARCH_SUBSTRING | Solv::Dataiterator::SEARCH_NOCASE)
516 matches[di.solvid] = true
518 for solvid in matches.keys.sort
519 s = pool.solvables[solvid]
520 puts "- #{s.str} [#{s.repo.name}]"
526 pool.createwhatprovides
530 njobs = mkjobs(pool, cmd, ARGV[0])
531 abort("nothing matches '#{arg}'") if njobs.empty?
536 job.how |= Solv::Job::SOLVER_ERASE
540 problems = solver.solve(jobs)
541 for problem in problems
542 puts "Problem #{problem.id}:"
543 puts problem.findproblemrule.info.problemstr
544 solutions = problem.solutions
545 for solution in solutions
546 puts " Solution #{solution.id}:"
547 elements = solution.elements
548 for element in elements
549 puts " - type #{element.type}"