198 lines
5.9 KiB
Ruby
198 lines
5.9 KiB
Ruby
|
#! /usr/bin/ruby
|
||
|
|
||
|
# Copyright 2002 Neil Spring <nspring@cs.washington.edu>
|
||
|
# GPL
|
||
|
# report bugs to wmbiff-devel@lists.sourceforge.net
|
||
|
# or (preferred) use the debian BTS via 'reportbug'
|
||
|
|
||
|
# Based on security-update-check.py by Rob Bradford
|
||
|
|
||
|
require 'net/http'
|
||
|
|
||
|
#require 'profile'
|
||
|
|
||
|
# re-fetch interval - only bug the server once every hour.
|
||
|
# allows wmbiff to ask us often how many packages have been
|
||
|
# updated so that the number goes back to cyan (old) from
|
||
|
# yellow (new) quickly on upgrade.
|
||
|
|
||
|
# this still doesn't mean we grab the whole file. we get
|
||
|
# if-modified-since. it just means we don't connect to the
|
||
|
# server more often than this.
|
||
|
# 6 hours * 60 min/hour * 60 sec/min
|
||
|
Refetch_Interval_Sec = 6 * 60 * 60
|
||
|
|
||
|
# as an ordinary user, we store Packages in the home directory.
|
||
|
Cachedir = ENV['HOME'] + '/.wmbiff-sdr'
|
||
|
|
||
|
# look for updates from this server. This script is designed around
|
||
|
# (and simplified greatly by) using just a single server.
|
||
|
Server = 'security.debian.org'
|
||
|
|
||
|
# extend the Array class with a max method.
|
||
|
class Array
|
||
|
def inject(n)
|
||
|
each { |value| n = yield(n, value) }
|
||
|
n
|
||
|
end
|
||
|
def max
|
||
|
inject(0) { |n, value| ((n > value) ? n : value) }
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def debugmsg(str)
|
||
|
$stderr.puts str if($VERBOSE)
|
||
|
end
|
||
|
|
||
|
# to be reimplemented without execing touch.
|
||
|
def touch(filename)
|
||
|
debugmsg "touching #{filename}"
|
||
|
Kernel.system('/usr/bin/touch ' + filename)
|
||
|
end
|
||
|
|
||
|
# to be reimplemented without execing dpkg, though running
|
||
|
# dpkg excessively doesn't seem to be a bottleneck.
|
||
|
def version_a_gt_b(a, b)
|
||
|
cmd = "/usr/bin/dpkg --compare-versions %s le %s" % [ a, b ]
|
||
|
# $stderr.puts cmd
|
||
|
return (!Kernel.system(cmd))
|
||
|
end
|
||
|
|
||
|
# figure out which lists to check
|
||
|
# there can be many implementations of
|
||
|
# this behavior, this seemed simplest.
|
||
|
|
||
|
|
||
|
# we're going to make an array of arrays, for each package
|
||
|
# file, the url, the system's cache of the file, and a
|
||
|
# per-user cache of the file.
|
||
|
packagelists = Dir.glob("/var/lib/apt/lists/#{Server}*Packages").map { |pkgfile|
|
||
|
[ pkgfile.gsub(/.*#{Server}/, '').tr('_','/'), # the url path
|
||
|
pkgfile, # the system cache of the packages file. probably up-to-date.
|
||
|
# and finally, a user's cache of the page, if needed.
|
||
|
"%s/%s" % [ Cachedir, pkgfile.gsub(/.*#{Server}_/,'') ]
|
||
|
]
|
||
|
}
|
||
|
|
||
|
# we'll open a persistent session, but only if we need it.
|
||
|
session = nil
|
||
|
|
||
|
# update the user's cache if necessary.
|
||
|
packagelists.each { |urlpath, sc, uc|
|
||
|
sctime = File.stat(sc).mtime
|
||
|
cached_time =
|
||
|
if(test(?e, uc)) then
|
||
|
uctime = File.stat(uc).mtime
|
||
|
if ( uctime < sctime ) then
|
||
|
# we have a user cache, but it is older than the system cache
|
||
|
File.unlink(uc) # delete the obsolete user cache.
|
||
|
sctime
|
||
|
else
|
||
|
uctime
|
||
|
end
|
||
|
else
|
||
|
# the user cache doesn't exist, but we might have
|
||
|
# talked to the server recently.
|
||
|
if(test(?e, uc + '.stamp')) then
|
||
|
File.stat(uc + '.stamp').mtime
|
||
|
else
|
||
|
sctime
|
||
|
end
|
||
|
end
|
||
|
if(Time.now > cached_time + Refetch_Interval_Sec) then
|
||
|
debugmsg "fetching #{urlpath} %s > %s + %d" % [Time.now, cached_time, Refetch_Interval_Sec]
|
||
|
begin
|
||
|
if(session == nil) then
|
||
|
session = Net::HTTP.new(Server)
|
||
|
# session.set_pipe($stderr);
|
||
|
end
|
||
|
begin
|
||
|
# the warning with ruby1.8 on the following line
|
||
|
# has to do with the resp, data bit, which should
|
||
|
# eventually be replaced with (copied from the
|
||
|
# docs with the 1.8 net/http.rb)
|
||
|
# response = http.get('/index.html')
|
||
|
# puts response.body
|
||
|
resp, data = session.get(urlpath,
|
||
|
{ 'If-Modified-Since' =>
|
||
|
cached_time.strftime( "%a, %d %b %Y %H:%M:%S GMT" ) })
|
||
|
rescue SocketError => e
|
||
|
# if the net is down, we'll get this error; avoid printing a stack trace.
|
||
|
puts "XX old"
|
||
|
puts e
|
||
|
exit 1;
|
||
|
rescue Timeout::Error => e
|
||
|
# if the net is down, we might get this error instead.
|
||
|
# but there is no good reason to print the specific exception. (execution expired)
|
||
|
puts "XX old"
|
||
|
exit 1;
|
||
|
end
|
||
|
test(?e, Cachedir) or Dir.mkdir(Cachedir)
|
||
|
File.open(uc, 'w') { |o| o.puts data }
|
||
|
test(?e, uc + '.stamp') and File.unlink(uc + '.stamp') # we have a copy, don't need the stamp.
|
||
|
debugmsg "urlpath updated"
|
||
|
rescue Net::ProtoRetriableError => detail
|
||
|
head = detail.data
|
||
|
if head.code != "304"
|
||
|
raise "unexpected error occurred: " + detail
|
||
|
end
|
||
|
test(?e, Cachedir) or Dir.mkdir(Cachedir)
|
||
|
if(test(?e, uc)) then
|
||
|
touch(uc)
|
||
|
else
|
||
|
# we didn't get an update, but we don't have a cached
|
||
|
# copy in the user directory.
|
||
|
touch(uc + '.stamp')
|
||
|
end
|
||
|
end
|
||
|
else
|
||
|
debugmsg "skipping #{urlpath}"
|
||
|
end
|
||
|
}
|
||
|
|
||
|
available = Hash.new
|
||
|
package = nil
|
||
|
packagelists.each { |url, sc, uc|
|
||
|
File.open( (test(?e, uc)) ? uc : sc, 'r').each { |ln|
|
||
|
if(m = /^Package: (.*)/.match(ln)) then
|
||
|
package = m[1]
|
||
|
elsif(m = /^Version: (.*)/.match(ln)) then
|
||
|
available[package] = m[1]
|
||
|
end
|
||
|
}
|
||
|
}
|
||
|
|
||
|
installed = Hash.new
|
||
|
package = nil
|
||
|
isinstalled = false
|
||
|
File.open('/var/lib/dpkg/status').each { |ln|
|
||
|
if(m = /^Package: (.*)$/.match(ln)) then
|
||
|
package = m[1]
|
||
|
isinstalled = false # reset
|
||
|
elsif(m = /^Status: install ok installed/.match(ln)) then
|
||
|
isinstalled = true
|
||
|
elsif(m = /^Version: (.*)$/.match(ln)) then
|
||
|
isinstalled && installed[package] = m[1]
|
||
|
end
|
||
|
}
|
||
|
|
||
|
debugmsg "%d installed, %d available" % [ installed.length, available.length ]
|
||
|
|
||
|
updatedcount = 0
|
||
|
updated = Array.new
|
||
|
( installed.keys & available.keys ).each { |pkg|
|
||
|
if(version_a_gt_b(available[pkg], installed[pkg])) then
|
||
|
updatedcount += 1
|
||
|
updated.push(pkg + ": #{available[pkg]} > #{installed[pkg]}")
|
||
|
end
|
||
|
}
|
||
|
|
||
|
# we're done. output a count in the format expected by wmbiff.
|
||
|
if(updatedcount > 0) then
|
||
|
puts "%d new" % [ updatedcount ]
|
||
|
else
|
||
|
puts "%d old" % [ installed.length ]
|
||
|
end
|
||
|
|
||
|
puts updated.join("\n")
|