075a7f3f9ea70cbbfeda205f5282195e45f0f699
[systembsd.git] / src / interfaces / hostnamed / hostnamed.c
1 /*
2 * Copyright (c) 2014 Ian Sutton <ian@kremlin.cc>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <unistd.h>
18 #include <limits.h>
19 #include <signal.h>
20
21 #include <sys/param.h>
22 #include <sys/sysctl.h>
23 #include <string.h>
24
25 #include <glib/gprintf.h>
26 #include <glib-unix.h>
27
28 #include "hostnamed-gen.h"
29 #include "hostnamed.h"
30
31 GPtrArray *hostnamed_freeable;
32 Hostname1 *hostnamed_interf;
33
34 GMainLoop *hostnamed_loop;
35
36 guint bus_descriptor;
37 gboolean dbus_interface_exported; /* reliable because of gdbus operational guarantees */
38
39 /* --- begin method/property/dbus signal code --- */
40
41 /* add any sysctl strings that suggest virtualization here */
42 const gchar* vmstring_list[] = {
43 "QEMU Virtual CPU",
44 "SmartDC HVM",
45 "KVM",
46 "VirtualBox"
47 };
48
49 static gboolean is_vm;
50
51 static gboolean
52 on_handle_set_hostname(Hostname1 *hn1_passed_interf,
53 GDBusMethodInvocation *invoc,
54 const gchar *greet,
55 gpointer data) {
56 return FALSE;
57 }
58
59 static gboolean
60 on_handle_set_static_hostname(Hostname1 *hn1_passed_interf,
61 GDBusMethodInvocation *invoc,
62 const gchar *greet,
63 gpointer data) {
64 return FALSE;
65 }
66
67 static gboolean
68 on_handle_set_pretty_hostname(Hostname1 *hn1_passed_interf,
69 GDBusMethodInvocation *invoc,
70 const gchar *greet,
71 gpointer data) {
72 return FALSE;
73 }
74
75 static gboolean
76 on_handle_set_chassis(Hostname1 *hn1_passed_interf,
77 GDBusMethodInvocation *invoc,
78 const gchar *greet,
79 gpointer data) {
80 return FALSE;
81 }
82
83 static gboolean
84 on_handle_set_icon_name(Hostname1 *hn1_passed_interf,
85 GDBusMethodInvocation *invoc,
86 const gchar *greet,
87 gpointer data) {
88 return FALSE;
89 }
90
91 /* note: all hostnamed/hostname1's properties are read-only,
92 * and do not need set_ functions, gdbus-codegen realized
93 * this from the XML and handled the to-be error of trying
94 * to set a read-only property's value
95 */
96
97 const gchar *
98 our_get_hostname() {
99
100 gchar *hostname_buf, *ret;
101 size_t hostname_divider;
102
103 hostname_buf = (gchar*) g_malloc0(MAXHOSTNAMELEN);
104 ret = (gchar*) g_malloc0(MAXHOSTNAMELEN);
105
106 g_ptr_array_add(hostnamed_freeable, hostname_buf);
107 g_ptr_array_add(hostnamed_freeable, ret);
108
109 if(gethostname(hostname_buf, MAXHOSTNAMELEN) || g_strcmp0(hostname_buf, "") == 0)
110 return "localhost";
111
112 hostname_divider = strcspn(hostname_buf, ".");
113
114 return strncpy(ret, hostname_buf, hostname_divider);
115 }
116
117 const gchar *
118 our_get_static_hostname() {
119
120 const gchar *pretty_hostname;
121 const gchar *ret;
122
123 pretty_hostname = our_get_pretty_hostname();
124
125 if(g_strcmp0(pretty_hostname, "") == 0)
126 ret = our_get_hostname();
127
128 else if((ret = g_hostname_to_ascii(pretty_hostname))) {
129
130 g_ptr_array_add(hostnamed_freeable, (gpointer)ret);
131 return ret;
132 }
133
134 return ret;
135 }
136
137 const gchar *
138 our_get_pretty_hostname() {
139
140 GKeyFile *config;
141 gchar *ret;
142
143 config = g_key_file_new();
144
145 if(g_key_file_load_from_file(config, "/etc/systemd_compat.conf", G_KEY_FILE_NONE, NULL)
146 && (ret = g_key_file_get_value(config, "hostnamed", "PrettyHostname", NULL))) { /* ret might need to be freed, docs dont specify but i am suspicious */
147
148 g_key_file_unref(config);
149 return ret;
150 }
151
152 if(config)
153 g_free(config);
154
155 return "";
156 }
157
158 const gchar *
159 our_get_chassis() {
160
161 char *hwproduct, *hwmodel;
162 size_t hwproduct_size, hwmodel_size;
163 int hwproduct_name[2], hwmodel_name[2];
164
165 hwproduct_name[0] = CTL_HW;
166 hwproduct_name[1] = HW_PRODUCT;
167
168 hwmodel_name[0] = CTL_HW;
169 hwmodel_name[1] = HW_MODEL;
170
171 /* pass NULL buffer to check size first, then pass hw to be filled according to freshly-set hw_size */
172 if(sysctl(&hwproduct_name, 2, NULL, &hwproduct_size, NULL, 0) || sysctl(&hwproduct_name, 2, hwproduct, &hwproduct_size, NULL, 0))
173 return "desktop"; /* TODO error properly here */
174
175 if(sysctl(&hwmodel_name, 2, NULL, &hwmodel_size, NULL, 0) || sysctl(&hwmodel_name, 2, hwmodel, &hwmodel_size, NULL, 0))
176 return "desktop"; /* TODO error properly here */
177
178 if(test_against_known_vm_strings(hwproduct) || test_against_known_vm_strings(hwmodel))
179 return "vm"; /*TODO differentiate between VMs (hardware virt, seperate kernel) and containers (paravirt, shared kernel)
180
181 /* TODO: test for laptop, if not, dmidecode for desktop vs. server
182 * probably move this code to vm test func and set a global after running it early, once */
183
184 return "desktop";
185 }
186
187 const gchar *
188 our_get_icon_name() {
189
190 return "TODO";
191 }
192
193 const gchar *
194 our_get_kernel_name() {
195
196 return "TODO";
197 }
198
199 const gchar *
200 our_get_kernel_version() {
201
202 return "TODO";
203 }
204
205 const gchar *
206 our_get_kernel_release() {
207
208 return "TODO";
209 }
210
211 const gchar *
212 our_get_os_cpename() {
213
214 return "TODO";
215 }
216
217 const gchar *
218 our_get_os_pretty_name() {
219
220 return "TODO";
221 }
222
223 /* --- end method/property/dbus signal code, begin bus/name handlers --- */
224
225 static void hostnamed_on_bus_acquired(GDBusConnection *conn,
226 const gchar *name,
227 gpointer user_data) {
228
229 g_printf("got bus/name, exporting %s's interface...\n", name);
230
231 hostnamed_interf = hostname1_skeleton_new();
232
233 /* attach function pointers to generated struct's method handlers */
234 g_signal_connect(hostnamed_interf, "handle-set-hostname", G_CALLBACK(on_handle_set_hostname), NULL);
235 g_signal_connect(hostnamed_interf, "handle-set-static-hostname", G_CALLBACK(on_handle_set_static_hostname), NULL);
236 g_signal_connect(hostnamed_interf, "handle-set-pretty-hostname", G_CALLBACK(on_handle_set_pretty_hostname), NULL);
237 g_signal_connect(hostnamed_interf, "handle-set-chassis", G_CALLBACK(on_handle_set_chassis), NULL);
238 g_signal_connect(hostnamed_interf, "handle-set-icon-name", G_CALLBACK(on_handle_set_icon_name), NULL);
239
240 /* set our properties before export */
241 hostname1_set_hostname(hostnamed_interf, our_get_hostname());
242 hostname1_set_static_hostname(hostnamed_interf, our_get_static_hostname());
243 hostname1_set_pretty_hostname(hostnamed_interf, our_get_pretty_hostname());
244 hostname1_set_chassis(hostnamed_interf, our_get_chassis());
245 hostname1_set_icon_name(hostnamed_interf, our_get_icon_name());
246 hostname1_set_kernel_name(hostnamed_interf, our_get_kernel_name());
247 hostname1_set_kernel_version(hostnamed_interf, our_get_kernel_version());
248 hostname1_set_kernel_release(hostnamed_interf, our_get_kernel_release());
249 hostname1_set_operating_system_cpename(hostnamed_interf, our_get_os_cpename());
250 hostname1_set_operating_system_pretty_name(hostnamed_interf, our_get_os_pretty_name());
251
252 if(!g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(hostnamed_interf),
253 conn,
254 "/org/freedesktop/hostname1",
255 NULL)) {
256
257 g_printf("failed to export %s's interface!\n", name); /* unusual edge case, TODO check errno */
258 hostnamed_mem_clean();
259
260 } else {
261
262 dbus_interface_exported = TRUE;
263 g_printf("exported %s's interface on the system bus...\n", name);
264 }
265 }
266
267 static void hostnamed_on_name_acquired(GDBusConnection *conn,
268 const gchar *name,
269 gpointer user_data) {
270
271 g_printf("success!\n");
272 }
273
274 static void hostnamed_on_name_lost(GDBusConnection *conn,
275 const gchar *name,
276 gpointer user_data) {
277
278 if(!conn) {
279
280 g_printf("failed to connect to the system bus while trying to acquire name '%s': either dbus-daemon isn't running or we don't have permission to push names and/or their interfaces to it.\n", name);
281 hostnamed_mem_clean();
282 }
283
284 g_printf("lost name %s, exiting...\n", name);
285
286 hostnamed_mem_clean();
287 }
288
289 /* --- end bus/name handlers, begin misc unix functions --- */
290
291 /* safe call to clean and then exit
292 * this stops our GMainLoop safely before letting main() return */
293 void hostnamed_mem_clean() {
294
295 g_printf("exiting...\n");
296
297 if(dbus_interface_exported)
298 g_dbus_interface_skeleton_unexport(G_DBUS_INTERFACE_SKELETON(hostnamed_interf));
299
300 if(g_main_loop_is_running(hostnamed_loop))
301 g_main_loop_quit(hostnamed_loop);
302
303 }
304
305 /* wrapper for glib's unix signal handling; called only once if terminatating signal is raised against us */
306 gboolean unix_sig_terminate_handler(gpointer data) {
307
308 g_printf("caught SIGINT/HUP/TERM, exiting\n");
309
310 hostnamed_mem_clean();
311 return G_SOURCE_REMOVE;
312 }
313
314 void set_signal_handlers() {
315
316 /* we don't care about its descriptor, we never need to unregister these */
317 g_unix_signal_add(SIGINT, unix_sig_terminate_handler, NULL);
318 g_unix_signal_add(SIGHUP, unix_sig_terminate_handler, NULL);
319 g_unix_signal_add(SIGTERM, unix_sig_terminate_handler, NULL);
320
321 /* TODO: the "only once" guarantee only counts towards specific signals.
322 * make sure calling a SIGINT and SIGHUP doesn't cause term_handler()
323 * to be called twice */
324 }
325
326 int main() {
327
328 set_signal_handlers();
329
330 hostnamed_loop = g_main_loop_new(NULL, TRUE);
331 hostnamed_freeable = g_ptr_array_new();
332
333 bus_descriptor = g_bus_own_name(G_BUS_TYPE_SYSTEM,
334 "org.freedesktop.hostname1",
335 G_BUS_NAME_OWNER_FLAGS_NONE,
336 hostnamed_on_bus_acquired,
337 hostnamed_on_name_acquired,
338 hostnamed_on_name_lost,
339 NULL,
340 NULL);
341
342 g_main_loop_run(hostnamed_loop);
343 /* runs until single g_main_loop_quit() call is raised inside <interface>_mem_clean() */
344 g_main_loop_unref(hostnamed_loop);
345
346 /* guaranteed unownable */
347 g_bus_unown_name(bus_descriptor);
348
349 /* at this point no operations can occur with our data, it is safe to free it + its container */
350 g_ptr_array_free(hostnamed_freeable, TRUE);
351
352 return 0;
353 }
354
355 gboolean test_against_known_vm_strings(gchar *sysctl_string) {
356
357 unsigned int i;
358
359 if(is_vm)
360 return TRUE;
361
362 for(; i < G_N_ELEMENTS(vmstring_list); i++)
363 if(strcasestr(sysctl_string, vmstring_list[i]))
364 return (is_vm = TRUE) ? TRUE : FALSE;
365
366 return FALSE;
367 }
368
369 /* TODO figure out DMI variables on obsd */
370 /*static gchar *guess_icon_name() {
371
372 gchar *filebuf = NULL;
373 gchar *ret = NULL;
374
375 #if defined(__i386__) || defined(__x86_64__)
376
377 Taken with a few minor changes from systemd's hostnamed.c,
378 copyright 2011 Lennart Poettering.
379
380 See the SMBIOS Specification 2.7.1 section 7.4.1 for
381 details about the values listed here:
382
383 http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
384
385
386 if (g_file_get_contents ("/sys/class/dmi/id/chassis_type", &filebuf, NULL, NULL)) {
387 switch (g_ascii_strtoull (filebuf, NULL, 10)) {
388 case 0x3:
389 case 0x4:
390 case 0x5:
391 case 0x6:
392 case 0x7:
393 ret = g_strdup ("computer-desktop");
394 goto out;
395 case 0x9:
396 case 0xA:
397 case 0xE:
398 ret = g_strdup ("computer-laptop");
399 goto out;
400 case 0x11:
401 case 0x17:
402 case 0x1C:
403 case 0x1D:
404 ret = g_strdup ("computer-server");
405 goto out;
406 }
407 }
408 #endif
409 ret = g_strdup ("computer");
410 out:
411 g_free (filebuf);
412 return ret;
413 }*/
414