summaryrefslogtreecommitdiff
path: root/posts/linux:non-root-suspend.rst
blob: db6ee5c6eeedf226494e7f2273a439bef6ccb3b4 (plain)
    1 Linux:Non-root Suspend
    2 ======================
    3 
    4 Several years ago I switched to a significantly less user-friendly distro.
    5 Since this distro did so little for the end user, I had to start learning how
    6 to do some of the things often taken for granted by those using more
    7 ready-to-use distros (a group I used to be a part of). One of these seemingly
    8 very simple operations was putting my laptop into a suspended state without
    9 requiring root access.
   10 
   11 Note that this post is a moot point if you are using acpid, as its daemon is
   12 started on boot and runs as root, sidestepping the point of this post. I later
   13 configured my acpid daemon, but I still wanted to learn how to do this as a
   14 regular user.
   15 
   16 
   17 Linux System Power Management
   18 -----------------------------
   19 
   20 First, let's cover how you can manage system-wide power in Linux. Keep in mind
   21 that this isn't entirely accurate anymore with so many distros using systemd
   22 for their init systems. Systemd provides an alternate means than the standard
   23 linux sysfs method we will discuss here.
   24 
   25 Linux provides a few pseudo-filesystems to make interracting with kernel data
   26 structures much easier. Pertinent to this post, the kernel offers the sysfs
   27 filesystem. If you would like to know more about the details of this
   28 filesystem, its purpose, and other uses, take a quick peek at its man page. I
   29 promise, it makes for good bathroom reading.
   30 
   31 
   32 .. code-block:: sh
   33 
   34   man 5 sysfs
   35 
   36 
   37 For today's use, we'll be looking at **/sys/power/state**. This interface is
   38 used for, you guessed it, controlling kernel power states. What fun.
   39 
   40 First, let's take a look at what power states are available to us.
   41 
   42 .. code-block:: sh
   43 
   44    $ cat /sys/power/state
   45    freeze mem disk
   46 
   47 Without getting into the details of acpi states, here's what those three do.
   48 
   49 * **freeze**: Lightweight software-only lower power state. Not quite as good as
   50               suspending, but with a faster resume time. Leaves devices like
   51               the cpu running, but in an idle state.
   52 * **mem**: Suspend to memory (what we want to do), store cpu state in memory,
   53            and effectively power down all devices except RAM (acpi state S3).
   54 * **disk**: Effectively hibernate. Depending on the system configuration, this
   55             just powers off the system after writing memory contents to disk.
   56             **Beware**: If you don't have swap set up, you won't be able to
   57             recover successfully from this state.
   58 
   59 If you want to learn more about Linux kernel power states, have a look at the
   60 kernel documentation over at
   61 `kernel.org <https://www.kernel.org/doc/html/latest/admin-guide/pm/sleep-states.html>`_.
   62 
   63 
   64 Suspending to Memory
   65 --------------------
   66 
   67 Suspending to memory is actually very easy. You just need to execute the
   68 following command.
   69 
   70 .. code-block:: sh
   71 
   72   echo mem > /sys/power/state
   73 
   74 
   75 Once done, your computer should suspend shortly thereafter. One key press and
   76 it should wake back up. Here's the issue though.
   77 
   78 
   79 Permission denied?
   80 ------------------
   81 
   82 If you aren't root when you run that command though, you'll see...
   83 
   84 .. code-block:: sh
   85 
   86   echo mem > /sys/power/state
   87   -bash: /sys/power/state: Permission denied
   88 
   89 This is because nearly every path in /sys is owned by root:root with
   90 permissions of 644 by default. I found that the easiest fix for this, so users
   91 could control system state without being granted root privs through sudo (or
   92 worse, su), was to write an init script that set permissions on the state file
   93 on service start. Here's the one I wrote.
   94 
   95 .. code-block:: sh
   96 
   97   #!/usr/bin/env bash
   98 
   99   start() {
  100     chmod 664 /sys/power/state
  101     chgrp power /sys/power/state
  102   }
  103 
  104   stop() {
  105     chmod 644 /sys/power/state
  106     chgrp root /sys/power/state
  107   }
  108 
  109   status() {
  110     if [ "$(stat -c '%a:%G' /sys/power/state)" = '664:power' ]; then
  111       printf 'running\n'
  112     else
  113       printf 'stopped\n'
  114     fi
  115   }
  116 
  117 
  118   if [ -z "$(getent group power)" ]; then
  119     printf "No 'power' group found. Cannot proceed\n"
  120     exit 2
  121   fi
  122 
  123   if [ -z "${1:-}" ]; then
  124     printf "usage: %s [start|stop|status]\n" "${0}"
  125     exit 1
  126   elif [ $1 = 'start' ] || [ $1 = 'stop' ] || [ $1 = 'status' ]; then
  127     ${1}
  128   else
  129     printf "Unknown action '%s'\n" "${1}"
  130     exit 1
  131   fi
  132 
  133 
  134 Note that this init script requires that a 'power' group exist. We only want
  135 trusted users to be able to suspend the system. To achieve this, the init
  136 script changes ownership of the state file to **root:power** and sets
  137 permissions to 664 (allow group write). To allow a user to suspend the system
  138 without elevated privileges, simply add them to the 'power' group.
  139 
  140 Admittedly, this restriction is a bit overkill since system suspension is
  141 really only useful for user-facing devices (eg: laptops and desktops), which
  142 often have only one user. Regardless, if you want to have more than one user,
  143 this init script will do the trick for you! If you find you don't care, I've
  144 written you a less safe variant of this script!
  145 
  146 .. code-block:: sh
  147 
  148   #!/usr/bin/env bash
  149 
  150   start() { chmod 666 /sys/power/state; }
  151 
  152   stop() { chmod 644 /sys/power/state; }
  153 
  154   status() {
  155     if [ "$(stat -c '%a' /sys/power/state)" = '666' ]; then
  156       printf 'running\n'
  157     else
  158       printf 'stopped\n'
  159     fi
  160   }
  161 
  162   if [ -z "${1:-}" ]; then
  163     printf "usage: %s [start|stop|status]\n" "${0}"
  164     exit 1
  165   elif [ $1 = 'start' ] || [ $1 = 'stop' ] || [ $1 = 'status' ]; then
  166     ${1}
  167   else
  168     printf "Unknown action %s\n" "${1}"
  169     exit 1
  170   fi
  171 
  172 Autobots! ... Suspend! ........

Generated by cgit