Monthly Archives: February 2019

Crostini on Acer R13

Update 20190730: Got build 75.0.3770.144 , it seems to work now!!!
Update 20190617: Got build 74.0.3729.159, problems remain.
Update 20190409: Activated Developer mode and installed Crouton again. It works perfectly for me.

Update 20190329: Got build 73.0.3683.88 four days late, Chrome OS 73, still no improvement.
Update 20190306: Got build 72.0.3626.122, problems remain.
Update 20190225: Got build 72.0.3626.117, problems remain.

Finally Crostini is available on the stable channel of ChromeOS for Acer R13 (elm platform 72). Unfortunately, the experience is still not what I had hoped.

I get a container as expected, but after a while problems start, it crashes and fails to start again. This is a way to destroy the virtual machine (termina) and the container (penguin) inside it.

crosh> vmc stop termina
crosh> vmc destroy termina
crosh> vmc start termina
(termina) chronos@localhost ~ $ lxc list
To start your first container, try: lxc launch ubuntu:18.04
+------+-------+------+------+------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------+-------+------+------+------+-----------+

When I have done this, I can start the terminal app, it takes a while, and I get:

zo0ok@penguin:~$ uname -a
Linux penguin 4.19.4-02480-gd44d301822f0 #1 SMP PREEMPT Thu Dec 6 17:48:31 PST 2018 aarch64 GNU/Linux

That is good. I run apt-get update and apt-get upgrade successfully.

Trying to install a real terminal

However, when I try to install gnome-terminal “it” crashes.

(termina) chronos@localhost ~ $ [ERROR:utils.cc(50)] Failed to read message size from socket: Resource temporarily unavailable
[ERROR:vsh_client.cc(186)] Failed to receive message from server: Resource temporarily unavailable
crosh>

Both the virtual machine (termina) and the container (penguin) crashed. I can start termina again, but penguin is dead.

Yesterday, when running apt-get install, I got “Illegal instruction” repeatedly. I can make some semi-qualified guesses based on that:

  1. Something with the visualization/containerization layer is not working properly on ARM64 yet.
  2. The Debian guest OS is built in a way that is not compatible with my machine, at least not inside a container in a VM

I tried (as suggested in a message above) to set up Ubuntu instead:

(termina) chronos@localhost ~ $ lxc launch ubuntu:18.04
Creating the container
Container name is: set-kitten
Starting set-kitten
(termina) chronos@localhost ~ $ lxc list
+------------+---------+-----------------------+------+------------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------------+---------+-----------------------+------+------------+-----------+
| set-kitten | RUNNING | 100.115.92.201 (eth0) | | PERSISTENT | 0 |
+------------+---------+-----------------------+------+------------+-----------+

Then it turned out to be necessary to use a little trick to set the password in Ubuntu, and log in as usual.

(termina) chronos@localhost ~ $ lxc exec set-kitten -- /bin/bash
root@set-kitten:~# passwd ubuntu
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
root@set-kitten:~# exit

(termina) chronos@localhost ~ $ lxc console set-kitten
To detach from the console, press: +a q
Ubuntu 18.04.2 LTS set-kitten console
set-kitten login: ubuntu
Password:

But again, apt-get update and apt-get upgrade works just fine. But when I tried to install gnome-terminal all looked fine for a while, until:

Setting up libcdparanoia0:arm64 (3.10.2+debian-13) …
Setting up libblockdev-loop2:arm64 (2.16-2) …
[ERROR:utils.cc(50)] Failed to read message size from socket:
Resource temporarily unavailable
[ERROR:vsh_client.cc(186)] Failed to receive message from server: Resource temporarily unavailable
crosh>

From here it got worse, and all I could think of was to start over again.

(termina) chronos@localhost ~ $ lxc list
+------------+---------+------+------+------------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------------+---------+------+------+------------+-----------+
| set-kitten | STOPPED | | | PERSISTENT | 0 |
+------------+---------+------+------+------------+-----------+
(termina) chronos@localhost ~ $ lxc start set-kitten
Error: Missing source '/run/sshd/penguin/authorized_keys' for disk 'ssh_authorized_keys'

Back to basics

What if the problem is related to me wanting a GUI-terminal? I started over with a new default-penguin-container (using the vmc destroy termina trick I mentioned in the beginning). Unfortunately, when I did apt-get ugrade in my new penguin-system I have the impression that many things anyway were left since before, and apt-get upgrade crashed.

So I decided to:

  1. Powerwash the Acer R13
  2. First thing when it came up again, install Linux
  3. apt-get update
  4. apt-get uprade (CRASHED)

Trying with Archlinux

I gave an arch-container a try. Installing archlinux is as easy as:

lxc launch images:archlinux

I installed a few tools (ssh, git, nodejs) and that was fine. Then I tried to git clone a private repository. It got stuck half way, but Ctrl-C allowed me to restart and all was good. Then I installed some node-packages with npm install and I got the familiar:

[ERROR:utils.cc(50)] Failed to read message size from socket: Transport endpoint is not connected [ERROR:vsh_client.cc(186)]
Failed to receive message from server: Transport endpoint is not connected
crosh>

…that is, container, and virtual machine all crashed.

Other people…

It seems I am not alone according to this reddit thread (that I have also posted to).

Conclusion

Unfortunately , I must say that Crostini is not at all stable and useful on Acer R13, despite it is now in the stable channel.

At this point when I get very irregular crashes I must ask myself if there is anything wrong with my Chromebook. However, apart from Crostini I never had any problems with it. And for several months I ran it in Developer mode with Crouton, always rock solid.

The good thing is possibly that this should not be a major bug with architectural implications. If they (Google) have made it all this way with Crouton for ARM64 I don’t think they will abandon it. This will silently be fixed some day and forgotten.

So unfortunately, I can not recommend the Acer R13 (or any other ARM device) at the moment if you are interested in running Linux/Crostini on your Chromebook. Crouton should still be good, I guess, it was a while since I used it.

Where to ‘use strict’ with Object.freeze()?

I have coded JavaScript short enough time to consider ‘use strict’ a mandatory and obvious feature of the language to use. I always use it unless I forget to.

A while ago I was aware of Object.freeze(). I have been thinking about different ways to exploit this (strict) feature for a while and I now have a very good use case: freeze indata in unit tests to ensure my tested functions don’t incidentally change indata (pure functions are good, pure functions don’t change indata, and it is hard to really guarantee a function in JavaScript is pure).

Imagine I am writing a function that calculates the average and I have a test for it.

const averageOfArray1 = (a) => {
let s = 0;
for ( let i=0 ; i<a.length ; i++ ) s+=a[i];
return s/a.length;
};

describe('test avg', () => {
it('should give the average value of 2', () => {
const a = [1,2,3];
assert.equal(2, averageOfArray1(a) );
});
});

If averageOfArray mutates its input, it would be a serious bug, and the above test would not detect it. Lets look at a different implementation:

const averageOfArray2 = (a) => {
for ( let i=1 ; i<a.length ; i++ ) a[0] += a[i];
return a[0]/a.length;
};

describe('test avg', () => {
it('should give the average value of 2', () => {
const a = [1,2,3];
assert.equal(2, averageOfArray2(a) );
});
});

Some genious “optimized” the function by eliminating an unnecessary variable (s), and the test still passes! However, if the tests where written:

describe('test loop', () => {
it('should give the average value of 2', () => {
const a = Object.freeze([1,2,3]);
assert.equal(2, averageOfArray2(a) );
});
})

the tests would fail! Much better. How do the tests fail? This is what I get:

1) test avg
should give the average value of 2:

AssertionError [ERR_ASSERTION]: 2 == 0.3333333333333333
+ expected - actual
-2
+0.3333333333333333

So it appears that the first element [0] of the array was never changed, thus the return value of 0.3333. But no exception was thrown. If I instead would ‘use strict’ for the entire code:

 'use strict';

const assert = require('assert');

const averageOfArray2 = (a) => {
for ( let i=1 ; i<a.length ; i++ ) a[0] += a[i];
return a[0]/a.length;
};
describe('test avg', () => {
it('should give the average value of 2', () => {
const a = Object.freeze([1,2,3]);
assert.equal(2, averageOfArray2(a));
});
});

instead I get:

1) test avg
should give the average value of 2:
TypeError: Cannot assign to read only property '0' of object '[object Array]'
at averageOfArray2 (avg.js:12:45)
at Context.it (avg.js:20:25)

which is what I really wanted.

So it APPEARS to me that without ‘use strict’ the frozen object is not changed, but changing it just fails silently. With ‘use strict’ I get an exception right way, which leads me to the question where I can put use strict? This is what I found:

 // 'use strict';  // GOOD

const assert = require('assert');

// 'use strict'; // BAD

const averageOfArray2 = (a) => {
// 'use strict'; // GOOD
let i;
// 'use strict'; // BAD
for ( i=1 ; i<a.length ; i++ ) a[0] += a[i];
return a[0]/a.length;
};
describe('test avg', () => {
// 'use strict'; // BAD
it('should give the average value of 2', () => {
const a = Object.freeze([1,2,3]);
assert.equal(2, averageOfArray2(a));
});
});

That is, ‘use strict’ should be in place where the violation actually takes place. And ‘use strict’ must be placed first in whatever function it is placed, otherwise it is silently ignored! This is probably well known to everyone, but it was not to me.

Conclusion

Object.freeze() is very useful for improved unit tests. However, you should use it together with properly placed ‘use strict’ and that is in the function begin tested (not only the unit test).

And note, if you have done Object.freeze in a unit test, and someone refactors the tested function in a way that it both:

  1. Mutates the frozen object
  2. Removes or moves ‘use strict’ to an invalid place

your unit tests may still pass, even though the function is now very dangerous.

Best way to write compare-functions

The workhorse of many (JavaScript) programs is sort(). When you want to sort objects (or numbers, actually) you need to supply a compare-function. Those are nice functions because they are very testable and reusable, but sorting is also a bit expensive (perhaps the most expensive thing your program does) so you want them fast.

For the rest of this article I will assume we are sorting som Order objects based status, date and time (all strings).

The naive way to write this is:

function compareOrders1(a,b) {
if ( a.status < b.status ) return -1;
if ( a.status > b.status ) return 1;
if ( a.date < b.date ) return -1;
if ( a.date > b.date ) return 1;
if ( a.time < b.time ) return -1;
if ( a.time > b.time ) return 1;
return 0;
}

There are somethings about this that is just not appealing: too verbose, risk of a typo, and not too easy to read.

Another option follows:

function cmpStrings(a,b) {
if ( a < b ) return -1;
if ( a > b ) return 1;
return 0;
}

function compareOrders2(a,b) {
return cmpStrings(a.status,b.status)
|| cmpStrings(a.date ,b.date )
|| cmpStrings(a.time ,b.time );
}

Note that the first function (cmpStrings) is highly reusable, so this is shorter code. However, there is still som repetition, so I tried:

function cmpProps(a,b,p) {
return cmpStrings(a[p], b[p]);
}

function compareOrders3(a,b) {
return cmpProps(a,b,'status')
|| cmpProps(a,b,'date')
|| cmpProps(a,b,'time');
}

There is something nice about not repeating status, date and time, but there is something not so appealing about quoting them as strings. If you want to go more functional you can do:

function compareOrders4(a,b) {
function c(p) {
return cmpStrings(a[p],b[p]);
}
return c('status') || c('date') || c('time');
}

To my taste, that is a bit too functional and obscure. Finally, since it comes to mind and some people may suggest it, you can concatenate strings, like:

function compareOrders5(a,b) {
return cmpStrings(
a.status + a.date + a.time,
b.status + b.date + b.time
);
}

Note that in case fields “overlap” and/or have different length, this could give unexpected results.

Benchmarks

I tried the five different compare-functions on two different machines and got this kind of results (i5 N=100000, ARM N=25000), with slightly different parameters.

In these tests I used few unique values of status and date to often hit the entire compare function.

(ms)   i5    i5    ARM
#1 293 354 507
#2 314 351 594
#3 447 506 1240
#4 509 541 1448
#5 866 958 2492

This is quite easy to understand. #2 does exactly what #1 does and the function overhead is eliminated by the JIT. #3 is trickier for the JIT since a string is used to read a property. That is true also for #4, which also requires a function to be generated. #5 puts two strings on the stack needlessly when often only the first two strings are needed to compare anyway.

Conclusion & Recommendation

My conclusion is that #3 may be the best choice, despite it is slightly slower. I find #2 clearly preferable to #1, and I think #4 and #5 should be avoided.

ArchLinux on RPi with USD Harddrive

Update 20190624: Today I ran “pacman -Suy” and ended up with a system that does not start. Two blinking green leds indicate failure to read the SD card. That is less than 5 months after I used SD-card for only /boot. I really consider not running RPi as headless servers anymore.

I have found that one of the weakest parts of a Raspberry Pi server or workstation is the SD card: it is slow and it will break sooner rather than later. There may be industrial SD cards or better SD cards, but a good old USB hard drive is just better.

With RPi v3 it shall be possible to boot straight off a USB drive! That sounded great so I got a brand new RPi v3 B+, a USB hard drive, and I installed ArchLinux on the hard drive, just as if it was a memory card. Fail. That did not work (with ArchLinux, Raspbian may be another story).

But there are levels of pain:

  1. All SD-card
  2. SD-card, but /home on USB harddrive
  3. USB harddrive, but /boot on SD-card
  4. All USB harddrive

I decided to try #3.

It turns out that when the RPi boots it runs u-boot (its like the BIOS of RPi, and many other embedded devices). At one point u-boot reads boot.scr (from the first VFAT partition of the SD card). It had the lines:

part uuid ${devtype} ${devnum}:2 uuid

setenv bootargs console=ttyS1,115200 console=tty0 root=PARTUUID=${uuid} rw rootwait smsc95xx.macaddr="${usbethaddr}"

I figured that I could do this instead:

# part uuid ${devtype} ${devnum}:2 uuid

setenv bootargs console=ttyS1,115200 console=tty0 root=/dev/sda2 rw rootwait smsc95xx.macaddr="${usbethaddr}"

However, boot.scr has a checksum so you cant just edit it. But it tells you what to do: run ./mkscr. But it is dependent on mkimage, so the procedure is:

  1. Install uboot tools
    1. ARCH: pacman -S uboot-tools
    2. Ubuntu/Debian: apt-get install u-boot-tools
  2. Edit boot.txt (not boot.scr) to your liking
  3. Run: ./mkscr

Now only /boot is on SD-card. That is quite ok with me. There is very little I/O to boot so the SD-card should survive. If I want to I can make a regular simple backup by simple file copy of /boot to a zip-file or something, and just restore that zip-file to any SD-card.

There seems to be no need to edit anything else (like fstab).

Well, the bad thing is it did not work out 100% as I hoped. The good thing is that this should work with any RPi, not just the RPi v3 that supports USB boot.