chiark / gitweb /
sleep: Move the error level debugging for write_mode and write_state
[elogind.git] / src / sleep / sleep.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3   This file is part of systemd.
4
5   Copyright 2012 Lennart Poettering
6   Copyright 2013 Zbigniew JÄ™drzejewski-Szmek
7   Copyright 2010-2017 Canonical
8   Copyright 2018 Dell Inc.
9
10   systemd is free software; you can redistribute it and/or modify it
11   under the terms of the GNU Lesser General Public License as published by
12   the Free Software Foundation; either version 2.1 of the License, or
13   (at your option) any later version.
14
15   systemd is distributed in the hope that it will be useful, but
16   WITHOUT ANY WARRANTY; without even the implied warranty of
17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18   Lesser General Public License for more details.
19
20   You should have received a copy of the GNU Lesser General Public License
21   along with systemd; If not, see <http://www.gnu.org/licenses/>.
22 ***/
23
24 //#include <errno.h>
25 //#include <getopt.h>
26 //#include <linux/fiemap.h>
27 //#include <stdio.h>
28
29 #include "sd-messages.h"
30
31 //#include "parse-util.h"
32 #include "def.h"
33 #include "exec-util.h"
34 #include "fd-util.h"
35 #include "fileio.h"
36 //#include "log.h"
37 //#include "sleep-config.h"
38 //#include "stdio-util.h"
39 //#include "string-util.h"
40 #include "strv.h"
41 //#include "util.h"
42
43 /// Additional includes needed by elogind
44 #include "sleep.h"
45
46 static char* arg_verb = NULL;
47
48 static int write_hibernate_location_info(void) {
49         _cleanup_free_ char *device = NULL, *type = NULL;
50         _cleanup_free_ struct fiemap *fiemap = NULL;
51         char offset_str[DECIMAL_STR_MAX(uint64_t)];
52         char device_str[DECIMAL_STR_MAX(uint64_t)];
53         _cleanup_close_ int fd = -1;
54         struct stat stb;
55         uint64_t offset;
56         int r;
57
58         r = find_hibernate_location(&device, &type, NULL, NULL);
59         if (r < 0)
60                 return log_debug_errno(r, "Unable to find hibernation location: %m");
61
62         /* if it's a swap partition, we just write the disk to /sys/power/resume */
63         if (streq(type, "partition"))
64                 return write_string_file("/sys/power/resume", device, 0);
65         else if (!streq(type, "file"))
66                 return log_debug_errno(EINVAL, "Invalid hibernate type %s: %m",
67                                        type);
68
69         /* Only available in 4.17+ */
70         if (access("/sys/power/resume_offset", F_OK) < 0) {
71                 if (errno == ENOENT)
72                         return 0;
73                 return log_debug_errno(errno, "/sys/power/resume_offset unavailable: %m");
74         }
75
76         r = access("/sys/power/resume_offset", W_OK);
77         if (r < 0)
78                 return log_debug_errno(errno, "/sys/power/resume_offset not writeable: %m");
79
80         fd = open(device, O_RDONLY | O_CLOEXEC | O_NONBLOCK);
81         if (fd < 0)
82                 return log_debug_errno(errno, "Unable to open '%s': %m", device);
83         r = fstat(fd, &stb);
84         if (r < 0)
85                 return log_debug_errno(errno, "Unable to stat %s: %m", device);
86         r = read_fiemap(fd, &fiemap);
87         if (r < 0)
88                 return log_debug_errno(r, "Unable to read extent map for '%s': %m",
89                                        device);
90         if (fiemap->fm_mapped_extents == 0) {
91                 log_debug("No extents found in '%s'", device);
92                 return -EINVAL;
93         }
94         offset = fiemap->fm_extents[0].fe_physical / page_size();
95         xsprintf(offset_str, "%" PRIu64, offset);
96         r = write_string_file("/sys/power/resume_offset", offset_str, 0);
97         if (r < 0)
98                 return log_debug_errno(r, "Failed to write offset '%s': %m",
99                                        offset_str);
100
101         xsprintf(device_str, "%lx", (unsigned long)stb.st_dev);
102         r = write_string_file("/sys/power/resume", device_str, 0);
103         if (r < 0)
104                 return log_debug_errno(r, "Failed to write device '%s': %m",
105                                        device_str);
106         return 0;
107 }
108
109 static int write_mode(char **modes) {
110         int r = 0;
111         char **mode;
112
113         STRV_FOREACH(mode, modes) {
114                 int k;
115
116                 k = write_string_file("/sys/power/disk", *mode, 0);
117                 if (k == 0)
118                         return 0;
119
120                 log_debug_errno(k, "Failed to write '%s' to /sys/power/disk: %m",
121                                 *mode);
122                 if (r == 0)
123                         r = k;
124         }
125
126         return r;
127 }
128
129 static int write_state(FILE **f, char **states) {
130         char **state;
131         int r = 0;
132
133         STRV_FOREACH(state, states) {
134                 int k;
135
136                 k = write_string_stream(*f, *state, 0);
137                 if (k == 0)
138                         return 0;
139                 log_debug_errno(k, "Failed to write '%s' to /sys/power/state: %m",
140                                 *state);
141                 if (r == 0)
142                         r = k;
143
144                 fclose(*f);
145                 *f = fopen("/sys/power/state", "we");
146                 if (!*f)
147                         return -errno;
148         }
149
150         return r;
151 }
152
153 static int execute(char **modes, char **states) {
154
155         char *arguments[] = {
156                 NULL,
157                 (char*) "pre",
158                 arg_verb,
159                 NULL
160         };
161         static const char* const dirs[] = {
162                 SYSTEM_SLEEP_PATH,
163                 NULL
164         };
165
166         int r;
167         _cleanup_fclose_ FILE *f = NULL;
168
169         /* This file is opened first, so that if we hit an error,
170          * we can abort before modifying any state. */
171         f = fopen("/sys/power/state", "we");
172         if (!f)
173                 return log_error_errno(errno, "Failed to open /sys/power/state: %m");
174
175         /* Configure the hibernation mode */
176         if (!strv_isempty(modes)) {
177                 r = write_hibernate_location_info();
178                 if (r < 0)
179                         return log_error_errno(r, "Failed to write hibernation disk offset: %m");
180                 r = write_mode(modes);
181                 if (r < 0)
182                         return log_error_errno(r, "Failed to write mode to /sys/power/disk: %m");;
183         }
184
185         execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments);
186
187         log_struct(LOG_INFO,
188                    "MESSAGE_ID=" SD_MESSAGE_SLEEP_START_STR,
189                    LOG_MESSAGE("Suspending system..."),
190                    "SLEEP=%s", arg_verb,
191                    NULL);
192
193         r = write_state(&f, states);
194         if (r < 0)
195                 return log_error_errno(r, "Failed to write /sys/power/state: %m");
196
197         log_struct(LOG_INFO,
198                    "MESSAGE_ID=" SD_MESSAGE_SLEEP_STOP_STR,
199                    LOG_MESSAGE("System resumed."),
200                    "SLEEP=%s", arg_verb,
201                    NULL);
202
203         arguments[1] = (char*) "post";
204         execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments);
205
206         return r;
207 }
208
209 static int read_wakealarm(uint64_t *result) {
210         _cleanup_free_ char *t = NULL;
211
212         if (read_one_line_file("/sys/class/rtc/rtc0/since_epoch", &t) >= 0)
213                 return safe_atou64(t, result);
214         return -EBADF;
215 }
216
217 static int write_wakealarm(const char *str) {
218
219         _cleanup_fclose_ FILE *f = NULL;
220         int r;
221
222         f = fopen("/sys/class/rtc/rtc0/wakealarm", "we");
223         if (!f)
224                 return log_error_errno(errno, "Failed to open /sys/class/rtc/rtc0/wakealarm: %m");
225
226         r = write_string_stream(f, str, 0);
227         if (r < 0)
228                 return log_error_errno(r, "Failed to write '%s' to /sys/class/rtc/rtc0/wakealarm: %m", str);
229
230         return 0;
231 }
232
233 static int execute_s2h(usec_t hibernate_delay_sec) {
234
235         _cleanup_strv_free_ char **hibernate_modes = NULL, **hibernate_states = NULL,
236                                  **suspend_modes = NULL, **suspend_states = NULL;
237         usec_t orig_time, cmp_time;
238         char time_str[DECIMAL_STR_MAX(uint64_t)];
239         int r;
240
241         r = parse_sleep_config("suspend", &suspend_modes, &suspend_states,
242                                NULL);
243         if (r < 0)
244                 return r;
245
246         r = parse_sleep_config("hibernate", &hibernate_modes,
247                                &hibernate_states, NULL);
248         if (r < 0)
249                 return r;
250
251         r = read_wakealarm(&orig_time);
252         if (r < 0)
253                 return log_error_errno(errno, "Failed to read time: %d", r);
254
255         orig_time += hibernate_delay_sec / USEC_PER_SEC;
256         xsprintf(time_str, "%" PRIu64, orig_time);
257
258         r = write_wakealarm(time_str);
259         if (r < 0)
260                 return r;
261
262         log_debug("Set RTC wake alarm for %s", time_str);
263
264         r = execute(suspend_modes, suspend_states);
265         if (r < 0)
266                 return r;
267
268         r = read_wakealarm(&cmp_time);
269         if (r < 0)
270                 return log_error_errno(errno, "Failed to read time: %d", r);
271
272         /* reset RTC */
273         r = write_wakealarm("0");
274         if (r < 0)
275                 return r;
276
277         log_debug("Woke up at %"PRIu64, cmp_time);
278
279         /* if woken up after alarm time, hibernate */
280         if (cmp_time >= orig_time)
281                 r = execute(hibernate_modes, hibernate_states);
282
283         return r;
284 }
285
286 #if 0 /// elogind calls execute() by itself and does not need another binary
287 static void help(void) {
288         printf("%s COMMAND\n\n"
289                "Suspend the system, hibernate the system, or both.\n\n"
290                "Commands:\n"
291                "  -h --help            Show this help and exit\n"
292                "  --version            Print version string and exit\n"
293                "  suspend              Suspend the system\n"
294                "  hibernate            Hibernate the system\n"
295                "  hybrid-sleep         Both hibernate and suspend the system\n"
296                "  suspend-then-hibernate Initially suspend and then hibernate\n"
297                "                       the system after a fixed period of time\n"
298                , program_invocation_short_name);
299 }
300
301 static int parse_argv(int argc, char *argv[]) {
302         enum {
303                 ARG_VERSION = 0x100,
304         };
305
306         static const struct option options[] = {
307                 { "help",         no_argument,       NULL, 'h'           },
308                 { "version",      no_argument,       NULL, ARG_VERSION   },
309                 {}
310         };
311
312         int c;
313
314         assert(argc >= 0);
315         assert(argv);
316
317         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
318                 switch(c) {
319                 case 'h':
320                         help();
321                         return 0; /* done */
322
323                 case ARG_VERSION:
324                         return version();
325
326                 case '?':
327                         return -EINVAL;
328
329                 default:
330                         assert_not_reached("Unhandled option");
331                 }
332
333         if (argc - optind != 1) {
334                 log_error("Usage: %s COMMAND",
335                           program_invocation_short_name);
336                 return -EINVAL;
337         }
338
339         arg_verb = argv[optind];
340
341         if (!streq(arg_verb, "suspend") &&
342             !streq(arg_verb, "hibernate") &&
343             !streq(arg_verb, "hybrid-sleep") &&
344             !streq(arg_verb, "suspend-then-hibernate")) {
345                 log_error("Unknown command '%s'.", arg_verb);
346                 return -EINVAL;
347         }
348
349         return 1 /* work to do */;
350 }
351
352 int main(int argc, char *argv[]) {
353         _cleanup_strv_free_ char **modes = NULL, **states = NULL;
354         usec_t delay = 0;
355         int r;
356
357         log_set_target(LOG_TARGET_AUTO);
358         log_parse_environment();
359         log_open();
360
361         r = parse_argv(argc, argv);
362         if (r <= 0)
363                 goto finish;
364
365         r = parse_sleep_config(arg_verb, &modes, &states, &delay);
366         if (r < 0)
367                 goto finish;
368
369         if (streq(arg_verb, "suspend-then-hibernate"))
370                 r = execute_s2h(delay);
371         else
372                 r = execute(modes, states);
373 finish:
374         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
375 }
376 #else
377 int do_sleep(const char *verb, char **modes, char **states) {
378         assert(verb);
379         arg_verb = (char*)verb;
380         return execute(modes, states);
381 }
382 #endif // 0