Search Results: "will"

16 March 2014

Benjamin Mako Hill: Community Data Science Workshops in Seattle

Photo from the Boston Python Workshop   a similar workshop run in Boston that has inspired and provided a template for the CDSW.

Photo from the Boston Python Workshop a similar workshop run in Boston that has inspired and provided a template for the CDSW.

On three Saturdays in April and May, I will be helping run three day-long project-based workshops at the University of Washington in Seattle. The workshops are for anyone interested in learning how to use programming and data science tools to ask and answer questions about online communities like Wikipedia, Twitter, free and open source software, and civic media. The workshops are for people with no previous programming experience and the goal is to bring together researchers as well as participants and leaders in online communities. The workshops will all be free of charge and open to the public given availability of space. Our goal is that, after the three workshops, participants will be able to use data to produce numbers, hypothesis tests, tables, and graphical visualizations to answer questions like: If you are interested in participating, fill out our registration form here. The deadline to register is Wednesday March 26th. We will let participants know if we have room for them by Saturday March 29th. Space is limited and will depend on how many mentors we can recruit for the sessions. If you already have experience with Python, please consider helping out at the sessions as a mentor. Being a mentor will involve working with participants and talking them through the challenges they encounter in programming. No special preparation is required. If you re interested, send me an email.

Russell Coker: BTRFS Status March 2014

I m currently using BTRFS on most systems that I can access easily. It s not nearly reliable enough that I want to install it on a server in another country or an embedded device that s only accessible via 3G, but for systems where I can access the console it s not doing too badly. Balancing and Space Allocation # btrfs filesystem df /
Data, single: total=103.97GiB, used=85.91GiB
System, DUP: total=32.00MiB, used=20.00KiB
Metadata, DUP: total=1.78GiB, used=1.31GiB
# df -h /
Filesystem Size Used Avail Use% Mounted on
/dev/disk/by-uuid/ac696117-473c-4945-a71e-917e09c6503c 108G 89G 19G 84% /
Currently there are still situations where it can run out of space and deadlock on freeing space. The above shows the output of the btrfs df command and the regular df command, I have about 106G of disk space used by data and metadata in BTRFS while df shows that the entire filesystem (IE the block device) is 108G. So if I use another 2G of data or metadata then the system is at risk of deadlocking. To avoid that happening I have to run btrfs balance start / to start a balance which defragments the space use and frees some blocks. Currently there is a bug in BTRFS (present in all Debian/Unstable kernels) which prevents a balance operation from completing when systemd is used in a default configuration (there s something about the way systemd accesses it s journal files that triggers a BTRFS bug). This is really inconvenient, particularly given that there s probably a strong correlation between people who use experimental filesystems and people who use experimental init programs. When you get to the stage of the filesystem being deadlocked you can sometimes recover by removing snapshots and sometimes by adding a new device to the filesystem (even a USB flash drive will do). But I once had a filesystem get into a state where there wasn t enough space to balance, add a device, or remove a snapshot so I had to do a backup/format/restore. Quota Groups Last time I asked the developers (a few weeks ago) they told me that quota groups aren t ready to use. They also said that they know about enough bugs that there s no benefit in testing that feature. Even people who want to report bugs in BTRFS shouldn t use quotas. Kernel Panics with Kmail I ve had three systems develop filesystem corruption on files related to Kmail (the email program from KDE). I suspect that Kmail is triggering a bug in BTRFS. On all three systems the filesystem developed corruption that persisted across a reboot. One of the three systems was fixed by deleting the file for the Outbox, the others are waiting for kernel 3.14 which is supposed to fix the bug that causes kernel panics when accessing the corrupted files in question. I don t know whether kernel 3.14 will fix the bug that caused the corruption in the first place. Conclusion As I don t use quotas BTRFS is working well for me on systems that have plenty of storage space and don t run Kmail. There are some systems running systemd where I plan to upgrade the kernel before all the filesystem space is allocated. One of my systems is currently running SysVinit so I can balance the filesystem. Apart from these issues BTRFS is working reasonably well for me. I haven t yet had it s filesystem checksums correct corrupted data from disk in any situation other than tests (I have had ZFS correct such an error, so hardware I use does benefit from this). I have restored data from BTRFS snapshots on many occasions, so that feature has been a major benefit for me. When I had a system with faulty RAM the internal checks in BTRFS alerted me to the problem and I didn t lose any data, the filesystem became read-only and I was able to copy everything off even though it was too corrupted for writes.

Gunnar Wolf: Trying to get Blender to run in the CuBox-i (armhf): Help debugging Python!

As I posted some weeks ago, I have been playing with my CuBox-i4Pro, a gorgeous little ARM machine by SolidRun, built around an iMX6 system-on-a-chip. My first stabs at using it resulted in my previous post on how to get a base, almost-clean Debian distribution to run (Almost? Yes, the kernel requires some patches not yet accepted upstream, so I'm still running with a patched 3.0.35-8 kernel). After writing this step by step instructions, I followed them and built images ready to dd to a SD card and start running (available at my people.debian.org space. Now, what to do with this little machine? My version is by no means a limited box: 4 ARM cores, 2GB RAM make a quite decent box. In my case, this little machine will most likely be a home storage server with little innovation. However, the little guy is a power server, at only 3W consumption. I wanted to test its capabilities to do some number crunching and aid some of my friends The obvious candidate is building a Blender render farm. Right, the machines might be quite underpowered, but they are cheap (and look gorgeous!), so at least it's worth playing a bit! Just as a data point, running on an old hard disk (and not on my very slow SD card), the little machine was able to compile the Blender sources into a Debian package in 89m13.537s, that is, 5353 seconds. According to the Debian build logs (yes, for a different version, I tried with the version in Wheezy and in a clean Wheezy system), the time it took to build on some other architectures' build daemons was 1886s on i386, 1098s on PowerPC, 2003s on AMD64, 11513s on MIPS and 27721 on ARMHF. That means, my little machine is quite slower than desktop systems, but not unbearably so. But sadly, I have hit a wall, and have been unable to do any further progress. Blender segfaults at startup under the Debian armhf architecture. I have submitted bug report #739194 about this, but have got no replies to it yet. I did get the great help from my friends in the OFTC #debian-arm channel, but they could only help up to a given point. It seems the problem lies in the Python interpreter in armhf, not in Blender itself... But I cannot get much further either. I'm sending this as a blog post to try to get more eyeballs on my problem How selfish, right? :-) So, slightly going over the bug report, blender just dies at startup:
  1. $ blender -b -noaudio
  2. Segmentation fault
After being told that strace is of little help when debugging this kind of issues, I went via gdb. A full backtrace pointed to what feels like the right error point:
  1. (gdb) bt full
  2. #0 0x2acd8cce in PyErr_SetObject ()
  3. from /usr/lib/arm-linux-gnueabihf/libpython3.3m.so.1.0
  4. No symbol table info available.
  5. #1 0x2acd8c9a in PyErr_Format ()
  6. from /usr/lib/arm-linux-gnueabihf/libpython3.3m.so.1.0
  7. No symbol table info available.
  8. #2 0x2ac8262c in PyType_Ready ()
  9. from /usr/lib/arm-linux-gnueabihf/libpython3.3m.so.1.0
  10. No symbol table info available.
  11. #3 0x2ac55052 in _PyExc_Init ()
  12. from /usr/lib/arm-linux-gnueabihf/libpython3.3m.so.1.0
  13. No symbol table info available.
  14. #4 0x2ace95e2 in _Py_InitializeEx_Private ()
  15. from /usr/lib/arm-linux-gnueabihf/libpython3.3m.so.1.0
  16. No symbol table info available.
  17. #5 0x00697898 in BPY_python_start (argc=3, argv=0x7efff8d4)
  18. at /build/blender-dPxPUD/blender-2.69/source/blender/python/intern/bpy_interface.c:274
  19. py_tstate = 0x0
  20. py_path_bundle = 0x0
  21. program_path_wchar = L"/usr/bin/blender", '\000' <repeats 1007 times>
  22. #6 0x0044a1ca in WM_init (C=C@entry=0x1d83220, argc=argc@entry=3,
  23. argv=argv@entry=0x7efff8d4)
  24. at /build/blender-dPxPUD/blender-2.69/source/blender/windowmanager/intern/wm_init_exit.c:176
  25. No locals.
  26. #7 0x0042e4de in main (argc=3, argv=0x7efff8d4)
  27. at /build/blender-dPxPUD/blender-2.69/source/creator/creator.c:1597
  28. C = 0x1d83220
  29. syshandle = 0x1d8a338
  30. ba = 0x1d8a840
  31. (gdb)
I'm not pasting here the full bug history (go to the bug report for the full information!), but it does point me to this being a problem in Python-land: It points to something not found at line 59 of Python/errors.c. And what I understand from that line is that some kind of unknown exception is thrown, and the Python interpreter does not now what to do with it. The check done at line 59 is the if (exception != NULL ** ....:
  1. void
  2. PyErr_SetObject(PyObject *exception, PyObject *value)
  3. PyThreadState *tstate = PyThreadState_GET();
  4. PyObject *exc_value;
  5. PyObject *tb = NULL;
  6. if (exception != NULL &&
  7. !PyExceptionClass_Check(exception))
  8. PyErr_Format(PyExc_SystemError,
  9. "exception %R not a BaseException subclass",
  10. exception);
  11. return;
So... Dear lazyweb: Any pointers on where to go on from here? kthxbye.

15 March 2014

Lars Wirzenius: Obnam 1.7 (backup software)

I have just released version 1.7 of Obnam, my backup program. Version 1.7, released 2014-03-15 WARNING: This release has had fairly large parts of the internals re-written. There shouldn't be any externally visible changes due to that, but there is a chance of bugs. Be careful. Make a copy of your backup repository before upgrading, if you can. Bug fixes: Internal changes:

Bits from Debian: Video streams for the MiniDebConf 2014 Barcelona

This is just a quick note to tell you that the video stream of the Barcelona MiniDebConf will be available at the following URL: http://bcn2014.video.debconf.org/ If you were not able to make it to Barcelona, now you can still follow from home! May you have a productive and joyful MiniDebConf - and thanks for volunteering and talking if you do so! The MiniDebConf is what you make it.

DebConf team: Barcelona MiniDebConf 2014 video streams (Posted by Mart n Ferrari and the DebConf videoteam)

This is just a quick note to tell you that the video stream of the Barcelona MiniDebConf will be available at the following URL: http://bcn2014.video.debconf.org/ If you were not able to make it to Barcelona, now you can still follow from home! May you have a productive and joyful MiniDebConf - and thanks for volunteering and talking if you do so! The MiniDebConf is what you make it.

14 March 2014

Richard Hartmann: Git prize: Outstanding Contribution to Open Source/Linux/Free Software

In February, Linux Magazine contacted me, asking if I would be willing to accept the Linux New Media Award 2014 in the main category "Outstanding Contribution to Open Source/Linux/Free Software" on behalf of the Git community due to my involvement with evangelizing and vcsh. Needless to say, I was thrilled. I managed to poke Junio via someone at Google and he agreed. We also reached out within the German Git community and two maintainers of git submodule, Jens Lehmann and Heiko Voigt, joined in as well. While we didn't manage to hammer out interoperability issues of vcsh and git submodule due to time constraints and too much beer, we are planning to follow up on that. Git beat OpenStack, Python, and Ubuntu by a huge margin; sadly I don't have exact numbers (yet). More details and a rather crummy photo can be found in Linux Magazine's article. A video of the whole thing will uploaded to this page soonish. If it appears that we kept our "speech" very short, that was deliberate after the somewhat prolonged speeches beforehand ;) The aftershow event was nice even though the DJ refused to turn down the music down to tolerable levels; his reaction to people moving father away, and asking him to turn down the volume a bit, was to turn it up... Anyway, given the mix of people present during the award ceremony, very interesting discussions ensued. While I failed to convert Klaus Knopper to zsh and git, at least there's a chance that Cornelius Schuhmacher will start using vcsh and maybe even push for a separation of configuration and state in KDE. The most interesting tidbits of the evening were shared by Abhisek Devkota of cyanogenmod fame. Without spilling any secrets it's safe to say that the future of cyanogenmod is looking extremely bright and that there are surprises in the works which will have quite the impact. Last but not least, here's the physical prize: Glass trophy held by your's truly

Jan Wagner: Chemnitzer Linuxtage 2014 ahead

As Jan has previously announced, the Debian project is maintaining a booth at Chemnitzer Linux-Tage 2014, which is also organized by him. This year we will have merchandising at the booth, which is provided by Alexander Wirt and of course a demo system with Debian wheezy BabelBox as the past years. I'll drop it tomorrow, as I have a conflicting appointment on Saterday, maybe I can attend later on Sunday. In case you have spare time at the weekend ahead, it may be worth to spend it with great lectures and meet nice people over there.

Neil Williams: LAVA server on an arndale

(ARMv7 A15 dual core) I ve covered how I set up my home LAVA lab and how I got two iMX.53 boards running, the next step was to sort out the LAVA server. My initial setup relied on using my Thinkpad as the server. This was somewhat convenient as that s how I develop the code, but didn t help me run jobs like health checks because the laptop was usually somewhere other than on the home LAN. There had been a long-standing problem with using LAVA on ARM the uwsgi support didn t compile but the code being used was old. As the same code has been in Debian for a long time, I suspected that this was fixed with a newer version. So as part of migrating LAVA to django1.6, apache2.4, postgres9.3, django-tables2 0.13 and a few other updated dependencies, I have been using the version of uwsgi from Debian unstable for quite a while without problems on x86. A simple tweak to the LAVA setup scripts and a newer version of uwsgi became available and it builds just fine on armhf. (I haven t tried armel because Linaro is ARMv7 or newer and we don t test with ARMv5/ARMv6 boards, so there s no point in trying armel it s not likely to be an issue for this fix though.) (Only the current lava-deployment-tool methods need to actually compile uwsgi directly from upstream without any patches the packaging which will replace l-d-t uses precompiled binaries, just as any admin would expect.) That initial install was on one of the iMX.53 boards, which ran as slowly as anyone who has used an iMX.53 would expect. I haven t run test jobs on that install, it had a hard enough time serving the frontend. Clearly some more cores would be a good step and when an arndale became available for the home lab, the next step was to get a SATA drive onto the arndale, put Debian unstable on that and then deploy LAVA to the arndale using the Debian packages. As a bonus, these packages are built for django1.6 (using an unreleased branch). Sounds simple enough. It works on iMX.53, it should work on an arndale. Well, it does but only after some unexpected faff. arndale, u-boot and SATA I hate u-boot every single time I ever touch it, the little I know about any other u-boot device is completely useless. Uniformity is a good thing infinite permutations are just rope to hang ourselves with cute embedded nonsense hacks and I m bored of that whole scene. So, boot the arndale, interrupt the bootloader to get to the prompt. What s the first thing most people would want? help. Denied this particular u-boot has been compiled without the ability to display any help messages. u-boot has just sunk further into the depths of please can we have something else which is uniform across devices . OK, well maybe I can get by without help. sata init unknown command sata . (Go away, do something else for a bit.) The arndale isn t one of the newest boards in the ARMv7 world, LAVA has been using them for a while, so someone has probably done some work to fix this . nope. There s a workaround a nice detailed guide by Phil Endecott which details that the kernel needs to continue to be loaded from SD and then the SATA can be used for just the rootfs. Mildly annoyed (I was hoping to be able to update the arndale kernel without the hassle of mounting the SD card at all), the rest was straight-forward. Preparing the SATA and installing Debian Due to the complexity of some of the transitions currently ongoing in Debian ahead of the Jessie release freeze, uwsgi is not in Debian testing right now so this install has to be Debian unstable. That s OK, I ve been running Debian unstable on all my machines for several years now usually updating ~5 times a week.

# parted /dev/sda -s unit mb mktable msdos
# parted /dev/sda -s unit mb mkpart primary 1 -- "-0"
# mkfs.ext4 /dev/sda1
# mount /dev/sda1 /mnt/root/
(note the in the mkpart command, that catches me out most times I try this. Without it, the special -0 gets interpreted as an option to parted but parted doesn t then tell you how to fix it, it just complains about an unknown option.) The SD image I am using is a LAVA master image, based on Ubuntu, so it was easy to put debootstrap onto it.

# apt-get install debootstrap
# debootstrap --arch=armhf sid /mnt/root/ http://ftp.uk.debian.org/debian
# chroot /mnt/root/
Inside the chroot, it s Debian as usual:

apt-get update
apt-get install emdebian-archive-keyring
passwd
emdebian-archive-keyring is a package I (ab)use to sign my LAVA packaging as it has the advantage of being in Debian and Ubuntu going back to the time of Lenny, so it is always available from a repository which has already been authenticated to apt and doesn t involve downloading some random key from a website. I use passwd manually because this rootfs won t be using auto-login, it will have regular users. root passwords can be automated, if necessary. Thinking about login, remember to give the rootfs a usable tty. (The LAVA master image has a nasty habit of mangling editors like nano and vi when used over serial, so I m using echo.) For the arndale you need:

# echo T0:23:respawn:/sbin/getty -L ttySAC2 115200 vt102 >> ./etc/inittab
# echo >> ./etc/securetty
# echo ttySAC2 >> ./etc/securetty
Then to make the hostname useful, adapt this to your needs:

echo 127.0.0.1 arndale.codehelp arndale >> ./etc/hosts
Finally, get the network running at boot and add the LAVA repository:

# echo auto lo eth0 > ./etc/network/interfaces.d/arndale
# echo iface lo inet loopback >> ./etc/network/interfaces.d/arndale
# echo iface eth0 inet dhcp >> ./etc/network/interfaces.d/arndale
# echo deb http://people.linaro.org/~neil.williams/lava sid main >> ./etc/apt/sources.list.d/lava.list
# apt-get update
# exit
Once the LAVA packaging is finalised, the packages will be uploaded to a normal Linaro PPA, I ll update the packages to use an official download location and we ll move to using sane version string handling. For now, the packages are still in development. Code for the packaging is in Alioth but there are issues with anonymous access to the pkg-linaro-lava repositories currently, so I ve also pushed to http://github.com/Linaro if you are interested in building the packages yourself. See also my packaging guide which is also linked from the Links page on my own site. With that done, time to reboot, set the SATA drive as the rootfs using the horrid u-boot and install LAVA.

# reboot
> setenv bootargs console=tty0 console=ttySAC2,115200n8 drm_kms_helper.edid_firmware=edid-1920x1080.fw root=/dev/sda1
> boot
login
# apt-get install postgresql
After this operation, 63.0 MB of additional disk space will be used.

# apt-get install lava-server
After this operation, 231 MB of additional disk space will be used.

# a2dissite 000-default
# a2ensite lava-server
# apache2ctl restart
# service lava-server restart
Take note of the IP address of your new ARM LAVA server and sign in with your regular details, e.g. using OpenID before creating a temporary superuser:

# lava-server manage createsuperuser --username default --email=user@example.com
Choose a temporary password, log out, log in as the temporary superuser, promote your regular user to superuser and default owner of unrestricted devices in the django admin interface. Finally, log out and login as regular user to delete the temporary user. (Yes, that probably could do with some improvement remind me later and I ll see if a known user can be promoted from the command line.) See also Senthil s recent post on LAVA unit tests. Getting those to run without so many steps is also on the TODO list. Results
IP Address: 127.0.0.1
Is Master? : True
Uptime: 6:02:23.700000
Architecture: armv7l
Platform: Linux-3.10.1.0-1-linaro-arndale-armv7l-with-debian-jessie-sid
Insignal Arndale evaluation board based on EXYNOS5250
System memory: 1998MiB
Next! I m working on some code upstream to make it easier to add devices from the command line. The aim is that a set of simple commands will set up an initial device, run a job on that device and submit a result bundle to the LAVA server as job #1. This gives some assurance that everything has gone well. Now I can finally add health checks for the iMX.53 boards and start regular tests on those boards. The cubie boards will wait for a later blog post, there are interesting problems with the bootloader and serial connections. That s a whole different rant.

Steve Kemp: So load-balancers are awesome

When I was recently talking about load-balancers, and automatically adding back-ends, not just removing bad ones that go offline, I obviously spent a while looking over some. There are several dedicated load-balancers packaged for Debian GNU/Linux, including: In addition to actual dedicated load-balancers there are things that can be coerced into running in that way: apache2, varnish, squid, nginx, & etc. Of the load-balancers I was immediately drawn to both pen and pound, because they have command line tools ("penctl" and "poundctl" respectively) for adding/removing/updating the running configuration. Pen I've been using for a couple of days now, and although it suffers from some security issues I'm confident they will be resolved in the near future. (#741370) My only outstanding task is to juggle some hosts around and stress-test the pair of them a little more before deciding on a winner. In other news I kinda regret the whole blogspam.net API. I'd have had a far simpler life if I'd just ran the damn thing as a DNSBL in the first place. (That's essentially how it operates on the whole anyway. Submit spammy comments for long enough and you're just blacklisted, thereafter.)

Dirk Eddelbuettel: Rcpp 0.11.1

A new minor release 0.11.1 of Rcpp is now on the CRAN network for GNU R; binaries for Debian have also been uploaded. The release fixes a number of bugs that have come up since the 0.11.0 release in January, but also brings some extensions. See the NEWS file section below for details, or the ChangeLog file in the package and on the Rcpp Changelog page Once again, we tested this release by building against all CRAN packages which depend upon Rcpp. In short, three packages are blacklisted from tests, and three came up with something we noted --- but the remaining 177 packages all build and test cleanly. Detailed results of those tests (and the scripts for it) are on GitHub. There are a number of other fixes, upgrades and other extensions detailed in NEWS file extract below, in the ChangeLog file in the package and on the Rcpp Changelog page.
Changes in Rcpp version 0.11.1 (2014-03-13)
  • Changes in Rcpp API:
    • Preserve backwards compatibility with Rcpp 0.10.* by allowing RObject extraction from vectors (or lists) of Rcpp objects
    • Add missing default constructor to Reference class that was omitted in the header-only rewrite
    • Fixes for NA and NaN handling of the IndexHash class, as well as the vector .sort() method. These fixes ensure that sugar functions depending on IndexHash (i.e. unique(), sort_unique(), match()) will now properly handle NA and NaN values for numeric vectors.
    • DataFrame::nrows now more accurately mimics R's internal behavior (checks the row.names attribute)
    • Numerous changes to permit compilation on the Solaris OS
    • Rcpp vectors gain a subsetting method it is now possible to subset an Rcpp vector using CharacterVectors (subset a by name), LogicalVectors (logical subsetting), and IntegerVectors (0-based index subsetting). Such subsetting will also work with Rcpp sugar expressions, enabling expressions such as x[ x > 0].
    • Comma initialization (e.g. CharacterVector x = "a", "b", "c";, has been disabled, as it causes problems with the behavior of the = operator with Rcpp::Lists. Users who want to re-enable this functionality can use #define RCPP_COMMA_INITIALIZATION, but be aware of the above caveat. The more verbose CharacterVector x = CharacterVector::create("a", "b", "c") is preferred.
  • Changes in Rcpp Attributes
    • Fix issue preventing packages with Rcpp::interfaces attribute from compiling.
    • Fix behavior with attributes parsing of ::create for default arguments, and also allow constructors of a given size (e.g. NumericVector v = NumericVector(10)) gives a default value of numeric(10) at the R level). Also make NAs preserve type when exported to R (e.g. NA_STRING as a default argument maps to NA_character_ at the R level)
  • Changes in Rcpp modules
    • Corrected the un_pointer implementation for object
Thanks to CRANberries, you can also look at a diff to the previous release. As always, even fuller details are on the Rcpp Changelog page and the Rcpp page which also leads to the downloads, the browseable doxygen docs and zip files of doxygen output for the standard formats. A local directory has source and documentation too. Questions, comments etc should go to the rcpp-devel mailing list off the R-Forge page

This post by Dirk Eddelbuettel originated on his Thinking inside the box blog. Please report excessive re-aggregation in third-party for-profit settings.

Petter Reinholdtsen: Freedombox on Dreamplug, Raspberry Pi and virtual x86 machine

The Freedombox project is working on providing the software and hardware for making it easy for non-technical people to host their data and communication at home, and being able to communicate with their friends and family encrypted and away from prying eyes. It has been going on for a while, and is slowly progressing towards a new test release (0.2). And what day could be better than the Pi day to announce that the new version will provide "hard drive" / SD card / USB stick images for Dreamplug, Raspberry Pi and VirtualBox (or any other virtualization system), and can also be installed using a Debian installer preseed file. The Debian based Freedombox is now based on Debian Jessie, where most of the needed packages used are already present. Only one, the freedombox-setup package, is missing. To try to build your own boot image to test the current status, fetch the freedom-maker scripts and build using vmdebootstrap with a user with sudo access to become root:
git clone http://anonscm.debian.org/git/freedombox/freedom-maker.git \
  freedom-maker
sudo apt-get install git vmdebootstrap mercurial python-docutils \
  mktorrent extlinux virtualbox qemu-user-static binfmt-support \
  u-boot-tools
make -C freedom-maker dreamplug-image raspberry-image virtualbox-image
Root access is needed to run debootstrap and mount loopback devices. See the README for more details on the build. If you do not want all three images, trim the make line. But note that thanks to a race condition in vmdebootstrap, the build might fail without the patch to the kpartx call. If you instead want to install using a Debian CD and the preseed method, boot a Debian Wheezy ISO and use this boot argument to load the preseed values:
url=http://www.reinholdtsen.name/freedombox/preseed-jessie.dat
But note that due to a recently introduced bug in apt in Jessie, the installer will currently hang while setting up APT sources. Killing the 'apt-cdrom ident' process when it hang a few times during the installation will get the installation going. This affect all installations in Jessie, and I expect it will be fixed soon. Give it a go and let us know how it goes on the mailing list, and help us get the new release published. :) Please join us on IRC (#freedombox on irc.debian.org) and the mailing list if you want to help make this vision come true.

13 March 2014

Mario Lang: Living in RAM

I switched to SSD on my home server a few years ago, at a time when SSH was still new and sort of hyped. However, I immediately knew that this type of technology is for me: it is fast and rather silent. SSD storage has this air of being fragile though. You are supposed to not wear it out too much. It's not like in the good old days where you would write to your hard disk without any extra thought.
tmpfs Before I switched to SSD I was already using tmpfs to store /tmp in RAM, for performance reasons mostly. /etc/default/tmpfs:
RAMTMP=yes
Since RAM is relatively cheap these days I have lots of it in my home server.
/var/cache/apt/archives When I switched to SSD, I extended this concept by mounting /var/cache/apt/archives via tmpfs. This is to save disk I/O for package updates. And for a machine running sid which is more or less regularily updated, this saves quite some useless writes to disk. Roughly 100MB per update.
/tmp/src Nowadays, I have almost all of my source code checkouts and build-trees in /tmp/src. myrepos makes this very easy to handle. Simply clone all repositories you are interested in, and remember their details with mr register. After a reboot, all you need to do is:
$ mkdir /tmp/src
$ cd !$
$ mr up
Your repository locations have (likely) been recorded in ~/.mrconfig. Of course there is the danger of accidentally loosing data due to a power outage. However, I feel this leads to a rather clean workflow. Changes need to be pushed anyway. So since you can not trust your local repository to really persist, you are forced to push your changes regularily, which is a good thing! Besides, I have a UPS at home which gives me roughly one hour of backup power. This was enough to sustain every power outage I have witnessed in the past years. Typically, outages are rather short lived in my area, somewhere between a few seconds and 5 minutes. It is rather rare that electricity goes missing for more than half an hour. The only downside of this approach is that from time to time, you will use more bandwidth by re-cloning repositories after a reboot. But really, who needs reboots on Linux? Last time I had half a year uptime, and only rebooted because I wanted to jump from linux 3.8 to 3.13. This approach only works on long-running machines though. It is probably not very useful for a laptop.

12 March 2014

Matthew Garrett: Dealing with Apple ACPI issues

I wrote about Thunderbolt on Apple hardware a while ago. Since then Andreas Noever has somehow managed to write a working Thunderbolt stack, which awesome! But there was still the problem I mentioned of the device not appearing unless you passed acpi_osi="Darwin" on the kernel command line, and a further problem that if you suspended and then resumed it vanished again.

The ACPI _OSI interface is a mechanism for the firmware to determine the OS that the system is running. It turns out that this works fine for operating systems that export fairly static interfaces (Windows, which adds a new _OSI per release) and poorly for operating systems that don't even guarantee any kind of interface stability in security releases (Linux, which claimed to be "Linux" regardless of version until we turned that off). OS X claims to be Darwin and nothing else. As I mentioned before, claiming to be Darwin in addition to Windows was enough to get the Thunderbolt hardware to stay alive after boot, but it wasn't enough to get it powered up again after suspend.

It turns out that there's two sections of ACPI code where this Mac checks _OSI. The first is something like:

if (_OSI("Darwin")) Store 0x2710 OSYS; else if(_OSI("Windows 2009") Store 0x7D9 OSYS; else

ie, if the OS claims to be Darwin, all other strings are ignored. This is called from \_SB._INI(), which is the first ACPI method the kernel executes. The check for whether to power down the Thunderbolt controller occurs after this and then works correctly.

The second version is less helpful. It's more like:

if (_OSI("Darwin")) Store 0x2710 OSYS; if (_OSI("Windows 2009")) Store 0x7D9 OSYS; if

ie, if the OS claims to be both Darwin and Windows 2009 (which Linux will if you pass acpi_osi="Darwin"), the OSYS variable gets set to the Windows 2009 value. This version gets called during PCI initialisation, and once it's run all the other Thunderbolt ACPI calls stop doing anything and the controller gets powered down after suspend/resume. That can be fixed easily enough by special casing Darwin. If the platform requests Darwin before anything else, we'll just stop claiming to be Windows.

Phew. Working Thunderbolt! (Well, almost - _OSC fails and so we disable PCIe hotplug, but that's easy to work around). But boo, no working battery. Apple do something very strange with their ACPI battery interface. If you're running anything that doesn't claim to be Darwin, Apple expose an ACPI Control Method battery. Control Method interfaces abstract the complexity away into either ACPI bytecode or system management traps - the OS simply calls an ACPI method, magic happens and it gets an answer back. If you claim to be Darwin, Apple remove that interface and instead expose the raw ACPI Smart Battery System interface. This provides an i2c bus over which the OS must then speak the Smart Battery System protocol, allowing it to directly communicate with the battery.

Linux has support for this, but it seems that this wasn't working so well and hadn't been for years. Loading the driver resulted in modprobe hanging until a timeout occurred, and most accesses to the battery would (a) take forever and (b) probably fail. It also had the nasty habit of breaking suspend and resume, which was unfortunate since getting Thunderbolt working over suspend and resume was the whole point of this exercise.

So. I modified the sbs driver to dump every command it sent over the i2c bus and every response it got. Pretty quickly I found that the failing operation was a write - specifically, a write used to select which battery should be connected to the bus. Interestingly, Apple implemented their Control Method interface by just using ACPI bytecode to speak the SBS protocol. Looking at the code in question showed that they never issued any writes, and the battery worked fine anyway. So why were we writing? SBS provides a command to tell you the current state of the battery subsystem, including which battery (out of a maximum of 4) is currently selected. Unsurprisingly, calling this showed that the battery we wanted to talk to was already selected. We then asked the SBS manager to select it anyway, and the manager promptly fell off the bus and stopped talking to us. In keeping with the maxim of "If hardware complains when we do something, and if we don't really need to do that, don't do that", this makes it work.

Working Thunderbolt and working battery. We're even getting close to getting switchable GPU support working reasonably, which is probably just going to involve rewriting the entirety of fbcon or something similarly amusing.

comment count unavailable comments

Francesca Ciceri: All hail the Spam Reviewers!

With the help of Enrico and the Debian Listmaster Team -and in particular Alexander Wirt: thank you very much! - the spam reviewers of the Debian mailing lists have been added to the list of Debian Contributors.
While some statistics about the reviewing job already existed, adding these data to the Debian Contributors list is another little step to map all kind of Debian contributions. Wondering what exactly is the job of the spam reviewers? It consists in checking all the messages of the Debian mailing lists reported as spam: if a message reported as spam gets three reviews as spam (and none as ham) it is removed from the archive.
Only DDs can do this: if you are one check here how to do it.
But there's one - and probably more important job - that everyone can do: report spam. When a message is reported as spam by 5 or more persons, it will make to the reviewers' queue. There are some organized spam cleaning efforts you can join: here there's a list of them, while on this page you can find more information on the whole process. Wondering what exactly is the Debian Contributors list? It's a brilliant effort started by Enrico to map all kind of contributions in Debian.
You can read more about it here and here.

Vasudev Kamath: Working around type system of Go with unsafe

Go language has a strong type system unlike C, and some times this will be head ache when we want to interact with C data types with Cgo or just to convert a Go type to lets say byte slice. I recently faced the same problem and after poking around things, I learned the Go provides unsafe package which can be used to work around the Go's type system.
Problem Recently I started using Cgo to use some C library I had to write some tools for development and testing. The reason I chose Go for this was prototyping and writing some quick tools is much easier in Go than done in C. The C library had some function which takes pointer to array types and fills it with some values. The problem I was facing here was how to create a C array in Go. Go does have array but its largely used as internal representation for much efficient type called slice and I can't directly cast a byte slice into an C array. Second problem I had was I had to store arbitrary Go types like float (float32,float64) and int (int32, int64) etc. into C array. So in brief, the problems that needed to solve are
  1. Find a way to convert byte slice from Go into C array and vice versa.
  2. Find a way to convert and store Go types into a C array.
Solution Basically C array are sequence of memory location which can be statically or dynamically allocated. In Cgo its possible to access C standard library functions for memory allocation, so why not use it. The memory allocation function then returns the pointer to starting of allocated memory, we can use this pointer to write Go's bytes into the memory location and bytes from memory location into Go's slice. The pointer returned by C allocation functions are not directly usable for memory dereferencing in Go, here is where unsafe package kicks in. We will cast the return of C allocation function as unsafe.Pointer type and from the documentation of unsafe package,
  1. A pointer value of any type can be converted to a Pointer.
  2. A Pointer can be converted to a pointer value of any type.
  3. A uintptr can be converted to a Pointer.
  4. A Pointer can be converted to a uintptr.
So we can then cast unsafe.Pointer to uintptr which is the Go type which is large enough to hold any memory address in Go and can be used for pointer arithmetic just like we do in C (of course with some more castings). Below I'm pasting a simplified code in C which I wrote for this post.
#ifndef __BYTETEST_H__
#define __BYTETEST_H__
typedef unsigned char UBYTE;
extern void ArrayReadFunc(UBYTE *arrayout);
extern void ArrayWriteFunc(UBYTE *arrayin);
#endif
#include "bytetest.h"
#include <stdio.h>
#include <string.h>
void ArrayReadFunc(UBYTE *arrayout)
 
     UBYTE array[20] =  1, 2, 3, 4,5, 6, 7, 8, 9, 10,
                        11, 12, 13, 14, 15, 16, 17,
                        18, 19, 20 ;
     memcpy(arrayout, array, 20);
 
void ArrayWriteFunc(UBYTE *arrayin)
 
     UBYTE array[20];
     memcpy(array, arrayin, 20);
     printf("Byte slice array received from Go:\n");
     for(int i = 0; i < 20; i ++) 
             printf("%d ", array[i]);
      
     printf("\n");
 
Functions are written just for this post and they don't really do anything. As you can see ArrayReadFunc takes a pointer to array and fills it with content of another array using memcpy. Function ArrayWriteFunc on other hand takes pointer to array and copies its content to internal array. I've added print logic to ArrayWriteFunc just to show that values passed from Go are making it here. Below is the Go code which uses the above C files passes byte slice to get value out of C code and array made of byte slice to C function to send values in.
package main
/*
#cgo CFLAGS: -std=c99
#include "bytetest.h"
#include <stdlib.h>
*/
import "C"
import (
     "fmt"
     "unsafe"
)
func ReadArray() unsafe.Pointer  
       var outArray = unsafe.Pointer (C.calloc(20,1))
       C.ArrayReadFunc((*C.UBYTE)(outArray))
       return outArray
 
func WriteArray(inArray unsafe.Pointer)  
       C.ArrayWriteFunc((*C.UBYTE)(inArray))
 
func CArrayToByteSlice(array unsafe.Pointer, size int) []byte  
       var arrayptr = uintptr(array)
       var byteSlice = make([]byte, size)
       for i := 0; i < len(byteSlice); i ++  
               byteSlice[i] = byte(*(*C.UBYTE)(unsafe.Pointer(arrayptr)))
               arrayptr ++
        
       return byteSlice
 
func ByteSliceToCArray (byteSlice []byte) unsafe.Pointer  
       var array = unsafe.Pointer(C.calloc(C.size_t(len(byteSlice)), 1))
       var arrayptr = uintptr(array)
       for i := 0; i < len(byteSlice); i ++  
              *(*C.UBYTE)(unsafe.Pointer(arrayptr)) = C.UBYTE(byteSlice[i])
              arrayptr ++
        
       return array
 
func main() 
        carray := ReadArray()
        defer C.free(carray)
        carraybytes := CArrayToByteSlice(carray, 20)
        fmt.Println("C array converted to byte slice:")
        for i := 0; i < len(carraybytes); i ++  
                fmt.Printf("%d ", carraybytes[i])
         
        fmt.Println()
        gobytes := []byte 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
                31, 32, 33, 34, 35, 36, 37, 38, 39, 40 
        gobytesarray := ByteSliceToCArray(gobytes)
        defer C.free(gobytesarray)
        WriteArray(gobytesarray)
 
Functions ReadArray and WriteArray are just wrapper to the calls to C counter parts ArrayReadFunc and ArrayWriteFunc. ReadArray returns unsafe.Pointer which is allocated C array and should be freed by caller. WriteArray takes unsafe.Pointer which is pointing to memory location containing C array. Now the functions of interest are CArrayToByteSlice and ByteSliceToCArray. It should be pretty clear from the above code to understand what is happening in these functions. Still I will just put explain them briefly. ByteSliceToCArray allocates a C array using calloc from C standard library. It then creates a uintptr, a pointer type in Go which is used to dereference the each memory location and store bytes from the input byte slice in them. CArrayToByteSlice on other hands creates a uintptr type by casting input unsafe.Pointer type and then uses this pointer type to dereference values from memoy and store it in byte slice with suitable casting. So lets build the code and run it and see the output:
C array converted to byte slice:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Byte slice array received from Go:
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
So yes it actually works and values are moving across C and Go. This solves first problem in hand next is converting arbitrary Go types into byte slices. There are many cases where we would like to convert an arbitrary Go types like (int, float) into bytes. One such use case I found was when writing a TCP client for communicating with a Server written using C speaking custom protocol. Here I'm just going to show how to convert types like float, int to byte slice, I've not tried converting structures but it is certainly possible. Below is the function which can convert int32,float32 into byte slice it can also be extended for other types.
func CopyValueToByte(value interface ) []byte  
     var valptr uintptr
     var slice []byte
     switch t := value.(type)  
     case int32:
             i := value.(int32)
             valptr = uintptr(unsafe.Pointer(&i))
             slice = make([]byte, unsafe.Sizeof(i))
     case float32:
             f := value.(float32)
             valptr = uintptr(unsafe.Pointer(&f))
             slice = make([]byte, unsafe.Sizeof(f))
     default:
             fmt.Fprintf(os.Stderr,"Unsupported type: %T\n", t)
             os.Exit(1)
      
     for i := 0; i < len(slice); i++  
             slice[i] = byte(*(*byte)(unsafe.Pointer(valptr)))
             valptr++
      
     return slice
 
This function is generic which can take various types of value. First we will use Go's type assertion to determine the type and creates a uintptr pointer for the value and allocates byte slice depending on the size of the value as calculated using unsafe.Sizeof. Later it uses the pointer to dereference value from memory location and copies each byte into byte slice. The idea used here is every type is represented as certain number of bytes in the memory. Below is the entire program.
package main
import (
     "fmt"
     "unsafe"
     "os"
)
func CopyValueToByte(value interface ) []byte  
     var valptr uintptr
     var slice []byte
     switch t := value.(type)  
     case int32:
             i := value.(int32)
             valptr = uintptr(unsafe.Pointer(&i))
             slice = make([]byte, unsafe.Sizeof(i))
     case float32:
             f := value.(float32)
             valptr = uintptr(unsafe.Pointer(&f))
             slice = make([]byte, unsafe.Sizeof(f))
     default:
             fmt.Fprintf(os.Stderr,"Unsupported type: %T\n", t)
             os.Exit(1)
      
     for i := 0; i < len(slice); i++  
             slice[i] = byte(*(*byte)(unsafe.Pointer(valptr)))
             valptr++
      
     return slice
 
func main()  
     a := float32(-10.3)
     floatbytes := CopyValueToByte(a)
     fmt.Println("Float value as byte slice:")
     for i := 0; i < len(floatbytes); i++  
             fmt.Printf("%x ", floatbytes[i])
      
     fmt.Println()
     b := new(float32)
     bptr := uintptr(unsafe.Pointer(b))
     for i := 0; i < len(floatbytes); i++  
             *(*byte)(unsafe.Pointer(bptr)) = floatbytes[i]
             bptr++
      
     fmt.Printf("Byte value copied to float var: %f\n", *b)
 
The above conversion can also be achieved using encoding/binary package provided by Go. But its been told to me that it makes things pretty slow.
Conclusion So goes unsafe.Pointer is really powerful thing which allows us to work around the Go's type system but as package documentation says it should be used with care
PS: I'm not really sure if its recommended to use allocation functions from C standard library, I will wait for expert gophers to comment on that.

11 March 2014

Wouter Verhelst: The apt-get install game

"Given a Debian system with just the set of essential packages installed, find a single package name that, together with its dependencies, will increase the used diskspace of the system by as much as possible." My entry: openarena. Off to bed, now.

Steve Kemp: Discovering back-end servers automatically?

Recently I've been pondering how to do service discovery. Pretend you have a load balancer which accepts traffic and routes incoming requests to different back-ends. The loadbalancer might be pound, varnish, haproxy, nginx, or similar. The back-ends might be node applications, apache, or similar. The typical configuration of the load-balancer will read:
# forward
# backends
backend web1    .host = "10.0.0.4";  
backend web2    .host = "10.0.0.6";  
backend web3    .host = "10.0.0.5";  
#  afterword
I've seen this same setup in many situations, and while it can easily be imagined that there might be "random HTTP servers" on your (V)LAN which shouldn't receive connections it seems like a pain to keep updating the backends. Using UDP/multicast broadcasts it is trivial to announce "Hey I'm a HTTP-server with the name 'foo'", and it seems to me that this should allow seamless HTTP load-balancing. To be more explicit - this is normal: What I'd like to propose is another step: i.e. This allows backends to be removed from service when they go offline but also to be added when they come online. Without the load-balancer needing its configuration to be updated. This means you'd not give a static list of back-ends to your load-balancer, instead you'd say "Route traffic to any service that adfvertises itself with the tag 'foo'.". VLANS, firewalls, multicast, udp, all come into play, but in theory this strikes me as being useful, obvious, and simple. (Failure cases? Well if the "announcer" dies then the backend won't get traffic routed to it. Just like if the backend were offline. And clearly if a backend is announced, but not receiving HTTP-requests it would be dropped as normal.) If I get the time this evening I'll sit down and look at some load-balancer source code to see if any are written in such a way that I could add this "broadcast discovery" as a plugin/minor change.

Steve Langasek: My CuBox-i has arrived

A couple of weeks ago, Gunnar Wolf mentioned on IRC that his CuBox-i4 had arrived. This resulted in various jealous noises from me; having heard about this device making the rounds at the Kernel Summit, I ordered one for myself back in December, as part of the long-delayed HDification of our home entertainment system and coinciding with the purchase of a new Samsung SmartTV. We've been running an Intel Coppermine Celeron for a decade as a MythTV frontend and encoder (hardware-assisted with a PVR-250), which is fine for SD video, but really doesn't cut it for anything HD. So after finally getting a TV that would showcase HD in all its glory, I figured it was time to upgrade from an S-Video-out, barely-limping-along tower machine to something more modern with HDMI out, eSATA, hardware video decoding, and whose biggest problem is it's so small that it threatens to get lost in the wiring! Since placing the order, I've been bemused to find that the SmartTV is so smart that it has had a dramatic impact on how we consume media; between that and our decision to not be a boiled frog in the face of DISH Network's annual price increase, the MythTV frontend has become a much less important part of our entertainment center, well before I ever got a chance to lay hands on the intended replacement hardware. But that's a topic for another day. Anyway, the CuBox-i4 finally arrived in the mail on Friday, so of course I immediately needed to start hacking on it! Like Gunnar, who wrote last week about his own experience getting a "proper" Debian install on the box, I'm not content with running a binary distribution image prepared by some third party; I expect my hardware toys to run official distro packages assembled using official distro tools and, if at all possible, distributed on official distro images for a minimum of hassle. Whereas Gunnar was willing to settle for using third-party binaries for the bootloader and kernel, however, I'm not inclined to do any such thing. And between my stint at Linaro a few years ago and the recent work on Ubuntu for phones, I do have a little knowledge of Linux on ARM (probably just enough to be dangerous), so I set to work trying to get the CuBox-i4 bootable with stock Debian unstable. Being such a cutting-edge piece of hardware, that does pose some challenges. Support for the i.MX6 chip is in the process of being upstreamed to U-Boot, but the support for the CuBox-i devices isn't there yet, nor is the support for SPL on i.MX6 (which allows booting the variants of the CuBox-i with a single U-Boot build, instead of requiring a different bootloader build for each flavor). The CuBox-i U-Boot that SolidRun makes available (with source at github) is based on U-Boot 2013.10-rc4, so more than a full release behind Debian unstable, and the patches there don't apply to U-Boot 2014.01 without a bit of effort. But if it's worth doing, it's worth doing right, so I've taken the time to rebase the CuBox-i patches on top of 2014.01, publishing the results of the rebase to my own github repository and submitting a bug to the Debian U-Boot maintainers requesting its inclusion. The next step is to get a Debian kernel that not only works, but fully supports the hardware out of the box (a 3.13 generic arm kernel will boot on the machine, but little things like ethernet and hdmi don't work yet). I've created a page in the Debian wiki for tracking the status of this work.

Andrew Pollock: [life] Day 42, a good night's sleep, Kindergarten, make up swim class and dinner with Nana

Zoe slept through the night for the first time in recent memory, and was up at around 6am. It was lovely. I'm thinking the slightly lower temperature helped. I'm definitely going to explore air conditioning for the bedrooms before next Summer. I'd picked up some egg cups on the weekend, so we did soft boiled eggs in the Thermomix for breakfast. They turned out lovely this time, and Zoe seemed to enjoy eating hers. She dressed herself while they were cooking. Her Kindergarten t-shirts arrived yesterday, so now she looks like all the other kids. It's very cute. It rained on and off in the morning, and I was in two minds about whether to chance it on the bike or not. As it happened we ended up messing around a bit before leaving, so we drove, and it was a good call, because it started raining again as we pulled out of the driveway. Drop off was super smooth. Megan had been away yesterday, but was back today, so Zoe was very happy to see her. My financial advisor came out for a chat about investments mid-morning, and then I got stuck into my US taxes. I will not miss having to do these tax returns. The 2013 one is going to be particularly complicated by the fact we sold our home and were only in the country for like 80 days. I biked over to Kindergarten to pick up Zoe, as the weather had improved. Surprisingly, she was awake, but her teacher said she'd just woken up from a nap. We managed to clear out pretty quickly. I stopped at the health food shop on the way home to get some peanut oil for the dinner I was making. Due to a miscommunication, Zoe had missed her Saturday swim class. I explained to the swim school the error, and they were kind enough to allow Zoe to have a make up class anyway, so we went to that after Kindergarten. Zoe wanted to do some more practice riding her bike. She's got a little 10 inch bike with no training wheels and a gyrobike front wheel. We haven't had a lot of success learning to ride to date, due to a combination of her height and just time, so it was interesting that she decided today she wanted to do some more practice. She wanted to "ride" her bike from home to the park on the way to swim class, but I managed to convince her to walk the bike to the park. It turns out even walking the bike is a bit of a challenge for her, so I ended up carrying it most of the way. It's heavy. We got to the park, and had a bit of a run with the bike. I'm glad I decided to take her knee pads and elbow pads, because she did stack it fairly decently, but managed to survive mostly unscathed. To her credit, she even got back on the bike again. She's a tough kid. Once we made it to the playground, she lost all interest in the bike, and we had about 15 minutes to kill before we needed to continue to the pool. We walked/carried the bike from the park to the pool, and the make up class Zoe ended up in was Megan's regular class, so the girls had a great time together. After swim class, we walked back home. Zoe's Nana was coming over for dinner, and I wanted to make a satay chicken dish. I'd bought some peanuts from the Peanut Wagon on Sunday, when I was in Nanango for my uncle's 90th birthday party, and I wanted to try making peanut butter from scratch in the Thermomix, and use that to make the satay sauce. Long story short, dinner wasn't super great. I'd made a satay chicken in the Thermomix previously, but I couldn't find the recipe I used last time. I used this recipe tonight, but didn't really follow it properly because I was trying to cut corners due to the time. I tried cooking wild rice in the microwave and it was a total flop. The satay chicken itself turned out okay, but it was a shame about the rice. It was cool making my own peanut butter. Zoe had a great time playing with her Nana, and after we dropped her off at the train station went to bed without a peep.

Next.

Previous.