Blob Blame History Raw
#!/usr/bin/tclsh

package require solv
package require inifile
package require fileutil

set reposdir /etc/zypp/repos.d

### some helpers

proc fileno {file} {
  if [regexp -- {^file(\d+)$} $file match fd] {
    return $fd
  }
  error "file not open"
}

set ::globalarray_cnt 0

proc globalarray {} {
  set name "::globalarray_[incr ::globalarray_cnt]"
  array set $name [list varName $name]
  return $name
}

### generic repo handling (cache reading/writing)

proc repo_calc_cookie_file {selfName filename} {
  upvar $selfName self
  set chksum [solv::new_Chksum $solv::REPOKEY_TYPE_SHA256]
  $chksum add "1.1"
  $chksum add_stat $filename
  return [$chksum raw]
}

proc repo_calc_cookie_fp {selfName fp} {
  upvar $selfName self
  set chksum [solv::new_Chksum $solv::REPOKEY_TYPE_SHA256]
  $chksum add "1.1"
  $chksum add_fp $fp
  return [$chksum raw]
}

proc repo_calc_cookie_ext {selfName f cookie} {
  upvar $selfName self
  set chksum [solv::new_Chksum $solv::REPOKEY_TYPE_SHA256]
  $chksum add "1.1"
  $chksum add $cookie
  $chksum add_fstat [$f fileno]
  return [$chksum raw]
}

proc repo_cachepath {selfName {ext "-"}} {
  upvar $selfName self
  regsub {^\.} $self(name) _ path
  if {$ext ne "-"} {
    set path "${path}_$ext.solvx"
  } else {
    set path "${path}.solv"
  }
  regsub -all / $path _ path
  return "/var/cache/solv/$path"
}

proc repo_generic_load {selfName pool} {
  upvar $selfName self
  set handle [ $pool add_repo $self(name) ]
  set self(handle) $handle
  $handle configure -priority [expr 99 - $self(priority)] -appdata $self(varName)
  set dorefresh $self(autorefresh)
  set metadata_expire $self(metadata_expire)
  catch {
    if {$metadata_expire == -1 || [clock seconds] - [file mtime [repo_cachepath self]] < $metadata_expire} {
      set dorefresh 0
    }
  }
  set self(cookie) {}
  set self(extcookie) {}
  if { !$dorefresh && [repo_usecachedrepo self] } {
    puts "repo $self(name): cached"
    return 1 
  }
  return 0 
}

proc repo_free_handle {selfName} {
  upvar $selfName self
  set handle $self(handle)
  unset self(handle)
  $handle free 1
}

proc repo_usecachedrepo {selfName {ext "-"} {mark 0}} {
  upvar $selfName self
  set repopath [repo_cachepath self $ext]
  set code [catch {
    set f [open $repopath "rb"]
    seek $f -32 end
    set fcookie [read $f 32]
    set cookie [expr {$ext eq "-" ? $self(cookie) : $self(extcookie)}]
    if {$cookie ne {} && $cookie ne $fcookie} {
      close $f
      return 0
    }
    set fextcookie {}
    if {$ext eq "-" && $self(type) ne "system"} {
      seek $f -64 end
      set fextcookie [read $f 32]
    }
    seek $f 0 start
    set ff [solv::xfopen_fd {} [fileno $f]]
    close $f
    set flags 0
    if {$ext ne "-"} {
      set flags [expr $solv::Repo_REPO_USE_LOADING | $solv::Repo_REPO_EXTEND_SOLVABLES]
      if {$ext ne "DL"} {
	set flags [expr $flags | $solv::Repo_REPO_LOCALPOOL]
      }
    }
    if {! [$self(handle) add_solv $ff $flags]} {
      $ff close
      return 0
    }
    $ff close
    if {$self(type) ne "system" && $ext eq "-"} {
      set self(cookie) $fcookie
      set self(extcookie) $fextcookie
    }
    if {$mark} {
      catch {
        ::fileutil::touch -c -m -t [clock seconds] $repopath
      }
    }
    return 1
  } res]
  return [expr {$code == 2 ? $res : 0}]
}

proc repo_writecachedrepo {selfName {ext "-"} {repodata "NULL"}} {
  upvar $selfName self
  if [info exists self(incomplete)] {
    return
  }
  file mkdir "/var/cache/solv"
  ::fileutil::tempdir "/var/cache/solv"
  set tempfilename [::fileutil::tempfile ".newsolv-"]
  ::fileutil::tempdirReset
  set f [solv::xfopen $tempfilename "w+"]
  file attributes $tempfilename -permissions 0444
  if {$repodata eq {NULL}} {
    $self(handle) write $f
  } else {
    $repodata write $f
  }
  $f flush
  if {$self(type) ne "system" && $ext eq "-"} {
    if {$self(extcookie) eq {}} {
      set self(extcookie) [repo_calc_cookie_ext self $f $self(cookie)]
    }
    $f write $self(extcookie)
  }
  $f write [expr {$ext eq "-" ? $self(cookie) : $self(extcookie)}]
  $f close
  file rename -force -- $tempfilename [repo_cachepath self $ext]
}

proc repo_download {selfName file uncompress chksum {markincomplete 0}} {
  upvar $selfName self
  regsub {/$} $self(baseurl) {} url
  set url "$url/$file"
  set tempfilename [::fileutil::tempfile]
  set f [open $tempfilename rb+]
  file delete -- $tempfilename
  if [catch {
    exec -ignorestderr -- curl -f -s -L $url ">@$f"
  }] {
    seek $f 0 end
    if {($chksum ne "" && $chksum ne "NULL") || [tell $f] != 0} {
      puts "$file: download error"
    }
    close $f
    return {NULL}
  }
  seek $f 0 start
  if {$chksum ne "" && $chksum ne "NULL"} {
    set fchksum [solv::new_Chksum [$chksum cget -type]]
    if {$fchksum eq "" || $fchksum eq "NULL"} {
      puts "$file: unknown checksum type"
      if {$markincomplete} {
	set self(incomplete) 1
      }
      close $f
      return {NULL}
    }
    $fchksum add_fd [fileno $f]
    if {[$fchksum != $chksum]} {
      puts "$file: checksum mismatch"
      if {$markincomplete} {
	set self(incomplete) 1
      }
      close $f
      return {NULL}
    }
  }
  set ff [solv::xfopen_fd [expr {$uncompress ? $file : ""}] [fileno $f]]
  close $f
  return $ff
}

proc repo_generic_add_ext_keys {selfName ext repodata h} {
  upvar $selfName self
  if {$ext eq "DL"} {
    $repodata add_idarray $h $solv::REPOSITORY_KEYS $solv::REPOSITORY_DELTAINFO
    $repodata add_idarray $h $solv::REPOSITORY_KEYS $solv::REPOKEY_TYPE_FLEXARRAY
  } elseif {$ext eq "DU"} {
    $repodata add_idarray $h $solv::REPOSITORY_KEYS $solv::SOLVABLE_DISKUSAGE
    $repodata add_idarray $h $solv::REPOSITORY_KEYS $solv::REPOKEY_TYPE_DIRNUMNUMARRAY
  } elseif {$ext eq "FL"} {
    $repodata add_idarray $h $solv::REPOSITORY_KEYS $solv::SOLVABLE_FILELIST
    $repodata add_idarray $h $solv::REPOSITORY_KEYS $solv::REPOKEY_TYPE_DIRSTRARRAY
  }
}

### system

proc repo_system_load {selfName pool} {
  upvar $selfName self
  set handle [ $pool add_repo $self(name) ]
  set self(handle) $handle
  $handle configure -appdata $self(varName)
  $pool configure -installed $handle
  puts -nonewline "rpm database: "
  set self(cookie) [repo_calc_cookie_file self "/var/lib/rpm/Packages"]
  if [repo_usecachedrepo self] {
    puts "cached"
    return 1
  }
  puts "reading"
  set f [solv::xfopen [repo_cachepath self]]
  $handle add_rpmdb_reffp $f $solv::Repo_REPO_REUSE_REPODATA
  repo_writecachedrepo self
}

### repomd

proc repo_repomd_add_ext {selfName repodata what ext} {
  upvar $selfName self
  set where [repo_repomd_find self $what]
  if {$where eq {}} {
    return
  }
  set h [$repodata new_handle]
  $repodata set_poolstr $h $solv::REPOSITORY_REPOMD_TYPE $what
  $repodata set_str $h $solv::REPOSITORY_REPOMD_LOCATION [lindex $where 0]
  $repodata set_checksum $h $solv::REPOSITORY_REPOMD_CHECKSUM [lindex $where 1]
  repo_generic_add_ext_keys self $ext $repodata $h
  $repodata add_flexarray $solv::SOLVID_META $solv::REPOSITORY_EXTERNAL $h
}

proc repo_repomd_add_exts {selfName} {
  upvar $selfName self
  set repodata [$self(handle) add_repodata 0]
  $repodata extend_to_repo
  repo_repomd_add_ext self $repodata "filelists" "FL"
  $repodata internalize
}

proc repo_repomd_find {selfName what} {
  upvar $selfName self
  set di [$self(handle) Dataiterator_meta $solv::REPOSITORY_REPOMD_TYPE $what $solv::Dataiterator_SEARCH_STRING]
  $di prepend_keyname $solv::REPOSITORY_REPOMD
  solv::iter d $di {
    set dp [$d parentpos]
    set filename [$dp lookup_str $solv::REPOSITORY_REPOMD_LOCATION]
    set checksum [$dp lookup_checksum $solv::REPOSITORY_REPOMD_CHECKSUM]
    if {$filename ne "" && $checksum eq "NULL"} {
      puts "no $filename file checksum"
    } elseif {$filename ne ""} {
      return [list $filename $checksum]
    }
  }
  return {}
}

proc repo_repomd_load {selfName pool} {
  upvar $selfName self
  if [repo_generic_load self $pool] {
    return 1
  }
  puts -nonewline "rpmmd repo '$self(name)': "
  set f [repo_download self {repodata/repomd.xml} 0 {}]
  if {$f eq {NULL}} {
    puts "no repomd.xml file, skipped"
    repo_free_handle self
    return 0
  }
  set self(cookie) [repo_calc_cookie_fp self $f]
  if [repo_usecachedrepo self "-" 1] {
    puts "cached"
    return 1
  }
  set handle $self(handle)
  $handle add_repomdxml $f
  puts "fetching"
  set primary [repo_repomd_find self primary]
  if {$primary ne {}} {
    set f [repo_download self [lindex $primary 0] 1 [lindex $primary 1] 1]
    if {$f ne {NULL}} {
      $handle add_rpmmd $f {}
      $f close
    }
    if [info exists self(incomplete)] {
      return 0
    }
  }
  set updateinfo [repo_repomd_find self primary]
  if {$updateinfo ne {}} {
    set f [repo_download self [lindex $updateinfo  0] 1 [lindex $updateinfo 1] 1]
    if {$f ne {NULL}} {
      $handle add_updateinfoxml $f
      $f close
    }
  }
  repo_repomd_add_exts self
  repo_writecachedrepo self
  $self(handle) create_stubs
  return 1
}

proc repo_repomd_packagespath {selfName} {
  return ""
}

proc repo_repomd_load_ext {selfName repodata} {
  upvar $selfName self
  switch -- [$repodata lookup_str $solv::SOLVID_META $solv::REPOSITORY_REPOMD_TYPE] {
    "deltainfo" {
      set ext DL
    }
    "filelists" {
      set ext FL
    }
    default {
      return 0
    }
  }
  puts -nonewline "\[$self(name):$ext: "
  flush stdout
  if [repo_usecachedrepo self $ext] {
    puts "cached]"
    return 1
  }
  puts "fetching]"
  set handle $self(handle)
  set filename [$repodata lookup_str $solv::SOLVID_META $solv::REPOSITORY_REPOMD_LOCATION]
  set filechecksum [$repodata lookup_checksum $solv::SOLVID_META $solv::REPOSITORY_REPOMD_CHECKSUM]
  set f [repo_download self $filename 1 $filechecksum]
  if {$f eq {NULL}} {
    return 0
  }
  if {$ext eq "FL"} {
    $handle add_rpmmd $f "FL" [ expr $solv::Repo_REPO_USE_LOADING | $solv::Repo_REPO_EXTEND_SOLVABLES | $solv::Repo_REPO_LOCALPOOL]
  }
  $f close
  repo_writecachedrepo self $ext $repodata
  return 1
}

### susetags

proc repo_susetags_add_ext {selfName repodata what ext} {
  upvar $selfName self
  set where [repo_susetags_find self $what]
  if {$where eq {}} {
    return
  }
  set h [$repodata new_handle]
  $repodata set_str $h $solv::SUSETAGS_FILE_NAME [lindex $where 0]
  $repodata set_checksum $h $solv::SUSETAGS_FILE_CHECKSUM [lindex $where 1]
  repo_generic_add_ext_keys self $ext $repodata $h
  $repodata add_flexarray $solv::SOLVID_META $solv::REPOSITORY_EXTERNAL $h
}

proc repo_susetags_add_exts {selfName} {
  upvar $selfName self
  set repodata [$self(handle) add_repodata 0]
  $repodata extend_to_repo
  repo_susetags_add_ext self $repodata "packages.FL" "FL"
  repo_susetags_add_ext self $repodata "packages.FL.gz" "FL"
  $repodata internalize
}

proc repo_susetags_find {selfName what} {
  upvar $selfName self
  set di [$self(handle) Dataiterator_meta $solv::SUSETAGS_FILE_NAME $what $solv::Dataiterator_SEARCH_STRING]
  $di prepend_keyname $solv::SUSETAGS_FILE
  solv::iter d $di {
    set dp [$d parentpos]
    set checksum [$dp lookup_checksum $solv::SUSETAGS_FILE_CHECKSUM]
    return [list $what $checksum]
  }
  return {}
}

proc repo_susetags_load {selfName pool} {
  upvar $selfName self
  if [repo_generic_load self $pool] {
    return 1
  }
  puts -nonewline "susetags repo '$self(name)': "
  set f [repo_download self {content} 0 {}]
  if {$f eq {NULL}} {
    puts "no content file, skipped"
    repo_free_handle self
    return 0
  }
  set self(cookie) [repo_calc_cookie_fp self $f]
  if [repo_usecachedrepo self "-" 1] {
    puts "cached"
    return 1
  }
  set handle $self(handle)
  $handle add_content $f
  puts "fetching"
  set defvendorid [[$handle cget -meta] lookup_id $solv::SUSETAGS_DEFAULTVENDOR]
  set descrdir [[$handle cget -meta] lookup_str $solv::SUSETAGS_DESCRDIR]
  if {$descrdir eq {NULL}} {
    set descrdir "suse/setup/descr"
  }
  set packages [repo_susetags_find self "packages.gz"]
  if {$packages eq {}} {
    set packages [repo_susetags_find self "packages"]
  }
  if {$packages ne {}} {
    set f [repo_download self "$descrdir/[lindex $packages 0]" 1 [lindex $packages 1] 1]
    if {$f ne {NULL}} {
      $handle add_susetags $f $defvendorid {} [expr $solv::Repo_REPO_NO_INTERNALIZE | $solv::Repo_SUSETAGS_RECORD_SHARES]
      $f close
      set packages [repo_susetags_find self "packages.en.gz"]
      if {$packages eq {}} {
	set packages [repo_susetags_find self "packages.en"]
      }
      if {$packages ne {}} {
	set f [repo_download self "$descrdir/[lindex $packages 0]" 1 [lindex $packages 1] 1]
	if {$f ne {NULL}} {
          $handle add_susetags $f $defvendorid {} [expr $solv::Repo_REPO_NO_INTERNALIZE | $solv::Repo_REPO_REUSE_REPODATA | $solv::Repo_REPO_EXTEND_SOLVABLES ]
	  $f close
	}
      }
      $handle internalize
    }
  }
  repo_susetags_add_exts self
  repo_writecachedrepo self
  $self(handle) create_stubs
  return 1
}

proc repo_susetags_packagespath {selfName} {
  upvar $selfName self
  set datadir [[$self(handle) cget -meta] lookup_str $solv::SUSETAGS_DATADIR]
  return [expr {$datadir ne {} ? "$datadir/" : "suse/"}]
}

proc repo_susetags_load_ext {selfName repodata} {
  upvar $selfName self
  set filename [$repodata lookup_str $solv::SOLVID_META $solv::SUSETAGS_FILE_NAME]
  set ext [string range $filename 9 10]
  puts -nonewline "\[$self(name):$ext: "
  flush stdout
  if [repo_usecachedrepo self $ext] {
    puts "cached]"
    return 1
  }
  puts "fetching]"
  set handle $self(handle)
  set defvendorid [[$handle cget -meta] lookup_id $solv::SUSETAGS_DEFAULTVENDOR]
  set descrdir [[$handle cget -meta] lookup_str $solv::SUSETAGS_DESCRDIR]
  if {$descrdir eq {NULL}} {
    set descrdir "suse/setup/descr"
  }
  set filechecksum [$repodata lookup_checksum $solv::SOLVID_META $solv::SUSETAGS_FILE_CHECKSUM]
  set f [repo_download self "$descrdir/$filename" 1 $filechecksum]
  if {$f eq {NULL}} {
    return 0
  }
  set flags [expr $solv::Repo_REPO_USE_LOADING | $solv::Repo_REPO_EXTEND_SOLVABLES]
  if {$ext ne "DL"} {
    set flags [expr $flags | $solv::Repo_REPO_LOCALPOOL]
  }
  $handle add_susetags $f $defvendorid $ext $flags
  $f close
  repo_writecachedrepo self $ext $repodata
  return 1
}

### unknown

proc repo_unknown_load {selfName pool} {
  upvar $selfName self
  puts "unsupported repo '$self(name)': skipped"
  return 0
}

### poor man's OO

proc repo_load {selfName pool} {
  upvar $selfName self
  "repo_$self(type)_load" self $pool
}

proc repo_packagespath {selfName} {
  upvar $selfName self
  "repo_$self(type)_packagespath" self
}

proc repo_load_ext {selfName repodata} {
  upvar $selfName self
  "repo_$self(type)_load_ext" self $repodata
}

###

proc load_stub {repodata} {
  set code [catch {
    upvar #0 [[$repodata cget -repo] cget -appdata] repo
    if [info exists repo(handle)] {
      return [repo_load_ext repo $repodata]
    }
    return 0
  } res]
  if {$code == 2} {
    return $res
  }
  puts stderr $res
  return 0
}

###

set repoNames {}
foreach reponame [lsort [glob -nocomplain -directory $reposdir *.repo]] {
  set ini [::ini::open $reponame r]
  foreach alias [::ini::sections $ini] {
    upvar #0 [globalarray] repo
    array set repo {enabled 0 priority 99 autorefresh 1 type rpm-md metadata_expire 900}
    array set repo [::ini::get $ini $alias]
    set repo(name) $alias
    switch -exact -- $repo(type) {
      rpm-md  { set repo(type) repomd }
      yast2   { set repo(type) susetags }
      default { set repo(type) unknown }
    }
    lappend repoNames $repo(varName)
  }
  ::ini::close $ini
}

set pool [solv::new_Pool]
$pool setarch
$pool set_loadcallback load_stub

upvar #0 [globalarray] sysrepo
array set sysrepo [list name {@System} type system]
repo_load sysrepo $pool

foreach repoName $repoNames {
  upvar 0 $repoName repo
  if {$repo(enabled)} {
    repo_load repo $pool
  }
}


set cmd [lindex $::argv 0]
set ::argv [lreplace $::argv 0 0]

array set cmdabbrev [ list \
  in install \
  rm erase \
  ls list \
  ve verify \
  se search \
]
if [info exists cmdabbrev($cmd)] {
  set cmd $cmdabbrev($cmd)
}

if {$cmd eq "search"} {
  set arg [lindex $::argv 0]
  $pool createwhatprovides
  set sel [$pool Selection]
  set di [$pool Dataiterator $solv::SOLVABLE_NAME $arg [ expr $solv::Dataiterator_SEARCH_SUBSTRING | $solv::Dataiterator_SEARCH_NOCASE ]]
  solv::iter d $di {
    $sel add_raw $solv::Job_SOLVER_SOLVABLE [$d cget -solvid]
  }
  foreach s [$sel solvables] {
    puts [format { - %s [%s]: %s} [$s str] [[$s cget -repo] cget -name] [$s lookup_str $solv::SOLVABLE_SUMMARY]]
  }
  exit
}

$pool addfileprovides
$pool createwhatprovides

array set cmdactionmap [ list \
  install $solv::Job_SOLVER_INSTALL \
  erase $solv::Job_SOLVER_ERASE \
  up $solv::Job_SOLVER_UPDATE \
  dup $solv::Job_SOLVER_DISTUPGRADE \
  verify $solv::Job_SOLVER_VERIFY \
  list 0 \
  info 0 \
]

set jobs {}
foreach arg $::argv {
  set flags [expr $solv::Selection_SELECTION_NAME | $solv::Selection_SELECTION_PROVIDES | $solv::Selection_SELECTION_GLOB | \
                  $solv::Selection_SELECTION_CANON | $solv::Selection_SELECTION_DOTARCH | $solv::Selection_SELECTION_REL ]
  switch -glob -- $arg {
    "/*" {
      set flags [expr $flags | $solv::Selection_SELECTION_FILELIST ]
      if {$cmd eq "erase"} {
        set flags [expr $flags | $solv::Selection_SELECTION_INSTALLED_ONLY ]
      }
    }
  }
  set sel [$pool select $arg $flags]
  if [$sel isempty] {
    set sel [$pool select $arg [expr $flags | $solv::Selection_SELECTION_NOCASE]]
    if {![$sel isempty]} {
      puts "\[ignoring case for '$arg']"
    }
  }
  if [$sel isempty] {
    puts "nothing matches '$arg'"
    exit 1
  }
  if {[$sel cget -flags] & $solv::Selection_SELECTION_FILELIST} {
    puts "\[using file list match for '$arg']"
  }
  if {[$sel cget -flags] & $solv::Selection_SELECTION_PROVIDES} {
    puts "\[using capability match for '$arg']"
  }
  lappend jobs {*}[$sel jobs $cmdactionmap($cmd)]
}

if {$jobs eq {} && ($cmd eq "up" || $cmd eq "dup" || $cmd eq "verify") } {
  set sel [$pool Selection_all]
  lappend jobs {*}[$sel jobs $cmdactionmap($cmd)]
}

if {$jobs eq {}} {
  puts "no package matched."
  exit 1
}

if {$cmd eq "list" || $cmd eq "info"} {
  foreach job $jobs {
    foreach s [$job solvables] {
      if {$cmd eq "info"} {
        puts [format {Name:        %s} [$s str]]
        puts [format {Repo:        %s} [[$s cget -repo] cget -name]]
        puts [format {Summary:     %s} [$s lookup_str $solv::SOLVABLE_SUMMARY]]
        set str [$s lookup_str $solv::SOLVABLE_URL]
	if {$str ne {}} {
          puts [format {Url:         %s} $str]
	}
        set str [$s lookup_str $solv::SOLVABLE_LICENSE]
	if {$str ne {}} {
          puts [format {License      %s} $str]
	}
        puts [format {Description: %s} [$s lookup_str $solv::SOLVABLE_DESCRIPTION]]
	puts {}
      } else {
        puts [format {  - %s [%s]} [$s str] [[$s cget -repo] cget -name]]
        puts [format {    %s} [$s lookup_str $solv::SOLVABLE_SUMMARY]]
      }
    }
  }
  exit
}

#$pool set_debuglevel 1
set solver [$pool Solver]
$solver set_flag $solv::Solver_SOLVER_FLAG_SPLITPROVIDES 1
if {$cmd eq "erase"} {
  $solver set_flag $solv::Solver_SOLVER_FLAG_ALLOW_UNINSTALL 1
}

set problems [$solver solve $jobs]
if {$problems ne {}} {
  set pcnt 1
  foreach problem $problems {
    puts [format {Problem %d/%d:} $pcnt [llength $problems]]
    puts [$problem str]
    incr pcnt
  }
  exit 1
}

set trans [$solver transaction]

if [$trans isempty] {
  puts "Nothing to do."
  exit
}

puts {}
puts "Transaction summary:"
puts {}
foreach cl [$trans classify [expr $solv::Transaction_SOLVER_TRANSACTION_SHOW_OBSOLETES | $solv::Transaction_SOLVER_TRANSACTION_OBSOLETE_IS_UPGRADE]] {
  switch -- [$cl cget -type] \
    $solv::Transaction_SOLVER_TRANSACTION_ERASE {
      puts [format {%d erased packages:} [$cl cget -count]]
    } \
    $solv::Transaction_SOLVER_TRANSACTION_INSTALL {
      puts [format {%d installed packages:} [$cl cget -count]]
    } \
    $solv::Transaction_SOLVER_TRANSACTION_REINSTALLED {
      puts [format {%d reinstalled packages:} [$cl cget -count]]
    } \
    $solv::Transaction_SOLVER_TRANSACTION_DOWNGRADED {
      puts [format {%d downgraded packages:} [$cl cget -count]]
    } \
    $solv::Transaction_SOLVER_TRANSACTION_CHANGED {
      puts [format {%d changed packages:} [$cl cget -count]]
    } \
    $solv::Transaction_SOLVER_TRANSACTION_UPGRADED {
      puts [format {%d upgraded packages:} [$cl cget -count]]
    } \
    $solv::Transaction_SOLVER_TRANSACTION_VENDORCHANGE {
      puts [format {%d vendor changes from '%s' to '%s':} [$cl cget -count] [$cl cget -fromstr] [$cl cget -tostr]]
    } \
    $solv::Transaction_SOLVER_TRANSACTION_ARCHCHANGE {
      puts [format {%d archchanges from '%s' to '%s':} [$cl cget -count] [$cl cget -fromstr] [$cl cget -tostr]]
    } \
    default continue
  foreach p [$cl solvables] {
    set cltype [$cl cget -type]
    if {$cltype == $solv::Transaction_SOLVER_TRANSACTION_UPGRADED || $cltype ==$solv::Transaction_SOLVER_TRANSACTION_DOWNGRADED} {
      set op [$trans othersolvable $p]
      puts [format {  - %s -> %s} [$p str] [$op str]]
    } else {
      puts [format {  - %s} [$p str]]
    }
  }
  puts {}
}
puts [format {install size change: %d K} [$trans calc_installsizechange]]
puts {}

while 1 {
  puts -nonewline "OK to continue (y/n)? "
  flush stdout
  set yn [gets stdin]
  if {$yn eq "y"} {
    break
  }
  if {$yn eq "n" || $yn eq "q"} {
    exit
  }
}

set newpkgs [$trans newsolvables]
array set newpkgs_f {}
if {$newpkgs ne {}} {
  set downloadsize 0
  foreach p $newpkgs {
    set downloadsize [expr $downloadsize + [$p lookup_num $solv::SOLVABLE_DOWNLOADSIZE]]
  }
  puts [format {Downloading %d packages, %d K} [llength $newpkgs] [expr $downloadsize / 1024]]
  foreach p $newpkgs {
    upvar #0 [[$p cget -repo] cget -appdata] repo
    set location [$p lookup_location]
    if {$location eq {}} {
      continue
    }
    set location "[repo_packagespath repo][lindex $location 0]"
    set checksum [$p lookup_checksum $solv::SOLVABLE_CHECKSUM]
    set f [repo_download repo $location 0 $checksum]
    set newpkgs_f([$p cget -id]) $f
    puts -nonewline "."
    flush stdout
  }
  puts {}
}

puts "Committing transaction:"
$trans order
foreach p [$trans steps] {
  set steptype [$trans steptype $p $solv::Transaction_SOLVER_TRANSACTION_RPM_ONLY]
  if {$steptype == $solv::Transaction_SOLVER_TRANSACTION_ERASE} {
    puts "erase [$p str]"
    regsub {^[0-9]+:} [$p cget -evr] {} nvr
    set nvr "[$p cget -name]-$nvr.[$p cget -arch]"
    exec -ignorestderr -- rpm -e --nodeps --nodigest --nosignature $nvr
  } elseif {$steptype == $solv::Transaction_SOLVER_TRANSACTION_INSTALL || $steptype == $solv::Transaction_SOLVER_TRANSACTION_MULTIINSTALL} {
    puts "install [$p str]"
    set f $newpkgs_f([$p cget -id])
    set mode [expr {$steptype == $solv::Transaction_SOLVER_TRANSACTION_INSTALL ? "-U" : "-i"}]
    $f cloexec 0
    exec -ignorestderr -- rpm $mode --force --nodeps --nodigest --nosignature "/dev/fd/[$f fileno]"
  }
}