chiark / gitweb /
event: rework sd-event exit logic
[elogind.git] / src / hostname / hostnamed.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2011 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <dlfcn.h>
26
27 #include "util.h"
28 #include "strv.h"
29 #include "def.h"
30 #include "virt.h"
31 #include "env-util.h"
32 #include "fileio-label.h"
33 #include "label.h"
34 #include "bus-util.h"
35 #include "event-util.h"
36
37 enum {
38         PROP_HOSTNAME,
39         PROP_STATIC_HOSTNAME,
40         PROP_PRETTY_HOSTNAME,
41         PROP_ICON_NAME,
42         PROP_CHASSIS,
43         _PROP_MAX
44 };
45
46 typedef struct Context {
47         char *data[_PROP_MAX];
48         Hashmap *polkit_registry;
49 } Context;
50
51 static void context_reset(Context *c) {
52         int p;
53
54         assert(c);
55
56         for (p = 0; p < _PROP_MAX; p++) {
57                 free(c->data[p]);
58                 c->data[p] = NULL;
59         }
60 }
61
62 static void context_free(Context *c, sd_bus *bus) {
63         assert(c);
64
65         context_reset(c);
66         bus_verify_polkit_async_registry_free(bus, c->polkit_registry);
67 }
68
69 static int context_read_data(Context *c) {
70         int r;
71
72         assert(c);
73
74         context_reset(c);
75
76         c->data[PROP_HOSTNAME] = gethostname_malloc();
77         if (!c->data[PROP_HOSTNAME])
78                 return -ENOMEM;
79
80         r = read_one_line_file("/etc/hostname", &c->data[PROP_STATIC_HOSTNAME]);
81         if (r < 0 && r != -ENOENT)
82                 return r;
83
84         r = parse_env_file("/etc/machine-info", NEWLINE,
85                            "PRETTY_HOSTNAME", &c->data[PROP_PRETTY_HOSTNAME],
86                            "ICON_NAME", &c->data[PROP_ICON_NAME],
87                            "CHASSIS", &c->data[PROP_CHASSIS],
88                            NULL);
89         if (r < 0 && r != -ENOENT)
90                 return r;
91
92         return 0;
93 }
94
95 static bool check_nss(void) {
96         void *dl;
97
98         dl = dlopen("libnss_myhostname.so.2", RTLD_LAZY);
99         if (dl) {
100                 dlclose(dl);
101                 return true;
102         }
103
104         return false;
105 }
106
107 static bool valid_chassis(const char *chassis) {
108
109         assert(chassis);
110
111         return nulstr_contains(
112                         "vm\0"
113                         "container\0"
114                         "desktop\0"
115                         "laptop\0"
116                         "server\0"
117                         "tablet\0"
118                         "handset\0",
119                         chassis);
120 }
121
122 static const char* fallback_chassis(void) {
123         int r;
124         char *type;
125         unsigned t;
126         Virtualization v;
127
128         v = detect_virtualization(NULL);
129
130         if (v == VIRTUALIZATION_VM)
131                 return "vm";
132         if (v == VIRTUALIZATION_CONTAINER)
133                 return "container";
134
135         r = read_one_line_file("/sys/firmware/acpi/pm_profile", &type);
136         if (r < 0)
137                 goto try_dmi;
138
139         r = safe_atou(type, &t);
140         free(type);
141         if (r < 0)
142                 goto try_dmi;
143
144         /* We only list the really obvious cases here as the ACPI data
145          * is not really super reliable.
146          *
147          * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
148          *
149          * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
150          */
151
152         switch(t) {
153
154         case 1:
155         case 3:
156         case 6:
157                 return "desktop";
158
159         case 2:
160                 return "laptop";
161
162         case 4:
163         case 5:
164         case 7:
165                 return "server";
166
167         case 8:
168                 return "tablet";
169         }
170
171 try_dmi:
172         r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type);
173         if (r < 0)
174                 return NULL;
175
176         r = safe_atou(type, &t);
177         free(type);
178         if (r < 0)
179                 return NULL;
180
181         /* We only list the really obvious cases here. The DMI data is
182            unreliable enough, so let's not do any additional guesswork
183            on top of that.
184
185            See the SMBIOS Specification 2.7.1 section 7.4.1 for
186            details about the values listed here:
187
188            http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
189          */
190
191         switch (t) {
192
193         case 0x3:
194         case 0x4:
195         case 0x6:
196         case 0x7:
197                 return "desktop";
198
199         case 0x8:
200         case 0x9:
201         case 0xA:
202         case 0xE:
203                 return "laptop";
204
205         case 0xB:
206                 return "handset";
207
208         case 0x11:
209         case 0x1C:
210                 return "server";
211         }
212
213         return NULL;
214 }
215
216 static char* context_fallback_icon_name(Context *c) {
217         const char *chassis;
218
219         assert(c);
220
221         if (!isempty(c->data[PROP_CHASSIS]))
222                 return strappend("computer-", c->data[PROP_CHASSIS]);
223
224         chassis = fallback_chassis();
225         if (chassis)
226                 return strappend("computer-", chassis);
227
228         return strdup("computer");
229 }
230
231 static int context_write_data_hostname(Context *c) {
232         const char *hn;
233
234         assert(c);
235
236         if (isempty(c->data[PROP_HOSTNAME]))
237                 hn = "localhost";
238         else
239                 hn = c->data[PROP_HOSTNAME];
240
241         if (sethostname(hn, strlen(hn)) < 0)
242                 return -errno;
243
244         return 0;
245 }
246
247 static int context_write_data_static_hostname(Context *c) {
248
249         assert(c);
250
251         if (isempty(c->data[PROP_STATIC_HOSTNAME])) {
252
253                 if (unlink("/etc/hostname") < 0)
254                         return errno == ENOENT ? 0 : -errno;
255
256                 return 0;
257         }
258         return write_string_file_atomic_label("/etc/hostname", c->data[PROP_STATIC_HOSTNAME]);
259 }
260
261 static int context_write_data_other(Context *c) {
262
263         static const char * const name[_PROP_MAX] = {
264                 [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
265                 [PROP_ICON_NAME] = "ICON_NAME",
266                 [PROP_CHASSIS] = "CHASSIS"
267         };
268
269         _cleanup_strv_free_ char **l = NULL;
270         int r, p;
271
272         assert(c);
273
274         r = load_env_file("/etc/machine-info", NULL, &l);
275         if (r < 0 && r != -ENOENT)
276                 return r;
277
278         for (p = 2; p < _PROP_MAX; p++) {
279                 char *t, **u;
280
281                 assert(name[p]);
282
283                 if (isempty(c->data[p]))  {
284                         strv_env_unset(l, name[p]);
285                         continue;
286                 }
287
288                 if (asprintf(&t, "%s=%s", name[p], strempty(c->data[p])) < 0)
289                         return -ENOMEM;
290
291                 u = strv_env_set(l, t);
292                 free(t);
293
294                 if (!u)
295                         return -ENOMEM;
296
297                 strv_free(l);
298                 l = u;
299         }
300
301         if (strv_isempty(l)) {
302
303                 if (unlink("/etc/machine-info") < 0)
304                         return errno == ENOENT ? 0 : -errno;
305
306                 return 0;
307         }
308
309         return write_env_file_label("/etc/machine-info", l);
310 }
311
312 static int property_get_icon_name(
313                 sd_bus *bus,
314                 const char *path,
315                 const char *interface,
316                 const char *property,
317                 sd_bus_message *reply,
318                 void *userdata,
319                 sd_bus_error *error) {
320
321         _cleanup_free_ char *n = NULL;
322         Context *c = userdata;
323         const char *name;
324
325         if (isempty(c->data[PROP_ICON_NAME]))
326                 name = n = context_fallback_icon_name(c);
327         else
328                 name = c->data[PROP_ICON_NAME];
329
330         if (!name)
331                 return -ENOMEM;
332
333         return sd_bus_message_append(reply, "s", name);
334 }
335
336 static int property_get_chassis(
337                 sd_bus *bus,
338                 const char *path,
339                 const char *interface,
340                 const char *property,
341                 sd_bus_message *reply,
342                 void *userdata,
343                 sd_bus_error *error) {
344
345         Context *c = userdata;
346         const char *name;
347
348         if (isempty(c->data[PROP_CHASSIS]))
349                 name = fallback_chassis();
350         else
351                 name = c->data[PROP_CHASSIS];
352
353         return sd_bus_message_append(reply, "s", name);
354 }
355
356 static int method_set_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
357         Context *c = userdata;
358         const char *name;
359         int interactive;
360         char *h;
361         int r;
362
363         r = sd_bus_message_read(m, "sb", &name, &interactive);
364         if (r < 0)
365                 return r;
366
367         if (isempty(name))
368                 name = c->data[PROP_STATIC_HOSTNAME];
369
370         if (isempty(name))
371                 name = "localhost";
372
373         if (!hostname_is_valid(name))
374                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name);
375
376         if (streq_ptr(name, c->data[PROP_HOSTNAME]))
377                 return sd_bus_reply_method_return(m, NULL);
378
379         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-hostname", interactive, error, method_set_hostname, c);
380         if (r < 0)
381                 return r;
382         if (r == 0)
383                 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
384
385         h = strdup(name);
386         if (!h)
387                 return -ENOMEM;
388
389         free(c->data[PROP_HOSTNAME]);
390         c->data[PROP_HOSTNAME] = h;
391
392         r = context_write_data_hostname(c);
393         if (r < 0) {
394                 log_error("Failed to set host name: %s", strerror(-r));
395                 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
396         }
397
398         log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME]));
399
400         sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL);
401
402         return sd_bus_reply_method_return(m, NULL);
403 }
404
405 static int method_set_static_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
406         Context *c = userdata;
407         const char *name;
408         int interactive;
409         int r;
410
411         r = sd_bus_message_read(m, "sb", &name, &interactive);
412         if (r < 0)
413                 return r;
414
415         if (isempty(name))
416                 name = NULL;
417
418         if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME]))
419                 return sd_bus_reply_method_return(m, NULL);
420
421         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-static-hostname", interactive, error, method_set_static_hostname, c);
422         if (r < 0)
423                 return r;
424         if (r == 0)
425                 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
426
427         if (isempty(name)) {
428                 free(c->data[PROP_STATIC_HOSTNAME]);
429                 c->data[PROP_STATIC_HOSTNAME] = NULL;
430         } else {
431                 char *h;
432
433                 if (!hostname_is_valid(name))
434                         return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name);
435
436                 h = strdup(name);
437                 if (!h)
438                         return -ENOMEM;
439
440                 free(c->data[PROP_STATIC_HOSTNAME]);
441                 c->data[PROP_STATIC_HOSTNAME] = h;
442         }
443
444         r = context_write_data_static_hostname(c);
445         if (r < 0) {
446                 log_error("Failed to write static host name: %s", strerror(-r));
447                 return sd_bus_error_set_errnof(error, r, "Failed to set static hostname: %s", strerror(-r));
448         }
449
450         log_info("Changed static host name to '%s'", strna(c->data[PROP_STATIC_HOSTNAME]));
451
452         sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL);
453
454         return sd_bus_reply_method_return(m, NULL);
455 }
456
457 static int set_machine_info(Context *c, sd_bus *bus, sd_bus_message *m, int prop, sd_bus_message_handler_t cb, sd_bus_error *error) {
458         int interactive;
459         const char *name;
460         int r;
461
462         assert(c);
463         assert(bus);
464         assert(m);
465
466         r = sd_bus_message_read(m, "sb", &name, &interactive);
467         if (r < 0)
468                 return r;
469
470         if (isempty(name))
471                 name = NULL;
472
473         if (streq_ptr(name, c->data[prop]))
474                 return sd_bus_reply_method_return(m, NULL);
475
476         /* Since the pretty hostname should always be changed at the
477          * same time as the static one, use the same policy action for
478          * both... */
479
480         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, prop == PROP_PRETTY_HOSTNAME ?
481                           "org.freedesktop.hostname1.set-static-hostname" :
482                           "org.freedesktop.hostname1.set-machine-info", interactive, error, cb, c);
483         if (r < 0)
484                 return r;
485         if (r == 0)
486                 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
487
488         if (isempty(name)) {
489                 free(c->data[prop]);
490                 c->data[prop] = NULL;
491         } else {
492                 char *h;
493
494                 /* The icon name might ultimately be used as file
495                  * name, so better be safe than sorry */
496
497                 if (prop == PROP_ICON_NAME && !filename_is_safe(name))
498                         return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name);
499                 if (prop == PROP_PRETTY_HOSTNAME &&
500                     (string_has_cc(name) || chars_intersect(name, "\t")))
501                         return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name);
502                 if (prop == PROP_CHASSIS && !valid_chassis(name))
503                         return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name);
504
505                 h = strdup(name);
506                 if (!h)
507                         return -ENOMEM;
508
509                 free(c->data[prop]);
510                 c->data[prop] = h;
511         }
512
513         r = context_write_data_other(c);
514         if (r < 0) {
515                 log_error("Failed to write machine info: %s", strerror(-r));
516                 return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %s", strerror(-r));
517         }
518
519         log_info("Changed %s to '%s'",
520                  prop == PROP_PRETTY_HOSTNAME ? "pretty host name" :
521                  prop == PROP_CHASSIS ? "chassis" : "icon name", strna(c->data[prop]));
522
523         sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
524                                        prop == PROP_PRETTY_HOSTNAME ? "PrettyHostname" :
525                                        prop == PROP_CHASSIS ? "Chassis" : "IconName", NULL);
526
527         return sd_bus_reply_method_return(m, NULL);
528 }
529
530 static int method_set_pretty_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
531         return set_machine_info(userdata, bus, m, PROP_PRETTY_HOSTNAME, method_set_pretty_hostname, error);
532 }
533
534 static int method_set_icon_name(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
535         return set_machine_info(userdata, bus, m, PROP_ICON_NAME, method_set_icon_name, error);
536 }
537
538 static int method_set_chassis(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
539         return set_machine_info(userdata, bus, m, PROP_CHASSIS, method_set_chassis, error);
540 }
541
542 static const sd_bus_vtable hostname_vtable[] = {
543         SD_BUS_VTABLE_START(0),
544         SD_BUS_PROPERTY("Hostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOSTNAME, 0),
545         SD_BUS_PROPERTY("StaticHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_STATIC_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
546         SD_BUS_PROPERTY("PrettyHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
547         SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
548         SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
549         SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
550         SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
551         SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
552         SD_BUS_METHOD("SetIconName", "sb", NULL, method_set_icon_name, SD_BUS_VTABLE_UNPRIVILEGED),
553         SD_BUS_METHOD("SetChassis", "sb", NULL, method_set_chassis, SD_BUS_VTABLE_UNPRIVILEGED),
554         SD_BUS_VTABLE_END,
555 };
556
557 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
558         _cleanup_bus_unref_ sd_bus *bus = NULL;
559         int r;
560
561         assert(c);
562         assert(event);
563         assert(_bus);
564
565         r = sd_bus_default_system(&bus);
566         if (r < 0) {
567                 log_error("Failed to get system bus connection: %s", strerror(-r));
568                 return r;
569         }
570
571         r = sd_bus_add_object_vtable(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable, c);
572         if (r < 0) {
573                 log_error("Failed to register object: %s", strerror(-r));
574                 return r;
575         }
576
577         r = sd_bus_request_name(bus, "org.freedesktop.hostname1", 0);
578         if (r < 0) {
579                 log_error("Failed to register name: %s", strerror(-r));
580                 return r;
581         }
582
583         r = sd_bus_attach_event(bus, event, 0);
584         if (r < 0) {
585                 log_error("Failed to attach bus to event loop: %s", strerror(-r));
586                 return r;
587         }
588
589         *_bus = bus;
590         bus = NULL;
591
592         return 0;
593 }
594
595 int main(int argc, char *argv[]) {
596         Context context = {};
597
598         _cleanup_event_unref_ sd_event *event = NULL;
599         _cleanup_bus_unref_ sd_bus *bus = NULL;
600         int r;
601
602         log_set_target(LOG_TARGET_AUTO);
603         log_parse_environment();
604         log_open();
605
606         umask(0022);
607         label_init("/etc");
608
609         if (argc != 1) {
610                 log_error("This program takes no arguments.");
611                 r = -EINVAL;
612                 goto finish;
613         }
614
615         if (!check_nss())
616                 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
617
618         if (argc != 1) {
619                 log_error("This program takes no arguments.");
620                 r = -EINVAL;
621                 goto finish;
622         }
623
624         r = sd_event_default(&event);
625         if (r < 0) {
626                 log_error("Failed to allocate event loop: %s", strerror(-r));
627                 goto finish;
628         }
629
630         sd_event_set_watchdog(event, true);
631
632         r = connect_bus(&context, event, &bus);
633         if (r < 0)
634                 goto finish;
635
636         r = context_read_data(&context);
637         if (r < 0) {
638                 log_error("Failed to read timezone data: %s", strerror(-r));
639                 goto finish;
640         }
641
642         r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC);
643         if (r < 0) {
644                 log_error("Failed to run event loop: %s", strerror(-r));
645                 goto finish;
646         }
647
648 finish:
649         context_free(&context, bus);
650
651         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
652 }