]>
Commit | Line | Data |
---|---|---|
e2054217 ZJS |
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | ||
3 | #include <errno.h> | |
4 | #include <stdio.h> | |
5 | #include <stdlib.h> | |
6 | #include <sys/utsname.h> | |
7 | #include <unistd.h> | |
8 | ||
9 | #include "alloc-util.h" | |
10 | #include "fd-util.h" | |
11 | #include "fileio.h" | |
60e4fb42 | 12 | #include "fs-util.h" |
e2054217 ZJS |
13 | #include "hostname-setup.h" |
14 | #include "hostname-util.h" | |
15 | #include "log.h" | |
16 | #include "macro.h" | |
17 | #include "proc-cmdline.h" | |
60e4fb42 | 18 | #include "string-table.h" |
e2054217 ZJS |
19 | #include "string-util.h" |
20 | #include "util.h" | |
21 | ||
b6fad306 | 22 | static int sethostname_idempotent_full(const char *s, bool really) { |
e2054217 ZJS |
23 | char buf[HOST_NAME_MAX + 1] = {}; |
24 | ||
25 | assert(s); | |
26 | ||
39ede7cc | 27 | if (gethostname(buf, sizeof(buf) - 1) < 0) |
e2054217 ZJS |
28 | return -errno; |
29 | ||
30 | if (streq(buf, s)) | |
31 | return 0; | |
32 | ||
b6fad306 ZJS |
33 | if (really && |
34 | sethostname(s, strlen(s)) < 0) | |
e2054217 ZJS |
35 | return -errno; |
36 | ||
37 | return 1; | |
38 | } | |
39 | ||
b6fad306 ZJS |
40 | int sethostname_idempotent(const char *s) { |
41 | return sethostname_idempotent_full(s, true); | |
42 | } | |
43 | ||
60e4fb42 ZJS |
44 | bool get_hostname_filtered(char ret[static HOST_NAME_MAX + 1]) { |
45 | char buf[HOST_NAME_MAX + 1] = {}; | |
46 | ||
47 | /* Returns true if we got a good hostname, false otherwise. */ | |
48 | ||
49 | if (gethostname(buf, sizeof(buf) - 1) < 0) | |
50 | return false; /* This can realistically only fail with ENAMETOOLONG. | |
51 | * Let's treat that case the same as an invalid hostname. */ | |
52 | ||
53 | if (isempty(buf)) | |
54 | return false; | |
55 | ||
56 | /* This is the built-in kernel default hostname */ | |
57 | if (streq(buf, "(none)")) | |
58 | return false; | |
59 | ||
60 | memcpy(ret, buf, sizeof buf); | |
61 | return true; | |
62 | } | |
63 | ||
e2054217 ZJS |
64 | int shorten_overlong(const char *s, char **ret) { |
65 | char *h, *p; | |
66 | ||
67 | /* Shorten an overlong name to HOST_NAME_MAX or to the first dot, | |
68 | * whatever comes earlier. */ | |
69 | ||
70 | assert(s); | |
71 | ||
72 | h = strdup(s); | |
73 | if (!h) | |
74 | return -ENOMEM; | |
75 | ||
76 | if (hostname_is_valid(h, 0)) { | |
77 | *ret = h; | |
78 | return 0; | |
79 | } | |
80 | ||
81 | p = strchr(h, '.'); | |
82 | if (p) | |
83 | *p = 0; | |
84 | ||
85 | strshorten(h, HOST_NAME_MAX); | |
86 | ||
87 | if (!hostname_is_valid(h, 0)) { | |
88 | free(h); | |
89 | return -EDOM; | |
90 | } | |
91 | ||
92 | *ret = h; | |
93 | return 1; | |
94 | } | |
95 | ||
96 | int read_etc_hostname_stream(FILE *f, char **ret) { | |
97 | int r; | |
98 | ||
99 | assert(f); | |
100 | assert(ret); | |
101 | ||
102 | for (;;) { | |
103 | _cleanup_free_ char *line = NULL; | |
104 | char *p; | |
105 | ||
106 | r = read_line(f, LONG_LINE_MAX, &line); | |
107 | if (r < 0) | |
108 | return r; | |
109 | if (r == 0) /* EOF without any hostname? the file is empty, let's treat that exactly like no file at all: ENOENT */ | |
110 | return -ENOENT; | |
111 | ||
112 | p = strstrip(line); | |
113 | ||
114 | /* File may have empty lines or comments, ignore them */ | |
115 | if (!IN_SET(*p, '\0', '#')) { | |
116 | char *copy; | |
117 | ||
118 | hostname_cleanup(p); /* normalize the hostname */ | |
119 | ||
120 | if (!hostname_is_valid(p, VALID_HOSTNAME_TRAILING_DOT)) /* check that the hostname we return is valid */ | |
121 | return -EBADMSG; | |
122 | ||
123 | copy = strdup(p); | |
124 | if (!copy) | |
125 | return -ENOMEM; | |
126 | ||
127 | *ret = copy; | |
128 | return 0; | |
129 | } | |
130 | } | |
131 | } | |
132 | ||
133 | int read_etc_hostname(const char *path, char **ret) { | |
134 | _cleanup_fclose_ FILE *f = NULL; | |
135 | ||
136 | assert(ret); | |
137 | ||
138 | if (!path) | |
139 | path = "/etc/hostname"; | |
140 | ||
141 | f = fopen(path, "re"); | |
142 | if (!f) | |
143 | return -errno; | |
144 | ||
145 | return read_etc_hostname_stream(f, ret); | |
e2054217 ZJS |
146 | } |
147 | ||
60e4fb42 ZJS |
148 | void hostname_update_source_hint(const char *hostname, HostnameSource source) { |
149 | int r; | |
e2054217 | 150 | |
60e4fb42 ZJS |
151 | /* Why save the value and not just create a flag file? This way we will |
152 | * notice if somebody sets the hostname directly (not going through hostnamed). | |
153 | */ | |
e2054217 | 154 | |
60e4fb42 ZJS |
155 | if (source == HOSTNAME_FALLBACK) { |
156 | r = write_string_file("/run/systemd/fallback-hostname", hostname, | |
157 | WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_ATOMIC); | |
158 | if (r < 0) | |
159 | log_warning_errno(r, "Failed to create \"/run/systemd/fallback-hostname\": %m"); | |
160 | } else | |
161 | unlink_or_warn("/run/systemd/fallback-hostname"); | |
e2054217 ZJS |
162 | } |
163 | ||
b6fad306 | 164 | int hostname_setup(bool really) { |
e2054217 ZJS |
165 | _cleanup_free_ char *b = NULL; |
166 | const char *hn = NULL; | |
60e4fb42 | 167 | HostnameSource source; |
e2054217 ZJS |
168 | bool enoent = false; |
169 | int r; | |
170 | ||
171 | r = proc_cmdline_get_key("systemd.hostname", 0, &b); | |
172 | if (r < 0) | |
173 | log_warning_errno(r, "Failed to retrieve system hostname from kernel command line, ignoring: %m"); | |
174 | else if (r > 0) { | |
60e4fb42 | 175 | if (hostname_is_valid(b, true)) { |
e2054217 | 176 | hn = b; |
60e4fb42 ZJS |
177 | source = HOSTNAME_TRANSIENT; |
178 | } else { | |
e2054217 ZJS |
179 | log_warning("Hostname specified on kernel command line is invalid, ignoring: %s", b); |
180 | b = mfree(b); | |
181 | } | |
182 | } | |
183 | ||
184 | if (!hn) { | |
185 | r = read_etc_hostname(NULL, &b); | |
186 | if (r < 0) { | |
187 | if (r == -ENOENT) | |
188 | enoent = true; | |
189 | else | |
190 | log_warning_errno(r, "Failed to read configured hostname: %m"); | |
60e4fb42 | 191 | } else { |
e2054217 | 192 | hn = b; |
60e4fb42 ZJS |
193 | source = HOSTNAME_STATIC; |
194 | } | |
e2054217 ZJS |
195 | } |
196 | ||
197 | if (isempty(hn)) { | |
198 | /* Don't override the hostname if it is already set and not explicitly configured */ | |
60e4fb42 ZJS |
199 | |
200 | char buf[HOST_NAME_MAX + 1] = {}; | |
201 | if (get_hostname_filtered(buf)) { | |
202 | log_debug("No hostname configured, leaving existing hostname <%s> in place.", buf); | |
e2054217 | 203 | return 0; |
60e4fb42 | 204 | } |
e2054217 ZJS |
205 | |
206 | if (enoent) | |
60e4fb42 | 207 | log_info("No hostname configured, using fallback hostname."); |
e2054217 ZJS |
208 | |
209 | hn = FALLBACK_HOSTNAME; | |
60e4fb42 ZJS |
210 | source = HOSTNAME_FALLBACK; |
211 | ||
e2054217 ZJS |
212 | } |
213 | ||
b6fad306 | 214 | r = sethostname_idempotent_full(hn, really); |
e2054217 ZJS |
215 | if (r < 0) |
216 | return log_warning_errno(r, "Failed to set hostname to <%s>: %m", hn); | |
b6fad306 ZJS |
217 | if (r == 0) |
218 | log_debug("Hostname was already set to <%s>.", hn); | |
219 | else | |
220 | log_info("Hostname %s to <%s>.", | |
221 | really ? "set" : "would have been set", | |
222 | hn); | |
223 | ||
60e4fb42 ZJS |
224 | if (really) |
225 | hostname_update_source_hint(hn, source); | |
226 | ||
b6fad306 | 227 | return r; |
e2054217 | 228 | } |
60e4fb42 ZJS |
229 | |
230 | static const char* const hostname_source_table[] = { | |
231 | [HOSTNAME_STATIC] = "static", | |
232 | [HOSTNAME_TRANSIENT] = "transient", | |
233 | [HOSTNAME_FALLBACK] = "fallback", | |
234 | }; | |
235 | ||
236 | DEFINE_STRING_TABLE_LOOKUP_TO_STRING(hostname_source, HostnameSource); |