summaryrefslogtreecommitdiff
path: root/posts/linux:non-root-suspend.rst
blob: 00bbea94599f62481035e20f37fccfec60294f97 (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 Once done, your computer should suspend shortly thereafter. One key press and
   75 it should wake back up. Here's the issue though.
   76 
   77 
   78 Permission denied?
   79 ------------------
   80 
   81 If you aren't root when you run that command though, you'll see...
   82 
   83 .. code-block:: sh
   84 
   85   echo mem > /sys/power/state
   86   -bash: /sys/power/state: Permission denied
   87 
   88 This is because nearly every path in /sys is owned by root:root with
   89 permissions of 644 by default. I found that the easiest fix for this, so users
   90 could control system state without being granted root privs through sudo (or
   91 worse, su), was to write an init script that set permissions on the state file
   92 on service start. Here's the one I wrote.
   93 
   94 .. code-block:: sh
   95 
   96   #!/usr/bin/env bash
   97 
   98   start() {
   99     chmod 664 /sys/power/state
  100     chgrp power /sys/power/state
  101   }
  102 
  103   stop() {
  104     chmod 644 /sys/power/state
  105     chgrp root /sys/power/state
  106   }
  107 
  108   status() {
  109     if [ "$(stat -c '%a:%G' /sys/power/state)" = '664:power' ]; then
  110       printf 'running\n'
  111     else
  112       printf 'stopped\n'
  113     fi
  114   }
  115 
  116 
  117   if [ -z "$(getent group power)" ]; then
  118     printf "No 'power' group found. Cannot proceed\n"
  119     exit 2
  120   fi
  121 
  122   if [ -z "${1:-}" ]; then
  123     printf "usage: %s [start|stop|status]\n" "${0}"
  124     exit 1
  125   elif [ $1 = 'start' ] || [ $1 = 'stop' ] || [ $1 = 'status' ]; then
  126     ${1}
  127   else
  128     printf "Unknown action '%s'\n" "${1}"
  129     exit 1
  130   fi
  131 
  132 
  133 Note that this init script requires that a 'power' group exist. We only want
  134 trusted users to be able to suspend the system. To achieve this, the init
  135 script changes ownership of the state file to **root:power** and sets
  136 permissions to 664 (allow group write). To allow a user to suspend the system
  137 without elevated privileges, simply add them to the 'power' group.
  138 
  139 Admittedly, this restriction is a bit overkill since system suspension is
  140 really only useful for user-facing devices (eg: laptops and desktops), which
  141 often have only one user. Regardless, if you want to have more than one user,
  142 this init script will do the trick for you! If you find you don't care, I've
  143 written you a less safe variant of this script!
  144 
  145 .. code-block:: sh
  146 
  147   #!/usr/bin/env bash
  148 
  149   start() { chmod 666 /sys/power/state; }
  150 
  151   stop() { chmod 644 /sys/power/state; }
  152 
  153   status() {
  154     if [ "$(stat -c '%a' /sys/power/state)" = '666' ]; then
  155       printf 'running\n'
  156     else
  157       printf 'stopped\n'
  158     fi
  159   }
  160 
  161   if [ -z "${1:-}" ]; then
  162     printf "usage: %s [start|stop|status]\n" "${0}"
  163     exit 1
  164   elif [ $1 = 'start' ] || [ $1 = 'stop' ] || [ $1 = 'status' ]; then
  165     ${1}
  166   else
  167     printf "Unknown action %s\n" "${1}"
  168     exit 1
  169   fi
  170 
  171 Autobots! ... Suspend! ........

Generated by cgit