9 def initialize(name, type, attribs = {})
12 @attribs = attribs.dup
17 return @attribs['enabled'].to_i != 0
21 return @attribs['autorefresh'].to_i != 0
25 return @handle ? @handle.id : 0
29 chksum = Solv::Chksum.new(Solv::REPOKEY_TYPE_SHA256)
34 def calc_cookie_file(filename)
35 chksum = Solv::Chksum.new(Solv::REPOKEY_TYPE_SHA256)
37 chksum.add_stat(filename)
41 def cachepath(ext = nil)
42 path = @name.sub(/^\./, '_')
43 path += ext ? "_#{ext}.solvx" : '.solv'
44 return '/var/cache/solv/' + path.gsub(/\//, '_')
48 @handle = pool.add_repo(@name)
49 @handle.appdata = self
50 @handle.priority = 99 - @attribs['priority'].to_i if @attribs['priority']
51 dorefresh = autorefresh?
54 s = File.stat(cachepath)
55 dorefresh = false if s && Time.now - s.mtime < @attribs['metadata_expire'].to_i
56 rescue SystemCallError
60 if !dorefresh && usecachedrepo(nil)
61 puts "repo: '#{@name}' cached"
64 return load_if_changed()
67 def load_ext(repodata)
75 def download(file, uncompress, chksum, markincomplete = false)
76 url = @attribs['baseurl']
78 puts "%{@name}: no baseurl"
81 url = url.sub(/\/$/, '') + "/#{file}"
82 f = Tempfile.new('rbsolv')
84 st = system('curl', '-f', '-s', '-L', '-o', "/dev/fd/" + f.fileno.to_s, '--', url)
85 return nil if f.stat.size == 0 && (st || !chksum)
87 puts "#{file}: download error #{$? >> 8}"
88 @incomplete = true if markincomplete
92 fchksum = Solv::Chksum.new(chksum.type)
93 fchksum.add_fd(f.fileno)
95 puts "#{file}: checksum error"
96 @incomplete = true if markincomplete
101 return Solv::xfopen_dup(file, f.fileno)
103 return Solv::xfopen_dup('', f.fileno)
107 def download_location(location, chksum)
108 f = download(location, false, chksum)
109 abort("\n#{@name}: #{location} not found in repository\n") unless f
113 def usecachedrepo(ext, mark = false)
114 cookie = ext ? @extcookie : @cookie
116 repopath = cachepath(ext)
117 f = File.new(repopath, "r")
118 f.sysseek(-32, IO::SEEK_END)
119 fcookie = f.sysread(32)
120 return false if fcookie.length != 32
121 return false if cookie && fcookie != cookie
122 if !ext && @type != 'system'
123 f.sysseek(-32 * 2, IO::SEEK_END)
124 fextcookie = f.sysread(32)
125 return false if fextcookie.length != 32
127 f.sysseek(0, IO::SEEK_SET)
128 f = Solv::xfopen_dup('', f.fileno)
129 flags = ext ? Solv::Repo::REPO_USE_LOADING|Solv::Repo::REPO_EXTEND_SOLVABLES : 0
130 flags |= Solv::Repo::REPO_LOCALPOOL if ext && ext != 'DL'
131 if ! @handle.add_solv(f, flags)
136 @cookie = fcookie unless ext
137 @extcookie = fextcookie if !ext && @type != 'system'
140 File::utime(now, now, repopath) if mark
141 rescue SystemCallError
144 rescue SystemCallError
151 chksum = Solv::Chksum.new(Solv::REPOKEY_TYPE_SHA256)
155 chksum.add(s.dev.to_s)
156 chksum.add(s.ino.to_s)
157 chksum.add(s.size.to_s)
158 chksum.add(s.mtime.to_s)
160 @extcookie = chksum.raw()
161 @extcookie[0] = 1 if @extcookie[0] == 0
164 def writecachedrepo(ext, info = nil)
166 Dir::mkdir("/var/cache/solv", 0755) unless FileTest.directory?("/var/cache/solv")
167 f = Tempfile.new('.newsolv-', '/var/cache/solv')
169 sf = Solv::xfopen_dup('', f.fileno)
175 @handle.write_first_repodata(sf)
178 f.sysseek(0, IO::SEEK_END)
179 if @type != 'system' && !ext
180 genextcookie(f) unless @extcookie
181 f.syswrite(@extcookie)
183 f.syswrite(ext ? @extcookie : @cookie)
185 if @handle.iscontiguous?
186 sf = Solv::xfopen(f.path)
190 abort("internal error, cannot reload solv file") unless @handle.add_solv(sf, Solv::Repo::SOLV_ADD_NO_STUBS)
192 info.extend_to_repo()
193 info.add_solv(sf, Solv::Repo::REPO_EXTEND_SOLVABLES)
198 File.rename(f.path, cachepath(ext))
201 rescue SystemCallError
206 def updateaddedprovides(addedprovides)
207 return if @incomplete
208 return unless @handle && !@handle.isempty?
209 repodata = @handle.first_repodata()
210 return unless repodata
211 oldaddedprovides = repodata.lookup_idarray(Solv::SOLVID_META, Solv::REPOSITORY_ADDEDFILEPROVIDES)
212 return if (oldaddedprovides | addedprovides) == oldaddedprovides
213 for id in addedprovides
214 repodata.add_idarray(Solv::SOLVID_META, Solv::REPOSITORY_ADDEDFILEPROVIDES, id)
216 repodata.internalize()
217 writecachedrepo(nil, repodata)
221 class Repo_rpmmd < Repo_generic
224 di = @handle.Dataiterator(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_TYPE, what, Solv::Dataiterator::SEARCH_STRING)
225 di.prepend_keyname(Solv::REPOSITORY_REPOMD)
228 filename = dp.lookup_str(Solv::REPOSITORY_REPOMD_LOCATION)
230 checksum = dp.lookup_checksum(Solv::REPOSITORY_REPOMD_CHECKSUM)
232 puts "no #{filename} checksum!"
235 return filename, checksum
241 print "rpmmd repo '#{@name}: "
242 f = download("repodata/repomd.xml", false, nil, nil)
244 puts "no repomd.xml file, skipped"
249 @cookie = calc_cookie_fp(f)
250 if usecachedrepo(nil, true)
255 @handle.add_repomdxml(f, 0)
258 filename, filechksum = find('primary')
260 f = download(filename, true, filechksum, true)
262 @handle.add_rpmmd(f, nil, 0)
265 return false if @incomplete
267 filename, filechksum = find('updateinfo')
269 f = download(filename, true, filechksum, true)
271 @handle.add_updateinfoxml(f, 0)
276 writecachedrepo(nil) unless @incomplete
277 @handle.create_stubs()
281 def add_ext(repodata, what, ext)
282 filename, filechksum = find(what)
283 filename, filechksum = find('prestodelta') if !filename && what == 'deltainfo'
284 return unless filename
285 h = repodata.new_handle()
286 repodata.set_poolstr(h, Solv::REPOSITORY_REPOMD_TYPE, what)
287 repodata.set_str(h, Solv::REPOSITORY_REPOMD_LOCATION, filename)
288 repodata.set_checksum(h, Solv::REPOSITORY_REPOMD_CHECKSUM, filechksum)
290 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOSITORY_DELTAINFO)
291 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_FLEXARRAY)
293 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::SOLVABLE_FILELIST)
294 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_DIRSTRARRAY)
296 repodata.add_flexarray(Solv::SOLVID_META, Solv::REPOSITORY_EXTERNAL, h)
300 repodata = @handle.add_repodata(0)
301 add_ext(repodata, 'deltainfo', 'DL')
302 add_ext(repodata, 'filelists', 'FL')
303 repodata.internalize()
306 def load_ext(repodata)
307 repomdtype = repodata.lookup_str(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_TYPE)
308 if repomdtype == 'filelists'
310 elsif repomdtype == 'deltainfo'
315 print "[#{@name}:#{ext}: "
317 if usecachedrepo(ext)
322 filename = repodata.lookup_str(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_LOCATION)
323 filechksum = repodata.lookup_checksum(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_CHECKSUM)
324 f = download(filename, true, filechksum)
325 return false unless f
327 @handle.add_rpmmd(f, 'FL', Solv::Repo::REPO_USE_LOADING|Solv::Repo::REPO_EXTEND_SOLVABLES)
329 @handle.add_deltainfoxml(f, Solv::Repo::REPO_USE_LOADING)
332 writecachedrepo(ext, repodata)
338 class Repo_susetags < Repo_generic
341 di = @handle.Dataiterator(Solv::SOLVID_META, Solv::SUSETAGS_FILE_NAME, what, Solv::Dataiterator::SEARCH_STRING)
342 di.prepend_keyname(Solv::SUSETAGS_FILE)
345 checksum = dp.lookup_checksum(Solv::SUSETAGS_FILE_CHECKSUM)
346 return what, checksum
352 print "susetags repo '#{@name}: "
353 f = download("content", false, nil, nil)
355 puts "no content file, skipped"
360 @cookie = calc_cookie_fp(f)
361 if usecachedrepo(nil, true)
366 @handle.add_content(f, 0)
369 defvendorid = @handle.lookup_id(Solv::SOLVID_META, Solv::SUSETAGS_DEFAULTVENDOR)
370 descrdir = @handle.lookup_str(Solv::SOLVID_META, Solv::SUSETAGS_DESCRDIR)
371 descrdir = "suse/setup/descr" unless descrdir
372 (filename, filechksum) = find('packages.gz')
373 (filename, filechksum) = find('packages') unless filename
375 f = download("#{descrdir}/#{filename}", true, filechksum, true)
377 @handle.add_susetags(f, defvendorid, nil, Solv::Repo::REPO_NO_INTERNALIZE|Solv::Repo::SUSETAGS_RECORD_SHARES)
379 (filename, filechksum) = find('packages.en.gz')
380 (filename, filechksum) = find('packages.en') unless filename
382 f = download("#{descrdir}/#{filename}", true, filechksum, true)
384 @handle.add_susetags(f, defvendorid, nil, Solv::Repo::REPO_NO_INTERNALIZE|Solv::Repo::REPO_REUSE_REPODATA|Solv::Repo::REPO_EXTEND_SOLVABLES)
388 @handle.internalize()
392 writecachedrepo(nil) unless @incomplete
393 @handle.create_stubs()
398 Solv::SOLVABLE_SUMMARY => Solv::REPOKEY_TYPE_STR,
399 Solv::SOLVABLE_DESCRIPTION => Solv::REPOKEY_TYPE_STR,
400 Solv::SOLVABLE_EULA => Solv::REPOKEY_TYPE_STR,
401 Solv::SOLVABLE_MESSAGEINS => Solv::REPOKEY_TYPE_STR,
402 Solv::SOLVABLE_MESSAGEDEL => Solv::REPOKEY_TYPE_STR,
403 Solv::SOLVABLE_CATEGORY => Solv::REPOKEY_TYPE_ID,
406 def add_ext(repodata, what, ext)
407 (filename, filechksum) = find(what)
408 h = repodata.new_handle()
409 repodata.set_str(h, Solv::SUSETAGS_FILE_NAME, filename)
410 repodata.set_checksum(h, Solv::SUSETAGS_FILE_CHECKSUM, filechksum)
412 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOSITORY_DELTAINFO)
413 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_FLEXARRAY)
415 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::SOLVABLE_DISKUSAGE)
416 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_DIRNUMNUMARRAY)
418 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::SOLVABLE_FILELIST)
419 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_DIRSTRARRAY)
421 @@langtags.sort.each do |langid, langtype|
422 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, @handle.pool.id2langid(langid, ext, true))
423 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, langtype)
426 repodata.add_flexarray(Solv::SOLVID_META, Solv::REPOSITORY_EXTERNAL, h)
430 repodata = @handle.add_repodata(0)
431 di = @handle.Dataiterator(Solv::SOLVID_META, Solv::SUSETAGS_FILE_NAME, nil, 0)
432 di.prepend_keyname(Solv::SUSETAGS_FILE)
435 next unless filename && filename =~ /^packages\.(..)(?:\..*)$/
436 next if $1 == 'en' || $1 == 'gz'
437 add_ext(repodata, filename, $1)
439 repodata.internalize()
442 def load_ext(repodata)
443 filename = repodata.lookup_str(Solv::SOLVID_META, Solv::SUSETAGS_FILE_NAME)
445 print "[#{@name}:#{ext}: "
447 if usecachedrepo(ext)
452 defvendorid = @handle.lookup_id(Solv::SOLVID_META, Solv::SUSETAGS_DEFAULTVENDOR)
453 descrdir = @handle.lookup_str(Solv::SOLVID_META, Solv::SUSETAGS_DESCRDIR)
454 descrdir = "suse/setup/descr" unless descrdir
455 filechksum = repodata.lookup_checksum(Solv::SOLVID_META, Solv::SUSETAGS_FILE_CHECKSUM)
456 f = download("#{descrdir}/#{filename}", true, filechksum)
457 return false unless f
458 @handle.add_susetags(f, defvendorid, ext, Solv::Repo::REPO_USE_LOADING|Solv::Repo::REPO_EXTEND_SOLVABLES)
460 writecachedrepo(ext, repodata)
464 def download_location(location, chksum)
465 datadir = @handle.lookup_str(Solv::SOLVID_META, Solv::SUSETAGS_DATADIR)
466 datadir = "suse" unless datadir
467 super("#{datadir}/#{location}", chksum)
472 class Repo_unknown < Repo_generic
474 puts "unsupported repo '#{@name}: skipped"
479 class Repo_system < Repo_generic
481 @handle = pool.add_repo(@name)
482 @handle.appdata = self
483 pool.installed = @handle
484 print "rpm database: "
485 @cookie = calc_cookie_file("/var/lib/rpm/Packages")
486 if usecachedrepo(nil)
491 if @handle.respond_to? :add_products
492 @handle.add_products("/etc/products.d", Solv::Repo::REPO_NO_INTERNALIZE)
494 @handle.add_rpmdb(nil, Solv::Repo::REPO_REUSE_REPODATA)
502 cmd = 'list' if cmd == 'li'
503 cmd = 'install' if cmd == 'in'
504 cmd = 'erase' if cmd == 'rm'
505 cmd = 'verify' if cmd == 've'
506 cmd = 'search' if cmd == 'se'
510 if FileTest.directory?('/etc/zypp/repos.d')
511 reposdirs = [ '/etc/zypp/repos.d' ]
513 reposdirs = [ '/etc/yum/repos.d' ]
515 for reposdir in reposdirs do
516 next unless FileTest.directory?(reposdir)
517 for reponame in Dir["#{reposdir}/*.repo"].sort do
518 cfg = IniFile.new(reponame)
519 cfg.each_section do |ali|
520 repoattr = { 'alias' => ali, 'enabled' => 0, 'priority' => 99, 'autorefresh' => 1, 'type' => 'rpm-md', 'metadata_expire' => 900}
521 repoattr.update(cfg[ali])
522 if repoattr['type'] == 'rpm-md'
523 repo = Repo_rpmmd.new(ali, 'repomd', repoattr)
524 elsif repoattr['type'] == 'yast2'
525 repo = Repo_susetags.new(ali, 'susetags', repoattr)
527 repo = Repo_unknown.new(ali, 'unknown', repoattr)
534 pool = Solv::Pool.new()
535 # require 'sys/uname' ; sysarch = Sys::Uname.machine
536 sysarch = `uname -p`.strip
537 pool.setarch(sysarch)
539 pool.set_loadcallback { |repodata|
540 repo = repodata.repo.appdata
541 repo ? repo.load_ext(repodata) : false
544 sysrepo = Repo_system.new('@System', 'system')
547 repo.load(pool) if repo.enabled?
552 for di in pool.Dataiterator(0, Solv::SOLVABLE_NAME, args[0], Solv::Dataiterator::SEARCH_SUBSTRING | Solv::Dataiterator::SEARCH_NOCASE)
553 matches[di.solvid] = true
555 for solvid in matches.keys.sort
556 s = pool.solvables[solvid]
557 puts "- #{s.str} [#{s.repo.name}]"
562 addedprovides = pool.addfileprovides_queue()
563 if !addedprovides.empty?
564 sysrepo.updateaddedprovides(addedprovides)
566 repo.updateaddedprovides(addedprovides)
569 pool.createwhatprovides()
573 flags = Solv::Selection::SELECTION_NAME | Solv::Selection::SELECTION_PROVIDES|Solv::Selection::SELECTION_GLOB
575 flags |= Solv::Selection::SELECTION_FILELIST
576 flags |= Solv::Selection::SELECTION_INSTALLED_ONLY if cmd == 'erase'
578 sel = pool.select(arg, flags)
580 sel = pool.select(arg, flags | Solv::Selection::SELECTION_NOCASE)
581 puts "[ignoring case for '#{arg}']" unless sel.isempty?
583 puts "[using file list match for '#{arg}']" if sel.flags & Solv::Selection::SELECTION_FILELIST != 0
584 puts "[using capability match for '#{arg}']" if sel.flags & Solv::Selection::SELECTION_PROVIDES != 0
588 if jobs.empty? && (cmd == 'up' || cmd == 'dup' || cmd == 'verify')
589 sel = pool.Selection()
590 sel.addsimple(Solv::Job::SOLVER_SOLVABLE_ALL, 0)
594 if cmd == 'list' || cmd == 'info'
595 abort("no package matched.") if jobs.empty?
597 for s in job.solvables()
599 puts "Name: #{s.str}"
600 puts "Repo: #{s.repo.name}"
601 puts "Summary: #{s.lookup_str(Solv::SOLVABLE_SUMMARY)}"
602 str = s.lookup_str(Solv::SOLVABLE_URL)
603 puts "Url: #{str}" if str
604 str = s.lookup_str(Solv::SOLVABLE_LICENSE)
605 puts "License: #{str}" if str
606 puts "Description:\n#{s.lookup_str(Solv::SOLVABLE_DESCRIPTION)}"
609 puts " - #{s.str} [#{s.repo.name}]"
610 puts " #{s.lookup_str(Solv::SOLVABLE_SUMMARY)}"
617 if cmd == 'install' || cmd == 'erase' || cmd == 'up' || cmd == 'dup' || cmd == 'verify'
618 abort("no package matched.") if jobs.empty?
621 if job.how == Solv::Job::SOLVER_SOLVABLE_ALL || job.solvables.any? {|s| s.isinstalled?}
622 job.how |= Solv::Job::SOLVER_UPDATE
624 job.how |= Solv::Job::SOLVER_INSTALL
626 elsif cmd == 'install'
627 job.how |= Solv::Job::SOLVER_INSTALL
629 job.how |= Solv::Job::SOLVER_ERASE
631 job.how |= Solv::Job::SOLVER_DISTUPGRADE
632 elsif cmd == 'verify'
633 job.how |= Solv::Job::SOLVER_VERIFY
638 #pool.set_debuglevel(1)
641 solver.set_flag(Solv::Solver::SOLVER_FLAG_SPLITPROVIDES, 1)
642 solver.set_flag(Solv::Solver::SOLVER_FLAG_ALLOW_UNINSTALL, 1) if cmd == 'erase'
643 problems = solver.solve(jobs)
644 break if problems.empty?
645 for problem in problems
646 puts "Problem #{problem.id}:"
647 puts problem.findproblemrule.info.problemstr
648 solutions = problem.solutions
649 for solution in solutions
650 puts " Solution #{solution.id}:"
651 elements = solution.elements(true)
652 for element in elements
653 puts " - #{element.str}"
659 print "Please choose a solution: "
661 sol = STDIN.gets.strip
662 break if sol == 's' || sol == 'q'
663 break if sol =~ /^\d+$/ && sol.to_i >= 1 && sol.to_i <= solutions.length
667 solution = solutions[sol.to_i - 1]
668 for element in solution.elements
669 newjob = element.Job()
670 if element.type == Solv::Solver::SOLVER_SOLUTION_JOB
671 jobs[element.jobidx] = newjob
673 jobs.push(newjob) if newjob && !jobs.include?(newjob)
678 trans = solver.transaction
681 puts "Nothing to do."
684 puts "\nTransaction summary:\n"
685 for cl in trans.classify()
686 if cl.type == Solv::Transaction::SOLVER_TRANSACTION_ERASE
687 puts "#{cl.count} erased packages:"
688 elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_INSTALL
689 puts "#{cl.count} installed packages:"
690 elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_REINSTALLED
691 puts "#{cl.count} reinstalled packages:"
692 elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_DOWNGRADED
693 puts "#{cl.count} downgraded packages:"
694 elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_CHANGED
695 puts "#{cl.count} changed packages:"
696 elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_UPGRADED
697 puts "#{cl.count} upgraded packages:"
698 elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_VENDORCHANGE
699 puts "#{cl.count} vendor changes from '#{pool.id2str(cl.fromid)}' to '#{pool.id2str(cl.toid)}':"
700 elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_ARCHCHANGE
701 puts "#{cl.count} arch changes from '#{pool.id2str(cl.fromid)}' to '#{pool.id2str(cl.toid)}':"
705 for p in cl.solvables
706 if cl.type == Solv::Transaction::SOLVER_TRANSACTION_UPGRADED || cl.type == Solv::Transaction::SOLVER_TRANSACTION_DOWNGRADED
707 puts " - #{p.str} -> #{trans.othersolvable(p).str}"
714 puts "install size change: #{trans.calc_installsizechange()} K\n\n"
716 print("OK to continue (y/n)? ")
718 yn = STDIN.gets.strip
722 newpkgs = trans.newpackages()
727 downloadsize += p.lookup_num(Solv::SOLVABLE_DOWNLOADSIZE)
729 puts "Downloading #{newpkgs.length} packages, #{downloadsize} K"
731 repo = p.repo.appdata
732 location, medianr = p.lookup_location()
734 chksum = p.lookup_checksum(Solv::SOLVABLE_CHECKSUM)
735 f = repo.download_location(location, chksum)
742 puts "Committing transaction:"
746 steptype = trans.steptype(p, Solv::Transaction::SOLVER_TRANSACTION_RPM_ONLY)
747 if steptype == Solv::Transaction::SOLVER_TRANSACTION_ERASE
748 puts "erase #{p.str}"
749 next unless p.lookup_num(Solv::RPM_RPMDBID)
750 evr = p.evr.sub(/^[0-9]+:/, '')
751 system('rpm', '-e', '--nodeps', '--nodigest', '--nosignature', "#{p.name}-#{evr}.#{p.arch}") || abort("rpm failed: #{$? >> 8}")
752 elsif (steptype == Solv::Transaction::SOLVER_TRANSACTION_INSTALL || steptype == Solv::Transaction::SOLVER_TRANSACTION_MULTIINSTALL)
753 puts "install #{p.str}"
754 f = newpkgsfp.delete(p.id)
756 mode = steptype == Solv::Transaction::SOLVER_TRANSACTION_INSTALL ? '-U' : '-i'
757 system('rpm', mode, '--force', '--nodeps', '--nodigest', '--nosignature', "/dev/fd/#{Solv::xfileno(f).to_s}") || abort("rpm failed: #{$? >> 8}")