#!/bin/sh # # apt-mtime-for - list available versions of packages with modification time # # $Id$ # first version - I have no idea how reliable this is! # NOTE: this isn't necessarily the repository it was actually installed from # (which information appears to be impossible to obtain on Ubuntu!) # the algorithm: # 1) run apt-cache policy # 2) extract each repository URL for the candidate version, # one per line with the space-separated fields # - package name # - package status (can be absent|newer|current) # - candidate version # - repository URL # 3) run apache-cache show , match the Version: and produce the Filename:, # 4) append it to each URL and fetch the remote HTTP mtime # 5) and insert it as the fourth field # # example output for 1): # # $ apt-cache policy ansible # ansible: # Installed: 2.8.10-1ppa~bionic # Candidate: 2.8.18-1ppa~bionic # Version table: # 2.8.18-1ppa~bionic 500 # 500 http://ppa.launchpad.net/ansible/ansible-2.8/ubuntu bionic/main amd64 Packages # 500 http://ppa.launchpad.net/ansible/ansible-2.8/ubuntu bionic/main i386 Packages # *** 2.8.10-1ppa~bionic 100 # 100 /var/lib/dpkg/status # 2.5.1+dfsg-1ubuntu0.1 500 # 500 http://nl.archive.ubuntu.com/ubuntu bionic-updates/universe amd64 Packages # 500 http://nl.archive.ubuntu.com/ubuntu bionic-updates/universe i386 Packages # 500 http://security.ubuntu.com/ubuntu bionic-security/universe amd64 Packages # 500 http://security.ubuntu.com/ubuntu bionic-security/universe i386 Packages # 2.5.1+dfsg-1 500 # 500 http://nl.archive.ubuntu.com/ubuntu bionic/universe amd64 Packages # 500 http://nl.archive.ubuntu.com/ubuntu bionic/universe i386 Packages Policy2UrlLines() { perl -e ' use strict; use warnings; my ($name, $installed, $candidate, $priority, @repourl); sub output_urls { @repourl or return; my $status = ($installed eq $candidate) ? "current" : ($installed eq "(none)") ? "absent" : "newer"; printf("%s %s %s %s\n", $name, $status, $candidate, $_) for @repourl } while (<>) { if (/^(\S+):/) { output_urls(); $name = $1; undef $_ for ($installed, $candidate, $priority, @repourl) } elsif (/^\s+Installed: (\S+)/) { $installed = $1 } elsif (/^\s+Candidate: (\S+)/) { $candidate = $1 } elsif (/^[*\s]+(\S+) (\d+)/) { undef($priority); if ($1 eq $candidate) { $priority = $2 } } elsif (m#^\s+(\d+) (\S+://\S+)#) { if ($priority // 0 == $1) { push(@repourl, $2) } } } output_urls() ' | perl -ne '$seen{$_}++ or print' # deduplicate } Show2Filenames() { perl -ne ' if (/^(\S+):\s+(\S+)/) { $prop{$1} = $2; if ($1 eq "Filename") { printf("%s %s %s\n", @prop{Package, Version, Filename}); undef %prop } } ' } UrlMtime() { for url in "$@" do wget -q -nd -S --spider "$url" 2>&1 | perl -lne '/^\s*Last-Modified:\s+(.*\S)/i and $mtime = $1; END {print $mtime}' | perl -MTime::Piece -lpe '$_ = Time::Piece->strptime($_, "%a, %d %b %Y %H:%M:%S GMT")->strftime("%FT%T")' done } for pkg in "$@" do apt-cache policy "$pkg" | Policy2UrlLines | while read name status version url do apt-cache show $name | Show2Filenames | while read name2 version2 filepath do if [ X"$version" = X"$version2" ] then echo $name2 $status $version `UrlMtime $url/$filepath` $url/$filepath fi done done done