Jan 23,
2017

Notebook's Keyboard Backlight

It seems that I'm frequently involved in a recurring scenario: I tweak something, a couple of days later I discover that an unrelated thing was broken by the tweak, I fall down the rabbit hole to fix the issue.

Today is the notebook's keyboard backlight, I don't even use the thing as I always keep it off, I find the light distracting.

So, a few months ago I started using a recent kernel otherwise a third party application causes the Intel Xorg driver to segfault. I think last week I spotted that a new kernel package was available, so I installed the new version without too much attention, but it wasn't a minor release and only today I realized that the backlight of the keyboard now turns on every time the notebook is resumed after a suspend or simply unlocking the screen lock.

A quick Google search pointed to a mix of a new kernel feature1 and UPower behavior, digging some more I found the freedesktop.org bug #95457 with a patch that should have fixed the issue in the upstream version.

- "It will took me half an hour tops to port it to the version in Debian stable and creating a package" - or that's what I thought.

A patch of a patch

It's a straight forward patch, it changes the behavior of the UPower daemon to check the brightness level instead of using the last saved value. The problem was that my modified daemon didn't fully worked, the backlight seemed to behave correctly or at least it didn't turn on after resume, but I couldn't query the brightness level using D-Bus and the "failed to convert brightness" error was present on syslog, so something was wrong with my version of the patch.

Bummer.

I spent a considerable amount of time staring at my code, searching for the error, but all looked consistent with what was applied upstream. So I rebuilt the package with the debug symbols2,

$ DEB_BUILD_OPTIONS='nostrip noopt debug' dpkg-buildpackage -b -uc -us

installed the new package, killed the daemon, launched it attached with gdb and set a break-point on the up_kbd_backlight_brightness_read function, in other words, standard boring C development.

A couple of minutes later, it turned out that I didn't introduced new bugs, the issue was with the upstream patch itself in the *end != '\0' test case, a off by 1 example. I found the confirmation in the UPower's git log and it was also tracked by the bug #96215.

So now I can query and set the brightness using D-Bus3 and the state of the backlight is preserved like I was used to:

$ gdbus call --system \n
  --dest org.freedesktop.UPower \n
  --object-path /org/freedesktop/UPower/KbdBacklight \n
  --method org.freedesktop.UPower.KbdBacklight.GetBrightness

If someone is interested in the patch for Debian Jessie the diff below apply to the upower_0.99.1-3.2_amd64.deb package:

--- upower-0.99.1/src/up-kbd-backlight.c    2013-10-29 11:37:08.000000000 +0100
+++ upower-0.99.1.mod/src/up-kbd-backlight.c    2017-01-23 15:37:54.022599766 +0100
@@ -47,7 +47,6 @@
 struct UpKbdBacklightPrivate
 {
    gint             fd;
-   gint             brightness;
    gint             max_brightness;
    DBusGConnection     *connection;
 };
@@ -62,10 +61,41 @@
 G_DEFINE_TYPE (UpKbdBacklight, up_kbd_backlight, G_TYPE_OBJECT)

 /**
+ * up_kbd_backlight_brightness_read:
+ **/
+static gint
+up_kbd_backlight_brightness_read (UpKbdBacklight *kbd_backlight)
+{
+   gchar buf[16];
+   gchar *end = NULL;
+   ssize_t len;
+   gint64 brightness = -1;
+
+   g_return_val_if_fail (kbd_backlight->priv->fd >= 0, brightness);
+
+   lseek (kbd_backlight->priv->fd, 0, SEEK_SET);
+   len = read (kbd_backlight->priv->fd, buf, G_N_ELEMENTS (buf) - 1);
+
+   if (len > 0) {
+       buf[len] = '\0';
+       brightness = g_ascii_strtoll (buf, &end, 10);
+
+       if (brightness < 0 ||
+           brightness > kbd_backlight->priv->max_brightness ||
+           end == buf) {
+           brightness = -1;
+           g_warning ("failed to convert brightness: %s", buf);
+       }
+   }
+
+   return brightness;
+}
+
+/**
  * up_kbd_backlight_brightness_write:
  **/
 static gboolean
-up_kbd_backlight_brightness_write (UpKbdBacklight *kbd_backlight, gint value)
+   up_kbd_backlight_brightness_write (UpKbdBacklight *kbd_backlight, gint value)
 {
    gchar *text = NULL;
    gint retval;
@@ -96,9 +126,8 @@
    }

    /* emit signal */
-   kbd_backlight->priv->brightness = value;
    g_signal_emit (kbd_backlight, signals [BRIGHTNESS_CHANGED], 0,
-              kbd_backlight->priv->brightness);
+              value);

 out:
    g_free (text);
@@ -113,8 +142,20 @@
 gboolean
 up_kbd_backlight_get_brightness (UpKbdBacklight *kbd_backlight, gint *value, GError **error)
 {
+   gint brightness;
+
    g_return_val_if_fail (value != NULL, FALSE);
-   *value = kbd_backlight->priv->brightness;
+
+   brightness = up_kbd_backlight_brightness_read (kbd_backlight);
+
+   if (brightness >= 0) {
+       *value = brightness;
+   } else {
+       g_set_error_literal (error, UP_DAEMON_ERROR,
+                    UP_DAEMON_ERROR_GENERAL,
+                    "error reading brightness");
+   }
+
    return TRUE;
 }

@@ -226,23 +267,13 @@
        goto out;
    }

-   /* read brightness */
+   /* open the brightness file for read and write operations */
    path_now = g_build_filename (dir_path, "brightness", NULL);
-   ret = g_file_get_contents (path_now, &buf_now, NULL, &error);
-   if (!ret) {
-       g_warning ("failed to get brightness: %s", error->message);
-       g_error_free (error);
-       goto out;
-   }
-   kbd_backlight->priv->brightness = g_ascii_strtoull (buf_now, &end, 10);
-   if (kbd_backlight->priv->brightness == 0 && end == buf_now) {
-       g_warning ("failed to convert brightness: %s", buf_now);
-       goto out;
-   }

-   /* open the file for writing */
    kbd_backlight->priv->fd = open (path_now, O_RDWR);
-   if (kbd_backlight->priv->fd < 0)
+
+   /* read brightness and check if it has an acceptable value */
+   if (up_kbd_backlight_brightness_read (kbd_backlight) < 0)
        goto out;

    /* success */
@@ -315,4 +346,3 @@
 {
    return g_object_new (UP_TYPE_KBD_BACKLIGHT, NULL);
 }

  1. a revamped LED control exposed by the sys file system. ↩︎

  2. this is a remainder for my future self because I keep forgetting the name of the environment variable. ↩︎

  3. I think it wasn't possible before kernel 4.8 but I didn't checked. ↩︎

 
 

Dec 26,
2016

Wakelock battery drain

Last week I've found out that the battery in my phone didn't last a day and according to the phone power estimations, a fully charged battery provided a little less than 9 hours of idle time.

Bummer.

My phone is an old Android terminal, it has already passed his manufacturer support life at least two times, but I'm really appalled by the current offer in the market. There's exactly one manufacturer that still produces a high-end Android model with a display under 5 inches1, I really hate the current trend of phones with ginourmous displays that can't fit in a trouser pocket. And nope, I'm not so keen to adopt Apple's ecosystem.

Have you tried turning it off and on again?

A reboot didn't solve the issue and at that moment I wasn't yet sure if it was a hardware or a software problem. Considering the age of the phone, I was leaning for a dying battery.

Turned out instead, the OS had stopped entering deep sleep state at all, a.k.a. the lowest power mode. So diagnosis was easy, at least one process was not releasing a "wakelock", a function in the Android power management stack where a process can keep the OS "awake", as the name suggests. Thorough analysis2 pointed specifically to the suspend_backoff wakelock, but trying to find the guilty program was fruitless, as disabling first and then removing a bunch of applications installed with the last update was fruitless.

Before proceeding with a factory reset, a process that I dread, I tried to clean the Dalvik cache as a last attempt. How do you clean a cache? Oh my, you delete its content! I always have trouble recalling the button sequence for boot in recovery mode: is it power and volume up or is it power and volume down? Anyway, the recovery image I installed didn't have that option, or at least I couldn't find it.

Yay!

Well, if one has access to the root user on the phone, cleaning the Dalvik cache it's a matter of seconds:

$ adb shell
* daemon not running. starting it now on port 5037 *
* daemon started successfully *
$ su
# cd /data/dalvik-cache/
# ls -l
drwx--x--x root     root              2016-12-23 19:08 arm
drwx--x--x system   system            2016-12-23 19:08 profiles
# rm -r arm profiles
# reboot

Faster than saying suspend_backoff wakelock!

After the reboot I've verified that the OS could finally transition to deep sleep state again. Brilliant, it looks like I don't have an excuse to buy a new one.

Bummer.


  1. Sony, with the Xperia Compact line. ↩︎

  2. a program running in background, recording power management events. ↩︎

 
 

Dec 06,
2016

Secure Connections

With the occasion of the redesign I had the motivation to install the Let's Encrypt certificates and to enable the HTTPS on the web server.

Doing so, I've found out that in a default Debian Jessie install nginx is configured to accept 1024 bits Diffie-Hellman's keys, and those are considered weak keys (the interested reader can search for Logjam attack for the analysis). I think there are old web browser that have a limit of 1024 bit as maximum keys' size and are still used.

To increase the security it's suggested to generate a new prime number of adequate size for the Diffie-Hellman key exchange, store it in a file:

$ openssl dhparam -out dhparams.pem 2048

and adding a line in the nginx configuration:

ssl_dhparam /path/to/the/dhparams.pem

pointing to the above newly created file.

After the little digression, I'm sure I'm not alone saying that certificates management it has always been an hassle, especially interacting with the relative CA. Let's Encrypt provides certificates with a 3 months expiration date, but conveniently they have deployed an infrastructure that enables the fully automation of the authentication of the site administrator and the certificates' renewal using specialized clients, for example the EFF certbot.

Root? How about nope?

It will be all fine and dandy, with packages available for Debian Jessie that in my case completed the configuration without issues, with the small detail that a process is running daily as root and is accepting input from the Interweb, something that I'm somewhat uncomfortable with.

To amend this and run the certbot script as an unprivileged user, I created the three required directories (config, logs and work) in the user's home, copied over the /etc/letsencrypt/config content in the new directory and changed the files ownership. Some paths needed to be modified in the configuration file under config/renewal, basically they all must point to the certificates' new location in the user's home. In the same file, under the renewalparams section, I changed the authentication method and set the path to use for the authentication:

# Options used in the renewal process
[renewalparams]
authenticator = webroot
webroot-map = {"domain.name.here":"/web/root/here"}
account = 4cc0un71dh3r3

When testing, if all is working, the following command:

$ certbot renew --dry-run \
--config-dir ~/letsencrypt/config \
--logs-dir   ~/letsencrypt/logs  \
--work-dir   ~/letsencrypt/work

should report "all good".

To automate it I stopped and disabled the two systemd unit files provided by the certbot packages, certbot.service and certbot.timer, and created two new units in the /etc/systemd/system directory, certbot-non-root.timer:

[Unit]
Description=Run certbot daily

[Timer]
OnCalendar=*-*-* 06:00:00
RandomizedDelaySec=3600
Persistent=true

[Install]
WantedBy=timers.target

and certbot-non-root.service:

[Unit]
Description=Let's Encrypt renewal

[Service]
Type=oneshot
User=username
Group=usergroup
PrivateTmp=yes
PermissionsStartOnly=yes
NoNewPrivileges=yes
ExecStart=/usr/bin/certbot -q renew \
 --config-dir /home/username/letsencrypt/config \
 --logs-dir   /home/username/letsencrypt/logs \
 --work-dir   /home/username/letsencrypt/work
ExecStartPre=/bin/sh -c 'sleep $(shuf -i 1-3600 -n 1)'
ExecStartPost=/bin/systemctl reload --no-block nginx.service

[Install]
WantedBy=multi-user.target

Two things about the timer unit file:

  • The systemd version in Debian Jessie doesn't seem to support the RandomizedDelaySec directive. There is an error in the logs but it's not fatal so I've left it there1, but for the time being I've added in the service file an equivalent random delay using the ExecStartPre line.

  • It needs to be enabled and started; I forgot the second part and I was quite puzzled when I didn't found traces of it in the logs.

I suppose the reload of the web server in the ExecStartPost line should be enough to read the certificates when they are updated, but I didn't have it tested yet, I'll probably add an update with a note.


  1. Debian Stretch is slated to be released in Q1 2017 and I'm an optimistic. ↩︎