Quick Apt Updates

Continuing my “Today’s del.icio.us links” variant in my usual indolent fashion, an interesting post from the other week was this one on making apt-get update a bit quicker and more pleasant. It’s based on these ideas from a while ago, but now it actually works. (Well, actually it worked years ago when Ben Bell first implemented something along these lines, but that’s been dead for years, afaik. There’s some more discussion in Bug#128818 if you’re obsessive about reading background information, which, of course, you’re not)

Anyway, on the client side, you can make use of the whole concept with two scripts: “untiffani” and “apt-qupdate”. Note that the scripts below obsolete the ones in the mail cited above, which will no longer work.

Here’s untiffani:

#!/bin/sh

file="$1"
index="$2"
patchurl="${3%/}"
patch=$(tempfile)

sizeof () {
 wc -c "$1" | sed 's/^ *//;s/ .*//'
}

c_sha1=""
c_size="-1"
cur_sha1 () {
  if [ "$c_sha1" = "" ]; then
    c_sha1=$(sha1sum < "$file" | cut -d\  -f1)
  fi
  echo $c_sha1
}
cur_size () {
  c2_size=$(sizeof "$file")
  if [ $c2_size != $c_size ]; then
    c_size=$c2_size
    c_sha1=""
  fi
  echo $c_size
}

patch_sha1_size () {
sed -n '/^SHA1-Patches:/,/^[^ ]/'"s/^ \([^ ]*\) *\([^ ]*\) $1\$/\1 \2/p" "$index"
}

sed -n '/^SHA1-History:/,/^[^ ]/s/^ / /p' "$index" |
  while read n_sha1 n_size n_patch; do
    echo "try: $n_patch"
    if [ $(cur_size) = "$n_size" ]; then
      if [ $(cur_sha1) = "$n_sha1" ]; then
        wget -q -O- "$patchurl/${n_patch}.gz" | zcat > $patch
        p_size=$(sizeof $patch)
        p_sha1=$(sha1sum < $patch | cut -d\  -f1)

        if [ "$p_sha1 $p_size" = "$(patch_sha1_size $n_patch)" ]; then
          echo "applying patch $n_patch"
          (cat $patch; echo "wq") | ed "$file" >/dev/null
          c_size=0
        fi
      fi
    fi
  done

rm -f "$patch"

And here’s apt-qupdate:

url=http://merkel.debian.org/~aba/debian
tmp=$(tempfile) || exit
cd /var/lib/apt/lists
for p in *_Packages *_Sources; do
  file=${p##*_}
  path=${p##*_dists_}
  if [ "$path" = "$p" ]; then continue; fi
  path=$(echo ${path%_*} | tr _ /)
  >"$tmp"
  url2="${url}/dists/${path}/${file}.diff"
  wget -q -O "$tmp" "${url2}/Index"
  if [ -s "$tmp" ]; then
    cp "$p" "${p}.bak"
    /usr/local/bin/untiffani "$p" "$tmp" "${url2}"
  fi
done
rm -f "$tmp"

apt-cache gencaches

If you install untiffani in /usr/local/bin, you can then use “apt-qupdate” in place of “apt-get update”, so that you only have to download the lines in Packages and Sources that actually changed. As you can probably guess by the url in apt-qupdate, it’s still unofficial, but with a little more testing it should be able to be integrated into both the archive and apt relatively soon.

Also potentially cool and interesting, in light of this thread is the possibility of doing the same thing for Contents.gz files (for those playing along at home, apt-get update updates Packages files, which just deal with, well, what packages are available and their relationships; Contents files gives you a list of every file in every Debian package. As such they’re rather large — over 100MB uncompressed). Anyway, if you’ve downloaded sid’s Contents-i386.gz to the current directory, and uncompressed it (as ./Contents-i386), you can update it with:

url=http://merkel.debian.org/~aba/debian
url2=$url/dists/sid/Contents-i386.diff
wget -O Contents-index $url2/Index
/usr/local/bin/untiffani ./Contents-i386 Contents-index $url2

That’s something like a 150kB download instead of an 8MB one, which is always nice.

If you really want to stress about the copyright of quick hacks like the above, you can consider them licensed under the GPLv2 or the terms of the BSD license distributed in base-files. The scripts are available for download rather than cut and paste, too: untiffani and apt-qupdate. Since you have to run these programs as root, probably worth eyeballing them for security issues before running them.

Leave a Reply