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)
35 def calc_cookie_file(filename)
36 chksum = Solv::Chksum.new(Solv::REPOKEY_TYPE_SHA256)
38 chksum.add_stat(filename)
42 def calc_cookie_ext(f, cookie)
43 chksum = Solv::Chksum.new(Solv::REPOKEY_TYPE_SHA256)
46 chksum.add_fstat(f.fileno)
50 def cachepath(ext = nil)
51 path = @name.sub(/^\./, '_')
52 path += ext ? "_#{ext}.solvx" : '.solv'
53 return '/var/cache/solv/' + path.gsub(/\//, '_')
57 @handle = pool.add_repo(@name)
58 @handle.appdata = self
59 @handle.priority = 99 - @attribs['priority'].to_i if @attribs['priority']
60 dorefresh = autorefresh?
63 s = File.stat(cachepath)
64 dorefresh = false if s && (@attribs['metadata_expire'].to_i == -1 || Time.now - s.mtime < @attribs['metadata_expire'].to_i)
65 rescue SystemCallError
70 if !dorefresh && usecachedrepo(nil)
71 puts "repo: '#{@name}' cached"
77 def load_ext(repodata)
81 def download(file, uncompress, chksum, markincomplete = false)
82 url = @attribs['baseurl']
84 puts "%{@name}: no baseurl"
87 url = url.sub(/\/$/, '') + "/#{file}"
88 f = Tempfile.new('rbsolv')
90 st = system('curl', '-f', '-s', '-L', '-o', "/dev/fd/" + f.fileno.to_s, '--', url)
91 return nil if f.stat.size == 0 && (st || !chksum)
93 puts "#{file}: download error #{$? >> 8}"
94 @incomplete = true if markincomplete
98 fchksum = Solv::Chksum.new(chksum.type)
99 fchksum.add_fd(f.fileno)
100 if !fchksum == chksum
101 puts "#{file}: checksum error"
102 @incomplete = true if markincomplete
108 rf = Solv::xfopen_fd(file, f.fileno)
110 rf = Solv::xfopen_fd('', f.fileno)
116 def usecachedrepo(ext, mark = false)
117 cookie = ext ? @extcookie : @cookie
119 repopath = cachepath(ext)
120 f = File.new(repopath, "r")
121 f.sysseek(-32, IO::SEEK_END)
122 fcookie = f.sysread(32)
123 return false if fcookie.length != 32
124 return false if cookie && fcookie != cookie
125 if !ext && @type != 'system'
126 f.sysseek(-32 * 2, IO::SEEK_END)
127 fextcookie = f.sysread(32)
128 return false if fextcookie.length != 32
130 f.sysseek(0, IO::SEEK_SET)
131 nf = Solv::xfopen_fd('', f.fileno)
133 flags = ext ? Solv::Repo::REPO_USE_LOADING|Solv::Repo::REPO_EXTEND_SOLVABLES : 0
134 flags |= Solv::Repo::REPO_LOCALPOOL if ext && ext != 'DL'
135 if ! @handle.add_solv(nf, flags)
140 @cookie = fcookie unless ext
141 @extcookie = fextcookie if !ext && @type != 'system'
144 File::utime(now, now, repopath) if mark
145 rescue SystemCallError
148 rescue SystemCallError
154 def writecachedrepo(ext, repodata = nil)
155 return if @incomplete
157 Dir::mkdir("/var/cache/solv", 0755) unless FileTest.directory?("/var/cache/solv")
158 f = Tempfile.new('.newsolv-', '/var/cache/solv')
160 sf = Solv::xfopen_fd('', f.fileno)
166 @handle.write_first_repodata(sf)
169 f.sysseek(0, IO::SEEK_END)
170 if @type != 'system' && !ext
171 @extcookie = calc_cookie_ext(f, @cookie) unless @extcookie
172 f.syswrite(@extcookie)
174 f.syswrite(ext ? @extcookie : @cookie)
176 if @handle.iscontiguous?
177 sf = Solv::xfopen(f.path)
181 abort("internal error, cannot reload solv file") unless @handle.add_solv(sf, repodata ? 0 : Solv::Repo::SOLV_ADD_NO_STUBS)
183 repodata.extend_to_repo()
184 flags = Solv::Repo::REPO_EXTEND_SOLVABLES
185 flags |= Solv::Repo::REPO_LOCALPOOL if ext != 'DL'
186 repodata.add_solv(sf, flags)
191 File.rename(f.path, cachepath(ext))
194 rescue SystemCallError
199 def updateaddedprovides(addedprovides)
200 return if @incomplete
201 return unless @handle && !@handle.isempty?
202 repodata = @handle.first_repodata()
203 return unless repodata
204 oldaddedprovides = repodata.lookup_idarray(Solv::SOLVID_META, Solv::REPOSITORY_ADDEDFILEPROVIDES)
205 return if (oldaddedprovides | addedprovides) == oldaddedprovides
206 for id in addedprovides
207 repodata.add_idarray(Solv::SOLVID_META, Solv::REPOSITORY_ADDEDFILEPROVIDES, id)
209 repodata.internalize()
210 writecachedrepo(nil, repodata)
218 Solv::SOLVABLE_SUMMARY => Solv::REPOKEY_TYPE_STR,
219 Solv::SOLVABLE_DESCRIPTION => Solv::REPOKEY_TYPE_STR,
220 Solv::SOLVABLE_EULA => Solv::REPOKEY_TYPE_STR,
221 Solv::SOLVABLE_MESSAGEINS => Solv::REPOKEY_TYPE_STR,
222 Solv::SOLVABLE_MESSAGEDEL => Solv::REPOKEY_TYPE_STR,
223 Solv::SOLVABLE_CATEGORY => Solv::REPOKEY_TYPE_ID,
226 def add_ext_keys(ext, repodata, h)
228 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOSITORY_DELTAINFO)
229 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_FLEXARRAY)
231 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::SOLVABLE_DISKUSAGE)
232 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_DIRNUMNUMARRAY)
234 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::SOLVABLE_FILELIST)
235 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, Solv::REPOKEY_TYPE_DIRSTRARRAY)
237 @@langtags.sort.each do |langid, langtype|
238 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, @handle.pool.id2langid(langid, ext, true))
239 repodata.add_idarray(h, Solv::REPOSITORY_KEYS, langtype)
245 class Repo_rpmmd < Repo_generic
248 di = @handle.Dataiterator_meta(Solv::REPOSITORY_REPOMD_TYPE, what, Solv::Dataiterator::SEARCH_STRING)
249 di.prepend_keyname(Solv::REPOSITORY_REPOMD)
252 filename = dp.lookup_str(Solv::REPOSITORY_REPOMD_LOCATION)
254 checksum = dp.lookup_checksum(Solv::REPOSITORY_REPOMD_CHECKSUM)
256 puts "no #{filename} checksum!"
259 return filename, checksum
265 return true if super(pool)
266 print "rpmmd repo '#{@name}: "
267 f = download("repodata/repomd.xml", false, nil, nil)
269 puts "no repomd.xml file, skipped"
274 @cookie = calc_cookie_fp(f)
275 if usecachedrepo(nil, true)
280 @handle.add_repomdxml(f, 0)
283 filename, filechksum = find('primary')
285 f = download(filename, true, filechksum, true)
287 @handle.add_rpmmd(f, nil, 0)
290 return false if @incomplete
292 filename, filechksum = find('updateinfo')
294 f = download(filename, true, filechksum, true)
296 @handle.add_updateinfoxml(f, 0)
302 @handle.create_stubs()
306 def add_ext(repodata, what, ext)
307 filename, filechksum = find(what)
308 filename, filechksum = find('prestodelta') if !filename && what == 'deltainfo'
309 return unless filename
310 h = repodata.new_handle()
311 repodata.set_poolstr(h, Solv::REPOSITORY_REPOMD_TYPE, what)
312 repodata.set_str(h, Solv::REPOSITORY_REPOMD_LOCATION, filename)
313 repodata.set_checksum(h, Solv::REPOSITORY_REPOMD_CHECKSUM, filechksum)
314 add_ext_keys(ext, repodata, h)
315 repodata.add_flexarray(Solv::SOLVID_META, Solv::REPOSITORY_EXTERNAL, h)
319 repodata = @handle.add_repodata(0)
320 repodata.extend_to_repo()
321 add_ext(repodata, 'deltainfo', 'DL')
322 add_ext(repodata, 'filelists', 'FL')
323 repodata.internalize()
326 def load_ext(repodata)
327 repomdtype = repodata.lookup_str(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_TYPE)
328 if repomdtype == 'filelists'
330 elsif repomdtype == 'deltainfo'
335 print "[#{@name}:#{ext}: "
337 if usecachedrepo(ext)
342 filename = repodata.lookup_str(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_LOCATION)
343 filechksum = repodata.lookup_checksum(Solv::SOLVID_META, Solv::REPOSITORY_REPOMD_CHECKSUM)
344 f = download(filename, true, filechksum)
345 return false unless f
347 @handle.add_rpmmd(f, 'FL', Solv::Repo::REPO_USE_LOADING|Solv::Repo::REPO_EXTEND_SOLVABLES|Solv::Repo::REPO_LOCALPOOL)
349 @handle.add_deltainfoxml(f, Solv::Repo::REPO_USE_LOADING)
352 writecachedrepo(ext, repodata)
358 class Repo_susetags < Repo_generic
361 di = @handle.Dataiterator_meta(Solv::SUSETAGS_FILE_NAME, what, Solv::Dataiterator::SEARCH_STRING)
362 di.prepend_keyname(Solv::SUSETAGS_FILE)
365 checksum = dp.lookup_checksum(Solv::SUSETAGS_FILE_CHECKSUM)
366 return what, checksum
372 return true if super(pool)
373 print "susetags repo '#{@name}: "
374 f = download("content", false, nil, nil)
376 puts "no content file, skipped"
381 @cookie = calc_cookie_fp(f)
382 if usecachedrepo(nil, true)
387 @handle.add_content(f, 0)
390 defvendorid = @handle.meta.lookup_id(Solv::SUSETAGS_DEFAULTVENDOR)
391 descrdir = @handle.meta.lookup_str(Solv::SUSETAGS_DESCRDIR)
392 descrdir = "suse/setup/descr" unless descrdir
393 (filename, filechksum) = find('packages.gz')
394 (filename, filechksum) = find('packages') unless filename
396 f = download("#{descrdir}/#{filename}", true, filechksum, true)
398 @handle.add_susetags(f, defvendorid, nil, Solv::Repo::REPO_NO_INTERNALIZE|Solv::Repo::SUSETAGS_RECORD_SHARES)
400 (filename, filechksum) = find('packages.en.gz')
401 (filename, filechksum) = find('packages.en') unless filename
403 f = download("#{descrdir}/#{filename}", true, filechksum, true)
405 @handle.add_susetags(f, defvendorid, nil, Solv::Repo::REPO_NO_INTERNALIZE|Solv::Repo::REPO_REUSE_REPODATA|Solv::Repo::REPO_EXTEND_SOLVABLES)
409 @handle.internalize()
414 @handle.create_stubs()
418 def add_ext(repodata, what, ext)
419 (filename, filechksum) = find(what)
420 h = repodata.new_handle()
421 repodata.set_str(h, Solv::SUSETAGS_FILE_NAME, filename)
422 repodata.set_checksum(h, Solv::SUSETAGS_FILE_CHECKSUM, filechksum)
423 add_ext_keys(ext, repodata, h)
424 repodata.add_flexarray(Solv::SOLVID_META, Solv::REPOSITORY_EXTERNAL, h)
428 repodata = @handle.add_repodata(0)
429 di = @handle.Dataiterator_meta(Solv::SUSETAGS_FILE_NAME, nil, 0)
430 di.prepend_keyname(Solv::SUSETAGS_FILE)
433 next unless filename && filename =~ /^packages\.(..)(?:\..*)$/
434 next if $1 == 'en' || $1 == 'gz'
435 add_ext(repodata, filename, $1)
437 repodata.internalize()
440 def load_ext(repodata)
441 filename = repodata.lookup_str(Solv::SOLVID_META, Solv::SUSETAGS_FILE_NAME)
443 print "[#{@name}:#{ext}: "
445 if usecachedrepo(ext)
450 defvendorid = @handle.meta.lookup_id(Solv::SUSETAGS_DEFAULTVENDOR)
451 descrdir = @handle.meta.lookup_str(Solv::SUSETAGS_DESCRDIR)
452 descrdir = "suse/setup/descr" unless descrdir
453 filechksum = repodata.lookup_checksum(Solv::SOLVID_META, Solv::SUSETAGS_FILE_CHECKSUM)
454 f = download("#{descrdir}/#{filename}", true, filechksum)
455 return false unless f
456 flags = Solv::Repo::REPO_USE_LOADING|Solv::Repo::REPO_EXTEND_SOLVABLES
457 flags |= Solv::Repo::REPO_LOCALPOOL if ext != 'DL'
458 @handle.add_susetags(f, defvendorid, ext, flags)
460 writecachedrepo(ext, repodata)
465 datadir = @handle.meta.lookup_str(Solv::SUSETAGS_DATADIR)
466 datadir = "suse" unless datadir
471 class Repo_unknown < Repo_generic
473 puts "unsupported repo '#{@name}: skipped"
478 class Repo_system < Repo_generic
480 @handle = pool.add_repo(@name)
481 @handle.appdata = self
482 pool.installed = @handle
483 print "rpm database: "
484 @cookie = calc_cookie_file("/var/lib/rpm/Packages")
485 if usecachedrepo(nil)
490 if @handle.respond_to? :add_products
491 @handle.add_products("/etc/products.d", Solv::Repo::REPO_NO_INTERNALIZE)
493 f = Solv::xfopen(cachepath())
494 @handle.add_rpmdb_reffp(f, Solv::Repo::REPO_REUSE_REPODATA)
504 cmdabbrev = { 'ls' => 'list', 'in' => 'install', 'rm' => 'erase',
505 've' => 'verify', 'se' => 'search' }
506 cmd = cmdabbrev[cmd] if cmdabbrev.has_key?(cmd)
509 'install' => Solv::Job::SOLVER_INSTALL,
510 'erase' => Solv::Job::SOLVER_ERASE,
511 'up' => Solv::Job::SOLVER_UPDATE,
512 'dup' => Solv::Job::SOLVER_DISTUPGRADE,
513 'verify' => Solv::Job::SOLVER_VERIFY,
520 if FileTest.directory?('/etc/zypp/repos.d')
521 reposdirs = [ '/etc/zypp/repos.d' ]
523 reposdirs = [ '/etc/yum/repos.d' ]
525 for reposdir in reposdirs do
526 next unless FileTest.directory?(reposdir)
527 for reponame in Dir["#{reposdir}/*.repo"].sort do
528 cfg = IniFile.load(reponame)
529 cfg.each_section do |ali|
530 repoattr = { 'alias' => ali, 'enabled' => 0, 'priority' => 99, 'autorefresh' => 1, 'type' => 'rpm-md', 'metadata_expire' => 900}
531 repoattr.update(cfg[ali])
532 if repoattr['type'] == 'rpm-md'
533 repo = Repo_rpmmd.new(ali, 'repomd', repoattr)
534 elsif repoattr['type'] == 'yast2'
535 repo = Repo_susetags.new(ali, 'susetags', repoattr)
537 repo = Repo_unknown.new(ali, 'unknown', repoattr)
544 pool = Solv::Pool.new()
547 pool.set_loadcallback { |repodata|
548 repo = repodata.repo.appdata
549 repo ? repo.load_ext(repodata) : false
552 sysrepo = Repo_system.new('@System', 'system')
555 repo.load(pool) if repo.enabled?
559 pool.createwhatprovides()
561 for di in pool.Dataiterator(Solv::SOLVABLE_NAME, args[0], Solv::Dataiterator::SEARCH_SUBSTRING | Solv::Dataiterator::SEARCH_NOCASE)
562 sel.add_raw(Solv::Job::SOLVER_SOLVABLE, di.solvid)
564 for s in sel.solvables
565 puts "- #{s.str} [#{s.repo.name}]: #{s.lookup_str(Solv::SOLVABLE_SUMMARY)}"
570 abort("unknown command '#{cmd}'\n") unless cmdactionmap.has_key?(cmd)
572 addedprovides = pool.addfileprovides_queue()
573 if !addedprovides.empty?
574 sysrepo.updateaddedprovides(addedprovides)
576 repo.updateaddedprovides(addedprovides)
579 pool.createwhatprovides()
583 flags = Solv::Selection::SELECTION_NAME | Solv::Selection::SELECTION_PROVIDES | Solv::Selection::SELECTION_GLOB
584 flags |= Solv::Selection::SELECTION_CANON | Solv::Selection::SELECTION_DOTARCH | Solv::Selection::SELECTION_REL
586 flags |= Solv::Selection::SELECTION_FILELIST
587 flags |= Solv::Selection::SELECTION_INSTALLED_ONLY if cmd == 'erase'
589 sel = pool.select(arg, flags)
591 sel = pool.select(arg, flags | Solv::Selection::SELECTION_NOCASE)
592 puts "[ignoring case for '#{arg}']" unless sel.isempty?
594 puts "[using file list match for '#{arg}']" if sel.flags & Solv::Selection::SELECTION_FILELIST != 0
595 puts "[using capability match for '#{arg}']" if sel.flags & Solv::Selection::SELECTION_PROVIDES != 0
596 jobs += sel.jobs(cmdactionmap[cmd])
599 if jobs.empty? && (cmd == 'up' || cmd == 'dup' || cmd == 'verify')
600 sel = pool.Selection_all()
601 jobs += sel.jobs(cmdactionmap[cmd])
604 abort("no package matched.") if jobs.empty?
606 if cmd == 'list' || cmd == 'info'
608 for s in job.solvables()
610 puts "Name: #{s.str}"
611 puts "Repo: #{s.repo.name}"
612 puts "Summary: #{s.lookup_str(Solv::SOLVABLE_SUMMARY)}"
613 str = s.lookup_str(Solv::SOLVABLE_URL)
614 puts "Url: #{str}" if str
615 str = s.lookup_str(Solv::SOLVABLE_LICENSE)
616 puts "License: #{str}" if str
617 puts "Description:\n#{s.lookup_str(Solv::SOLVABLE_DESCRIPTION)}"
620 puts " - #{s.str} [#{s.repo.name}]"
621 puts " #{s.lookup_str(Solv::SOLVABLE_SUMMARY)}"
629 job.how ^= Solv::Job::SOLVER_UPDATE ^ Solv::Job::SOLVER_INSTALL if cmd == 'up' and job.isemptyupdate?
633 solver.set_flag(Solv::Solver::SOLVER_FLAG_SPLITPROVIDES, 1)
634 solver.set_flag(Solv::Solver::SOLVER_FLAG_ALLOW_UNINSTALL, 1) if cmd == 'erase'
635 #pool.set_debuglevel(1)
638 problems = solver.solve(jobs)
639 break if problems.empty?
640 for problem in problems
641 puts "Problem #{problem.id}/#{problems.count}:"
643 solutions = problem.solutions
644 for solution in solutions
645 puts " Solution #{solution.id}:"
646 elements = solution.elements(true)
647 for element in elements
648 puts " - #{element.str}"
654 print "Please choose a solution: "
656 sol = STDIN.gets.strip
657 break if sol == 's' || sol == 'q'
658 break if sol =~ /^\d+$/ && sol.to_i >= 1 && sol.to_i <= solutions.length
662 solution = solutions[sol.to_i - 1]
663 for element in solution.elements
664 newjob = element.Job()
665 if element.type == Solv::Solver::SOLVER_SOLUTION_JOB
666 jobs[element.jobidx] = newjob
668 jobs.push(newjob) if newjob && !jobs.include?(newjob)
674 trans = solver.transaction
677 puts "Nothing to do."
681 puts "\nTransaction summary:\n"
682 for cl in trans.classify(Solv::Transaction::SOLVER_TRANSACTION_SHOW_OBSOLETES | Solv::Transaction::SOLVER_TRANSACTION_OBSOLETE_IS_UPGRADE)
683 if cl.type == Solv::Transaction::SOLVER_TRANSACTION_ERASE
684 puts "#{cl.count} erased packages:"
685 elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_INSTALL
686 puts "#{cl.count} installed packages:"
687 elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_REINSTALLED
688 puts "#{cl.count} reinstalled packages:"
689 elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_DOWNGRADED
690 puts "#{cl.count} downgraded packages:"
691 elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_CHANGED
692 puts "#{cl.count} changed packages:"
693 elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_UPGRADED
694 puts "#{cl.count} upgraded packages:"
695 elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_VENDORCHANGE
696 puts "#{cl.count} vendor changes from '#{cl.fromstr}' to '#{cl.tostr}':"
697 elsif cl.type == Solv::Transaction::SOLVER_TRANSACTION_ARCHCHANGE
698 puts "#{cl.count} arch changes from '#{cl.fromstr}' to '#{cl.tostr}':"
702 for p in cl.solvables
703 if cl.type == Solv::Transaction::SOLVER_TRANSACTION_UPGRADED || cl.type == Solv::Transaction::SOLVER_TRANSACTION_DOWNGRADED
704 puts " - #{p.str} -> #{trans.othersolvable(p).str}"
711 puts "install size change: #{trans.calc_installsizechange()} K\n\n"
714 print("OK to continue (y/n)? ")
716 yn = STDIN.gets.strip
718 abort if yn == 'n' || yn == 'q'
721 newpkgs = trans.newsolvables()
726 downloadsize += p.lookup_num(Solv::SOLVABLE_DOWNLOADSIZE)
728 puts "Downloading #{newpkgs.length} packages, #{downloadsize / 1024} K"
730 repo = p.repo.appdata
731 location, medianr = p.lookup_location()
733 location = repo.packagespath + location
734 chksum = p.lookup_checksum(Solv::SOLVABLE_CHECKSUM)
735 f = repo.download(location, false, chksum)
736 abort("\n#{@name}: #{location} not found in repository\n") unless f
744 puts "Committing transaction:"
748 steptype = trans.steptype(p, Solv::Transaction::SOLVER_TRANSACTION_RPM_ONLY)
749 if steptype == Solv::Transaction::SOLVER_TRANSACTION_ERASE
750 puts "erase #{p.str}"
751 next unless p.lookup_num(Solv::RPM_RPMDBID)
752 evr = p.evr.sub(/^[0-9]+:/, '')
753 system('rpm', '-e', '--nodeps', '--nodigest', '--nosignature', "#{p.name}-#{evr}.#{p.arch}") || abort("rpm failed: #{$? >> 8}")
754 elsif (steptype == Solv::Transaction::SOLVER_TRANSACTION_INSTALL || steptype == Solv::Transaction::SOLVER_TRANSACTION_MULTIINSTALL)
755 puts "install #{p.str}"
756 f = newpkgsfp.delete(p.id)
758 mode = steptype == Solv::Transaction::SOLVER_TRANSACTION_INSTALL ? '-U' : '-i'
760 system('rpm', mode, '--force', '--nodeps', '--nodigest', '--nosignature', "/dev/fd/#{f.fileno().to_s}") || abort("rpm failed: #{$? >> 8}")