]>
Commit | Line | Data |
---|---|---|
0ad19a3f | 1 | /* |
2 | * lxc: linux Container library | |
3 | * | |
4 | * (C) Copyright IBM Corp. 2007, 2008 | |
5 | * | |
6 | * Authors: | |
7 | * Daniel Lezcano <dlezcano at fr.ibm.com> | |
8 | * | |
9 | * This library is free software; you can redistribute it and/or | |
10 | * modify it under the terms of the GNU Lesser General Public | |
11 | * License as published by the Free Software Foundation; either | |
12 | * version 2.1 of the License, or (at your option) any later version. | |
13 | * | |
14 | * This library is distributed in the hope that it will be useful, | |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
17 | * Lesser General Public License for more details. | |
18 | * | |
19 | * You should have received a copy of the GNU Lesser General Public | |
20 | * License along with this library; if not, write to the Free Software | |
21 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
22 | */ | |
23 | #define _GNU_SOURCE | |
24 | #include <stdio.h> | |
25 | #undef _GNU_SOURCE | |
26 | #include <stdlib.h> | |
27 | #include <errno.h> | |
28 | #include <string.h> | |
29 | #include <dirent.h> | |
30 | #include <mntent.h> | |
31 | #include <unistd.h> | |
b0a33c1e | 32 | #include <pty.h> |
0ad19a3f | 33 | |
34 | #include <sys/types.h> | |
35 | #include <sys/utsname.h> | |
36 | #include <sys/param.h> | |
37 | #include <sys/stat.h> | |
38 | #include <sys/socket.h> | |
39 | #include <sys/mount.h> | |
40 | #include <sys/mman.h> | |
41 | ||
42 | #include <arpa/inet.h> | |
43 | #include <fcntl.h> | |
44 | #include <netinet/in.h> | |
45 | #include <net/if.h> | |
6f4a3756 | 46 | #include <libgen.h> |
0ad19a3f | 47 | |
e5bda9ee | 48 | #include "network.h" |
49 | #include "error.h" | |
b2718c72 | 50 | #include "parse.h" |
881450bb | 51 | #include "config.h" |
e5bda9ee | 52 | |
b113348e | 53 | #include <lxc/lxc.h> |
36eb9bde CLG |
54 | #include <lxc/log.h> |
55 | ||
56 | lxc_log_define(lxc_conf, lxc); | |
e5bda9ee | 57 | |
0ad19a3f | 58 | #define MAXHWLEN 18 |
59 | #define MAXINDEXLEN 20 | |
442cbbe6 | 60 | #define MAXMTULEN 16 |
0ad19a3f | 61 | #define MAXLINELEN 128 |
62 | ||
fdc03323 DL |
63 | #ifndef MS_REC |
64 | #define MS_REC 16384 | |
65 | #endif | |
66 | ||
82d5ae15 | 67 | typedef int (*instanciate_cb)(struct lxc_netdev *); |
0ad19a3f | 68 | |
998ac676 RT |
69 | struct mount_opt { |
70 | char *name; | |
71 | int clear; | |
72 | int flag; | |
73 | }; | |
74 | ||
82d5ae15 DL |
75 | static int instanciate_veth(struct lxc_netdev *); |
76 | static int instanciate_macvlan(struct lxc_netdev *); | |
77 | static int instanciate_phys(struct lxc_netdev *); | |
78 | static int instanciate_empty(struct lxc_netdev *); | |
79 | ||
80 | static instanciate_cb netdev_conf[MAXCONFTYPE + 1] = { | |
81 | [VETH] = instanciate_veth, | |
82 | [MACVLAN] = instanciate_macvlan, | |
83 | [PHYS] = instanciate_phys, | |
84 | [EMPTY] = instanciate_empty, | |
0ad19a3f | 85 | }; |
86 | ||
998ac676 RT |
87 | static struct mount_opt mount_opt[] = { |
88 | { "defaults", 0, 0 }, | |
89 | { "ro", 0, MS_RDONLY }, | |
90 | { "rw", 1, MS_RDONLY }, | |
91 | { "suid", 1, MS_NOSUID }, | |
92 | { "nosuid", 0, MS_NOSUID }, | |
93 | { "dev", 1, MS_NODEV }, | |
94 | { "nodev", 0, MS_NODEV }, | |
95 | { "exec", 1, MS_NOEXEC }, | |
96 | { "noexec", 0, MS_NOEXEC }, | |
97 | { "sync", 0, MS_SYNCHRONOUS }, | |
98 | { "async", 1, MS_SYNCHRONOUS }, | |
99 | { "remount", 0, MS_REMOUNT }, | |
100 | { "mand", 0, MS_MANDLOCK }, | |
101 | { "nomand", 1, MS_MANDLOCK }, | |
102 | { "atime", 1, MS_NOATIME }, | |
103 | { "noatime", 0, MS_NOATIME }, | |
104 | { "diratime", 1, MS_NODIRATIME }, | |
105 | { "nodiratime", 0, MS_NODIRATIME }, | |
106 | { "bind", 0, MS_BIND }, | |
107 | { "rbind", 0, MS_BIND|MS_REC }, | |
108 | { NULL, 0, 0 }, | |
109 | }; | |
110 | ||
78ae2fcc | 111 | static int configure_find_fstype_cb(void* buffer, void *data) |
112 | { | |
113 | struct cbarg { | |
114 | const char *rootfs; | |
115 | const char *testdir; | |
116 | char *fstype; | |
117 | int mntopt; | |
118 | } *cbarg = data; | |
119 | ||
120 | char *fstype; | |
121 | ||
122 | /* we don't try 'nodev' entries */ | |
123 | if (strstr(buffer, "nodev")) | |
124 | return 0; | |
125 | ||
126 | fstype = buffer; | |
b2718c72 | 127 | fstype += lxc_char_left_gc(fstype, strlen(fstype)); |
128 | fstype[lxc_char_right_gc(fstype, strlen(fstype))] = '\0'; | |
78ae2fcc | 129 | |
130 | if (mount(cbarg->rootfs, cbarg->testdir, fstype, cbarg->mntopt, NULL)) | |
131 | return 0; | |
132 | ||
133 | /* found ! */ | |
134 | umount(cbarg->testdir); | |
135 | strcpy(cbarg->fstype, fstype); | |
136 | ||
137 | return 1; | |
138 | } | |
139 | ||
140 | /* find the filesystem type with brute force */ | |
141 | static int configure_find_fstype(const char *rootfs, char *fstype, int mntopt) | |
142 | { | |
143 | int i, found; | |
144 | char buffer[MAXPATHLEN]; | |
145 | ||
146 | struct cbarg { | |
147 | const char *rootfs; | |
148 | const char *testdir; | |
149 | char *fstype; | |
150 | int mntopt; | |
151 | } cbarg = { | |
152 | .rootfs = rootfs, | |
153 | .fstype = fstype, | |
154 | .mntopt = mntopt, | |
155 | }; | |
156 | ||
157 | /* first we check with /etc/filesystems, in case the modules | |
158 | * are auto-loaded and fall back to the supported kernel fs | |
159 | */ | |
160 | char *fsfile[] = { | |
161 | "/etc/filesystems", | |
162 | "/proc/filesystems", | |
163 | }; | |
164 | ||
165 | cbarg.testdir = tempnam("/tmp", "lxc-"); | |
166 | if (!cbarg.testdir) { | |
36eb9bde | 167 | SYSERROR("failed to build a temp name"); |
78ae2fcc | 168 | return -1; |
169 | } | |
170 | ||
171 | if (mkdir(cbarg.testdir, 0755)) { | |
36eb9bde | 172 | SYSERROR("failed to create temporary directory"); |
78ae2fcc | 173 | return -1; |
174 | } | |
175 | ||
176 | for (i = 0; i < sizeof(fsfile)/sizeof(fsfile[0]); i++) { | |
177 | ||
b2718c72 | 178 | found = lxc_file_for_each_line(fsfile[i], |
179 | configure_find_fstype_cb, | |
180 | buffer, sizeof(buffer), &cbarg); | |
78ae2fcc | 181 | |
182 | if (found < 0) { | |
36eb9bde | 183 | SYSERROR("failed to read '%s'", fsfile[i]); |
78ae2fcc | 184 | goto out; |
185 | } | |
186 | ||
187 | if (found) | |
188 | break; | |
189 | } | |
190 | ||
191 | if (!found) { | |
36eb9bde | 192 | ERROR("failed to determine fs type for '%s'", rootfs); |
78ae2fcc | 193 | goto out; |
194 | } | |
195 | ||
196 | out: | |
197 | rmdir(cbarg.testdir); | |
198 | return found - 1; | |
199 | } | |
200 | ||
201 | static int configure_rootfs_dir_cb(const char *rootfs, const char *absrootfs, | |
202 | FILE *f) | |
203 | { | |
fdc03323 | 204 | return fprintf(f, "%s %s none rbind 0 0\n", absrootfs, rootfs); |
78ae2fcc | 205 | } |
206 | ||
207 | static int configure_rootfs_blk_cb(const char *rootfs, const char *absrootfs, | |
208 | FILE *f) | |
209 | { | |
210 | char fstype[MAXPATHLEN]; | |
211 | ||
212 | if (configure_find_fstype(absrootfs, fstype, 0)) { | |
36eb9bde | 213 | ERROR("failed to configure mount for block device '%s'", |
78ae2fcc | 214 | absrootfs); |
215 | return -1; | |
216 | } | |
217 | ||
218 | return fprintf(f, "%s %s %s defaults 0 0\n", absrootfs, rootfs, fstype); | |
219 | } | |
220 | ||
eae6543d | 221 | static int configure_rootfs(const char *name, const char *rootfs) |
0ad19a3f | 222 | { |
223 | char path[MAXPATHLEN]; | |
b09ef133 | 224 | char absrootfs[MAXPATHLEN]; |
9b0f0477 | 225 | char fstab[MAXPATHLEN]; |
78ae2fcc | 226 | struct stat s; |
9b0f0477 | 227 | FILE *f; |
78ae2fcc | 228 | int i, ret; |
229 | ||
230 | typedef int (*rootfs_cb)(const char *, const char *, FILE *); | |
231 | ||
232 | struct rootfs_type { | |
233 | int type; | |
234 | rootfs_cb cb; | |
235 | } rtfs_type[] = { | |
236 | { __S_IFDIR, configure_rootfs_dir_cb }, | |
237 | { __S_IFBLK, configure_rootfs_blk_cb }, | |
238 | }; | |
0ad19a3f | 239 | |
4c8ab83b | 240 | if (!realpath(rootfs, absrootfs)) { |
36eb9bde | 241 | SYSERROR("failed to get real path for '%s'", rootfs); |
4c8ab83b | 242 | return -1; |
243 | } | |
b09ef133 | 244 | |
4c8ab83b | 245 | snprintf(path, MAXPATHLEN, LXCPATH "/%s/rootfs", name); |
b09ef133 | 246 | |
78ae2fcc | 247 | if (mkdir(path, 0755)) { |
36eb9bde | 248 | SYSERROR("failed to create the '%s' directory", path); |
78ae2fcc | 249 | return -1; |
250 | } | |
251 | ||
b09ef133 | 252 | if (access(absrootfs, F_OK)) { |
36eb9bde | 253 | SYSERROR("'%s' is not accessible", absrootfs); |
b09ef133 | 254 | return -1; |
255 | } | |
256 | ||
78ae2fcc | 257 | if (stat(absrootfs, &s)) { |
36eb9bde | 258 | SYSERROR("failed to stat '%s'", absrootfs); |
9b0f0477 | 259 | return -1; |
260 | } | |
261 | ||
78ae2fcc | 262 | for (i = 0; i < sizeof(rtfs_type)/sizeof(rtfs_type[0]); i++) { |
9b0f0477 | 263 | |
78ae2fcc | 264 | if (!__S_ISTYPE(s.st_mode, rtfs_type[i].type)) |
265 | continue; | |
9b0f0477 | 266 | |
78ae2fcc | 267 | snprintf(fstab, MAXPATHLEN, LXCPATH "/%s/fstab", name); |
4c8ab83b | 268 | |
78ae2fcc | 269 | f = fopen(fstab, "a+"); |
270 | if (!f) { | |
36eb9bde | 271 | SYSERROR("failed to open fstab file"); |
78ae2fcc | 272 | return -1; |
273 | } | |
9b0f0477 | 274 | |
78ae2fcc | 275 | ret = rtfs_type[i].cb(path, absrootfs, f); |
9b0f0477 | 276 | |
78ae2fcc | 277 | fclose(f); |
278 | ||
279 | if (ret < 0) { | |
36eb9bde | 280 | ERROR("failed to add rootfs mount in fstab"); |
78ae2fcc | 281 | return -1; |
282 | } | |
283 | ||
284 | snprintf(path, MAXPATHLEN, LXCPATH "/%s/rootfs/rootfs", name); | |
285 | ||
286 | return symlink(absrootfs, path); | |
287 | } | |
9b0f0477 | 288 | |
36eb9bde | 289 | ERROR("unsupported rootfs type for '%s'", absrootfs); |
78ae2fcc | 290 | return -1; |
0ad19a3f | 291 | } |
292 | ||
4e5440c6 | 293 | static int setup_utsname(struct utsname *utsname) |
0ad19a3f | 294 | { |
4e5440c6 DL |
295 | if (!utsname) |
296 | return 0; | |
0ad19a3f | 297 | |
4e5440c6 DL |
298 | if (sethostname(utsname->nodename, strlen(utsname->nodename))) { |
299 | SYSERROR("failed to set the hostname to '%s'", utsname->nodename); | |
0ad19a3f | 300 | return -1; |
301 | } | |
302 | ||
4e5440c6 | 303 | INFO("'%s' hostname has been setup", utsname->nodename); |
cd54d859 | 304 | |
0ad19a3f | 305 | return 0; |
306 | } | |
307 | ||
52e35957 | 308 | static int setup_tty(const char *rootfs, const struct lxc_tty_info *tty_info) |
b0a33c1e | 309 | { |
310 | char path[MAXPATHLEN]; | |
311 | int i; | |
312 | ||
313 | for (i = 0; i < tty_info->nbtty; i++) { | |
314 | ||
315 | struct lxc_pty_info *pty_info = &tty_info->pty_info[i]; | |
316 | ||
52e35957 DL |
317 | snprintf(path, sizeof(path), "%s/dev/tty%d", |
318 | rootfs ? rootfs : "", i + 1); | |
b0a33c1e | 319 | |
13954cce | 320 | /* At this point I can not use the "access" function |
b0a33c1e | 321 | * to check the file is present or not because it fails |
322 | * with EACCES errno and I don't know why :( */ | |
13954cce | 323 | |
b0a33c1e | 324 | if (mount(pty_info->name, path, "none", MS_BIND, 0)) { |
36eb9bde | 325 | WARN("failed to mount '%s'->'%s'", |
52e35957 | 326 | pty_info->name, path); |
b0a33c1e | 327 | continue; |
328 | } | |
329 | } | |
330 | ||
cd54d859 DL |
331 | INFO("%d tty(s) has been setup", tty_info->nbtty); |
332 | ||
b0a33c1e | 333 | return 0; |
334 | } | |
335 | ||
c69bd12f | 336 | static int setup_rootfs(const char *rootfs) |
0ad19a3f | 337 | { |
c69bd12f DL |
338 | char *tmpname; |
339 | int ret = -1; | |
0ad19a3f | 340 | |
c69bd12f DL |
341 | if (!rootfs) |
342 | return 0; | |
0ad19a3f | 343 | |
c69bd12f DL |
344 | tmpname = tempnam("/tmp", "lxc-rootfs"); |
345 | if (!tmpname) { | |
346 | SYSERROR("failed to generate temporary name"); | |
c3f0a28c | 347 | return -1; |
348 | } | |
0ad19a3f | 349 | |
c69bd12f DL |
350 | if (mkdir(tmpname, 0700)) { |
351 | SYSERROR("failed to create temporary directory '%s'", tmpname); | |
352 | return -1; | |
353 | } | |
354 | ||
355 | if (mount(rootfs, tmpname, "none", MS_BIND|MS_REC, NULL)) { | |
356 | SYSERROR("failed to mount '%s'->'%s'", rootfs, tmpname); | |
357 | goto out; | |
358 | } | |
359 | ||
360 | if (chroot(tmpname)) { | |
361 | SYSERROR("failed to set chroot %s", tmpname); | |
362 | goto out; | |
363 | } | |
364 | ||
c3f0a28c | 365 | if (chdir(getenv("HOME")) && chdir("/")) { |
36eb9bde | 366 | SYSERROR("failed to change to home directory"); |
c69bd12f | 367 | goto out; |
0ad19a3f | 368 | } |
369 | ||
c69bd12f | 370 | INFO("chrooted to '%s'", rootfs); |
cd54d859 | 371 | |
c69bd12f DL |
372 | ret = 0; |
373 | out: | |
374 | rmdir(tmpname); | |
375 | return ret; | |
0ad19a3f | 376 | } |
377 | ||
d852c78c | 378 | static int setup_pts(int pts) |
3c26f34e | 379 | { |
d852c78c DL |
380 | if (!pts) |
381 | return 0; | |
3c26f34e | 382 | |
383 | if (!access("/dev/pts/ptmx", F_OK) && umount("/dev/pts")) { | |
36eb9bde | 384 | SYSERROR("failed to umount 'dev/pts'"); |
3c26f34e | 385 | return -1; |
386 | } | |
387 | ||
d852c78c | 388 | if (mount("devpts", "/dev/pts", "devpts", MS_MGC_VAL, "newinstance")) { |
36eb9bde | 389 | SYSERROR("failed to mount a new instance of '/dev/pts'"); |
3c26f34e | 390 | return -1; |
391 | } | |
392 | ||
393 | if (chmod("/dev/pts/ptmx", 0666)) { | |
36eb9bde | 394 | SYSERROR("failed to set permission for '/dev/pts/ptmx'"); |
3c26f34e | 395 | return -1; |
396 | } | |
397 | ||
398 | if (access("/dev/ptmx", F_OK)) { | |
399 | if (!symlink("/dev/pts/ptmx", "/dev/ptmx")) | |
400 | goto out; | |
36eb9bde | 401 | SYSERROR("failed to symlink '/dev/pts/ptmx'->'/dev/ptmx'"); |
3c26f34e | 402 | return -1; |
403 | } | |
404 | ||
405 | /* fallback here, /dev/pts/ptmx exists just mount bind */ | |
406 | if (mount("/dev/pts/ptmx", "/dev/ptmx", "none", MS_BIND, 0)) { | |
36eb9bde | 407 | SYSERROR("mount failed '/dev/pts/ptmx'->'/dev/ptmx'"); |
3c26f34e | 408 | return -1; |
409 | } | |
cd54d859 DL |
410 | |
411 | INFO("created new pts instance"); | |
d852c78c | 412 | |
3c26f34e | 413 | out: |
414 | return 0; | |
415 | } | |
416 | ||
52e35957 | 417 | static int setup_console(const char *rootfs, const char *tty) |
6e590161 | 418 | { |
ed502555 | 419 | char console[MAXPATHLEN]; |
420 | ||
52e35957 DL |
421 | snprintf(console, sizeof(console), "%s/dev/console", |
422 | rootfs ? rootfs : ""); | |
423 | ||
424 | /* we have the rootfs with /dev/console but no tty | |
425 | * to be used as console, let's remap /dev/console | |
426 | * to /dev/null to avoid to log to the system console | |
427 | */ | |
428 | if (rootfs && !tty[0]) { | |
429 | ||
430 | if (!access(console, F_OK)) { | |
431 | ||
432 | if (mount("/dev/null", console, "none", MS_BIND, 0)) { | |
433 | SYSERROR("failed to mount '/dev/null'->'%s'", | |
434 | console); | |
435 | return -1; | |
436 | } | |
437 | } | |
438 | } | |
439 | ||
440 | if (!tty[0]) | |
441 | return 0; | |
ed502555 | 442 | |
443 | if (access(console, R_OK|W_OK)) | |
6e590161 | 444 | return 0; |
13954cce | 445 | |
ed502555 | 446 | if (mount(tty, console, "none", MS_BIND, 0)) { |
36eb9bde | 447 | ERROR("failed to mount the console"); |
6e590161 | 448 | return -1; |
449 | } | |
450 | ||
cd54d859 DL |
451 | INFO("console '%s' mounted to '%s'", tty, console); |
452 | ||
6e590161 | 453 | return 0; |
454 | } | |
455 | ||
102a5303 | 456 | static int setup_cgroup(const char *name, struct lxc_list *cgroups) |
576f946d | 457 | { |
102a5303 DL |
458 | struct lxc_list *iterator; |
459 | struct lxc_cgroup *cg; | |
88329c69 | 460 | int ret = -1; |
6f4a3756 | 461 | |
102a5303 DL |
462 | if (lxc_list_empty(cgroups)) |
463 | return 0; | |
6f4a3756 | 464 | |
102a5303 | 465 | lxc_list_for_each(iterator, cgroups) { |
13954cce | 466 | |
102a5303 | 467 | cg = iterator->elem; |
6f4a3756 | 468 | |
102a5303 | 469 | if (lxc_cgroup_set(name, cg->subsystem, cg->value)) |
88329c69 | 470 | goto out; |
6f4a3756 | 471 | |
102a5303 | 472 | DEBUG("cgroup '%s' set to '%s'", cg->subsystem, cg->value); |
6f4a3756 | 473 | } |
13954cce | 474 | |
88329c69 | 475 | ret = 0; |
cd54d859 | 476 | INFO("cgroup has been setup"); |
88329c69 MN |
477 | out: |
478 | return ret; | |
576f946d | 479 | } |
480 | ||
998ac676 RT |
481 | static void parse_mntopt(char *opt, unsigned long *flags, char **data) |
482 | { | |
483 | struct mount_opt *mo; | |
484 | ||
485 | /* If opt is found in mount_opt, set or clear flags. | |
486 | * Otherwise append it to data. */ | |
487 | ||
488 | for (mo = &mount_opt[0]; mo->name != NULL; mo++) { | |
489 | if (!strncmp(opt, mo->name, strlen(mo->name))) { | |
490 | if (mo->clear) | |
491 | *flags &= ~mo->flag; | |
492 | else | |
493 | *flags |= mo->flag; | |
494 | return; | |
495 | } | |
496 | } | |
497 | ||
498 | if (strlen(*data)) | |
499 | strcat(*data, ","); | |
500 | strcat(*data, opt); | |
501 | } | |
502 | ||
503 | static int parse_mntopts(struct mntent *mntent, unsigned long *mntflags, | |
504 | char **mntdata) | |
505 | { | |
506 | char *s, *data; | |
507 | char *p, *saveptr = NULL; | |
508 | ||
509 | if (!mntent->mnt_opts) | |
510 | return 0; | |
511 | ||
512 | s = strdup(mntent->mnt_opts); | |
513 | if (!s) { | |
36eb9bde | 514 | SYSERROR("failed to allocate memory"); |
998ac676 RT |
515 | return -1; |
516 | } | |
517 | ||
518 | data = malloc(strlen(s) + 1); | |
519 | if (!data) { | |
36eb9bde | 520 | SYSERROR("failed to allocate memory"); |
998ac676 RT |
521 | free(s); |
522 | return -1; | |
523 | } | |
524 | *data = 0; | |
525 | ||
526 | for (p = strtok_r(s, ",", &saveptr); p != NULL; | |
527 | p = strtok_r(NULL, ",", &saveptr)) | |
528 | parse_mntopt(p, mntflags, &data); | |
529 | ||
530 | if (*data) | |
531 | *mntdata = data; | |
532 | else | |
533 | free(data); | |
534 | free(s); | |
535 | ||
536 | return 0; | |
537 | } | |
538 | ||
e7938e9e | 539 | static int mount_file_entries(FILE *file) |
0ad19a3f | 540 | { |
0ad19a3f | 541 | struct mntent *mntent; |
0ad19a3f | 542 | int ret = -1; |
998ac676 RT |
543 | unsigned long mntflags; |
544 | char *mntdata; | |
0ad19a3f | 545 | |
998ac676 | 546 | while ((mntent = getmntent(file))) { |
1bc60a65 | 547 | |
998ac676 RT |
548 | mntflags = 0; |
549 | mntdata = NULL; | |
550 | if (parse_mntopts(mntent, &mntflags, &mntdata) < 0) { | |
36eb9bde | 551 | ERROR("failed to parse mount option '%s'", |
998ac676 RT |
552 | mntent->mnt_opts); |
553 | goto out; | |
554 | } | |
0ad19a3f | 555 | |
556 | if (mount(mntent->mnt_fsname, mntent->mnt_dir, | |
998ac676 | 557 | mntent->mnt_type, mntflags, mntdata)) { |
36eb9bde | 558 | SYSERROR("failed to mount '%s' on '%s'", |
0ad19a3f | 559 | mntent->mnt_fsname, mntent->mnt_dir); |
560 | goto out; | |
561 | } | |
998ac676 | 562 | |
cd54d859 DL |
563 | DEBUG("mounted %s on %s, type %s", mntent->mnt_fsname, |
564 | mntent->mnt_dir, mntent->mnt_type); | |
565 | ||
998ac676 | 566 | free(mntdata); |
0ad19a3f | 567 | } |
cd54d859 | 568 | |
0ad19a3f | 569 | ret = 0; |
cd54d859 DL |
570 | |
571 | INFO("mount points have been setup"); | |
0ad19a3f | 572 | out: |
e7938e9e MN |
573 | return ret; |
574 | } | |
575 | ||
576 | static int setup_mount(const char *fstab) | |
577 | { | |
578 | FILE *file; | |
579 | int ret; | |
580 | ||
581 | if (!fstab) | |
582 | return 0; | |
583 | ||
584 | file = setmntent(fstab, "r"); | |
585 | if (!file) { | |
586 | SYSERROR("failed to use '%s'", fstab); | |
587 | return -1; | |
588 | } | |
589 | ||
590 | ret = mount_file_entries(file); | |
591 | ||
0ad19a3f | 592 | endmntent(file); |
593 | return ret; | |
594 | } | |
595 | ||
e7938e9e MN |
596 | static int setup_mount_entries(struct lxc_list *mount) |
597 | { | |
598 | FILE *file; | |
599 | struct lxc_list *iterator; | |
600 | char *mount_entry; | |
601 | int ret; | |
602 | ||
603 | file = tmpfile(); | |
604 | if (!file) { | |
605 | ERROR("tmpfile error: %m"); | |
606 | return -1; | |
607 | } | |
608 | ||
609 | lxc_list_for_each(iterator, mount) { | |
610 | mount_entry = iterator->elem; | |
611 | fprintf(file, "%s", mount_entry); | |
612 | } | |
613 | ||
614 | rewind(file); | |
615 | ||
616 | ret = mount_file_entries(file); | |
617 | ||
618 | fclose(file); | |
619 | return ret; | |
620 | } | |
621 | ||
0ad19a3f | 622 | static int setup_hw_addr(char *hwaddr, const char *ifname) |
623 | { | |
624 | struct sockaddr sockaddr; | |
625 | struct ifreq ifr; | |
626 | int ret, fd; | |
627 | ||
628 | if (lxc_convert_mac(hwaddr, &sockaddr)) { | |
3ab87b66 | 629 | ERROR("conversion has failed"); |
0ad19a3f | 630 | return -1; |
631 | } | |
632 | ||
633 | memcpy(ifr.ifr_name, ifname, IFNAMSIZ); | |
634 | memcpy((char *) &ifr.ifr_hwaddr, (char *) &sockaddr, sizeof(sockaddr)); | |
635 | ||
636 | fd = socket(AF_INET, SOCK_DGRAM, 0); | |
637 | if (fd < 0) { | |
3ab87b66 | 638 | ERROR("socket failure : %s", strerror(errno)); |
0ad19a3f | 639 | return -1; |
640 | } | |
641 | ||
642 | ret = ioctl(fd, SIOCSIFHWADDR, &ifr); | |
643 | close(fd); | |
644 | if (ret) | |
3ab87b66 | 645 | ERROR("ioctl failure : %s", strerror(errno)); |
0ad19a3f | 646 | |
cd54d859 DL |
647 | DEBUG("mac address '%s' on '%s' has been setup", hwaddr, ifname); |
648 | ||
0ad19a3f | 649 | return ret; |
650 | } | |
651 | ||
82d5ae15 | 652 | static int setup_ipv4_addr(struct lxc_list *ip, int ifindex) |
0ad19a3f | 653 | { |
82d5ae15 DL |
654 | struct lxc_list *iterator; |
655 | struct lxc_inetdev *inetdev; | |
0ad19a3f | 656 | |
82d5ae15 DL |
657 | lxc_list_for_each(iterator, ip) { |
658 | ||
659 | inetdev = iterator->elem; | |
660 | ||
4bf1968d DL |
661 | if (lxc_ip_addr_add(AF_INET, ifindex, |
662 | &inetdev->addr, inetdev->prefix)) { | |
82d5ae15 DL |
663 | return -1; |
664 | } | |
665 | } | |
666 | ||
667 | return 0; | |
0ad19a3f | 668 | } |
669 | ||
82d5ae15 | 670 | static int setup_ipv6_addr(struct lxc_list *ip, int ifindex) |
0ad19a3f | 671 | { |
82d5ae15 | 672 | struct lxc_list *iterator; |
7fa9074f | 673 | struct lxc_inet6dev *inet6dev; |
0ad19a3f | 674 | |
82d5ae15 DL |
675 | lxc_list_for_each(iterator, ip) { |
676 | ||
677 | inet6dev = iterator->elem; | |
678 | ||
4bf1968d DL |
679 | if (lxc_ip_addr_add(AF_INET6, ifindex, |
680 | & inet6dev->addr, inet6dev->prefix)) | |
82d5ae15 | 681 | return -1; |
82d5ae15 DL |
682 | } |
683 | ||
684 | return 0; | |
0ad19a3f | 685 | } |
686 | ||
82d5ae15 | 687 | static int setup_netdev(struct lxc_netdev *netdev) |
0ad19a3f | 688 | { |
0ad19a3f | 689 | char ifname[IFNAMSIZ]; |
0ad19a3f | 690 | char *current_ifname = ifname; |
0ad19a3f | 691 | |
82d5ae15 DL |
692 | /* empty network namespace */ |
693 | if (!netdev->ifindex) { | |
694 | if (netdev->flags | IFF_UP) { | |
695 | if (lxc_device_up("lo")) { | |
696 | ERROR("failed to set the loopback up"); | |
697 | return -1; | |
698 | } | |
699 | return 0; | |
700 | } | |
0ad19a3f | 701 | } |
13954cce | 702 | |
82d5ae15 DL |
703 | /* retrieve the name of the interface */ |
704 | if (!if_indextoname(netdev->ifindex, current_ifname)) { | |
36eb9bde | 705 | ERROR("no interface corresponding to index '%d'", |
82d5ae15 | 706 | netdev->ifindex); |
0ad19a3f | 707 | return -1; |
708 | } | |
13954cce | 709 | |
018ef520 | 710 | /* default: let the system to choose one interface name */ |
82d5ae15 DL |
711 | if (!netdev->newname) |
712 | netdev->newname = "eth%d"; | |
018ef520 | 713 | |
82d5ae15 DL |
714 | /* rename the interface name */ |
715 | if (lxc_device_rename(ifname, netdev->newname)) { | |
018ef520 DL |
716 | ERROR("failed to rename %s->%s", ifname, current_ifname); |
717 | return -1; | |
718 | } | |
719 | ||
720 | /* Re-read the name of the interface because its name has changed | |
721 | * and would be automatically allocated by the system | |
722 | */ | |
82d5ae15 | 723 | if (!if_indextoname(netdev->ifindex, current_ifname)) { |
018ef520 | 724 | ERROR("no interface corresponding to index '%d'", |
82d5ae15 | 725 | netdev->ifindex); |
018ef520 | 726 | return -1; |
0ad19a3f | 727 | } |
728 | ||
82d5ae15 DL |
729 | /* set a mac address */ |
730 | if (netdev->hwaddr) { | |
731 | if (setup_hw_addr(netdev->hwaddr, current_ifname)) { | |
36eb9bde | 732 | ERROR("failed to setup hw address for '%s'", |
82d5ae15 | 733 | current_ifname); |
0ad19a3f | 734 | return -1; |
735 | } | |
736 | } | |
737 | ||
82d5ae15 DL |
738 | /* setup ipv4 addresses on the interface */ |
739 | if (setup_ipv4_addr(&netdev->ipv4, netdev->ifindex)) { | |
36eb9bde | 740 | ERROR("failed to setup ip addresses for '%s'", |
0ad19a3f | 741 | ifname); |
742 | return -1; | |
743 | } | |
744 | ||
82d5ae15 DL |
745 | /* setup ipv6 addresses on the interface */ |
746 | if (setup_ipv6_addr(&netdev->ipv6, netdev->ifindex)) { | |
36eb9bde | 747 | ERROR("failed to setup ipv6 addresses for '%s'", |
0ad19a3f | 748 | ifname); |
749 | return -1; | |
750 | } | |
751 | ||
82d5ae15 DL |
752 | /* set the network device up */ |
753 | if (netdev->flags | IFF_UP) { | |
497353b6 | 754 | if (lxc_device_up(current_ifname)) { |
36eb9bde | 755 | ERROR("failed to set '%s' up", current_ifname); |
0ad19a3f | 756 | return -1; |
757 | } | |
758 | ||
759 | /* the network is up, make the loopback up too */ | |
497353b6 | 760 | if (lxc_device_up("lo")) { |
36eb9bde | 761 | ERROR("failed to set the loopback up"); |
0ad19a3f | 762 | return -1; |
763 | } | |
764 | } | |
765 | ||
cd54d859 DL |
766 | DEBUG("'%s' has been setup", current_ifname); |
767 | ||
0ad19a3f | 768 | return 0; |
769 | } | |
770 | ||
5f4535a3 | 771 | static int setup_network(struct lxc_list *network) |
0ad19a3f | 772 | { |
82d5ae15 | 773 | struct lxc_list *iterator; |
82d5ae15 | 774 | struct lxc_netdev *netdev; |
0ad19a3f | 775 | |
5f4535a3 | 776 | lxc_list_for_each(iterator, network) { |
cd54d859 | 777 | |
5f4535a3 | 778 | netdev = iterator->elem; |
82d5ae15 DL |
779 | |
780 | if (setup_netdev(netdev)) { | |
781 | ERROR("failed to setup netdev"); | |
782 | return -1; | |
783 | } | |
784 | } | |
cd54d859 | 785 | |
5f4535a3 DL |
786 | if (!lxc_list_empty(network)) |
787 | INFO("network has been setup"); | |
cd54d859 DL |
788 | |
789 | return 0; | |
0ad19a3f | 790 | } |
791 | ||
792 | int conf_has(const char *name, const char *info) | |
793 | { | |
b09ef133 | 794 | int ret = 0; |
0ad19a3f | 795 | char path[MAXPATHLEN]; |
796 | struct stat st; | |
797 | ||
798 | snprintf(path, MAXPATHLEN, LXCPATH "/%s/%s", name, info); | |
799 | ||
b09ef133 | 800 | if (!stat(path, &st) || !lstat(path, &st)) { |
0ad19a3f | 801 | ret = 1; |
802 | goto out; | |
803 | } | |
804 | ||
805 | if (errno == ENOENT) { | |
806 | ret = 0; | |
807 | goto out; | |
808 | } | |
809 | ||
36eb9bde | 810 | SYSERROR("failed to stat %s info", info); |
0ad19a3f | 811 | out: |
812 | return ret; | |
813 | } | |
814 | ||
089cd8b8 DL |
815 | int lxc_conf_init(struct lxc_conf *conf) |
816 | { | |
817 | conf->rootfs = NULL; | |
818 | conf->fstab = NULL; | |
819 | conf->utsname = NULL; | |
820 | conf->tty = 0; | |
821 | conf->pts = 0; | |
571e6ec8 | 822 | conf->console[0] = '\0'; |
089cd8b8 | 823 | lxc_list_init(&conf->cgroup); |
5f4535a3 | 824 | lxc_list_init(&conf->network); |
e7938e9e | 825 | lxc_list_init(&conf->mount_list); |
089cd8b8 DL |
826 | return 0; |
827 | } | |
828 | ||
82d5ae15 | 829 | static int instanciate_veth(struct lxc_netdev *netdev) |
0ad19a3f | 830 | { |
82d5ae15 DL |
831 | char veth1[IFNAMSIZ]; |
832 | char veth2[IFNAMSIZ]; | |
833 | int ret = -1; | |
13954cce | 834 | |
82d5ae15 DL |
835 | snprintf(veth1, sizeof(veth1), "vethXXXXXX"); |
836 | snprintf(veth2, sizeof(veth2), "vethXXXXXX"); | |
837 | ||
838 | mktemp(veth1); | |
839 | mktemp(veth2); | |
840 | ||
841 | if (!strlen(veth1) || !strlen(veth2)) { | |
842 | ERROR("failed to allocate a temporary name"); | |
843 | return -1; | |
0ad19a3f | 844 | } |
845 | ||
eb14c10a | 846 | if (lxc_veth_create(veth1, veth2)) { |
82d5ae15 DL |
847 | ERROR("failed to create %s-%s/%s", |
848 | veth1, veth2, netdev->ifname); | |
0ad19a3f | 849 | goto out; |
850 | } | |
13954cce | 851 | |
82d5ae15 DL |
852 | if (netdev->mtu) { |
853 | if (lxc_device_set_mtu(veth1, atoi(netdev->mtu))) { | |
854 | ERROR("failed to set mtu '%s' for '%s'", | |
855 | netdev->mtu, veth1); | |
eb14c10a | 856 | goto out_delete; |
75d09f83 DL |
857 | } |
858 | ||
82d5ae15 DL |
859 | if (lxc_device_set_mtu(veth2, atoi(netdev->mtu))) { |
860 | ERROR("failed to set mtu '%s' for '%s'", | |
861 | netdev->mtu, veth2); | |
eb14c10a | 862 | goto out_delete; |
75d09f83 DL |
863 | } |
864 | } | |
865 | ||
82d5ae15 | 866 | if (lxc_bridge_attach(netdev->ifname, veth1)) { |
36eb9bde | 867 | ERROR("failed to attach '%s' to the bridge '%s'", |
82d5ae15 | 868 | veth1, netdev->ifname); |
eb14c10a DL |
869 | goto out_delete; |
870 | } | |
871 | ||
82d5ae15 DL |
872 | netdev->ifindex = if_nametoindex(veth2); |
873 | if (!netdev->ifindex) { | |
36eb9bde | 874 | ERROR("failed to retrieve the index for %s", veth2); |
eb14c10a DL |
875 | goto out_delete; |
876 | } | |
877 | ||
82d5ae15 | 878 | if (netdev->flags & IFF_UP) { |
497353b6 | 879 | if (lxc_device_up(veth1)) { |
36eb9bde | 880 | ERROR("failed to set %s up", veth1); |
eb14c10a | 881 | goto out_delete; |
0ad19a3f | 882 | } |
883 | } | |
884 | ||
82d5ae15 DL |
885 | DEBUG("instanciated veth '%s/%s', index is '%d'", |
886 | veth1, veth2, netdev->ifindex); | |
887 | ||
0ad19a3f | 888 | ret = 0; |
889 | out: | |
0ad19a3f | 890 | return ret; |
eb14c10a DL |
891 | |
892 | out_delete: | |
893 | lxc_device_delete(veth1); | |
894 | goto out; | |
13954cce | 895 | } |
82d5ae15 | 896 | static int instanciate_macvlan(struct lxc_netdev *netdev) |
0ad19a3f | 897 | { |
82d5ae15 DL |
898 | char peer[IFNAMSIZ]; |
899 | int ret = -1; | |
13954cce | 900 | |
82d5ae15 | 901 | snprintf(peer, sizeof(peer), "mcXXXXXX"); |
22ebac19 | 902 | |
82d5ae15 DL |
903 | mktemp(peer); |
904 | ||
905 | if (!strlen(peer)) { | |
906 | ERROR("failed to make a temporary name"); | |
907 | return -1; | |
0ad19a3f | 908 | } |
909 | ||
82d5ae15 | 910 | if (lxc_macvlan_create(netdev->ifname, peer)) { |
36eb9bde | 911 | ERROR("failed to create macvlan interface '%s' on '%s'", |
82d5ae15 | 912 | peer, netdev->ifname); |
0ad19a3f | 913 | goto out; |
914 | } | |
915 | ||
82d5ae15 DL |
916 | netdev->ifindex = if_nametoindex(peer); |
917 | if (!netdev->ifindex) { | |
36eb9bde | 918 | ERROR("failed to retrieve the index for %s", peer); |
82d5ae15 | 919 | goto out_delete; |
22ebac19 | 920 | } |
921 | ||
82d5ae15 | 922 | DEBUG("instanciated macvlan '%s', index is '%d'", peer, netdev->ifindex); |
0ad19a3f | 923 | |
924 | ret = 0; | |
925 | out: | |
0ad19a3f | 926 | return ret; |
22ebac19 | 927 | |
82d5ae15 DL |
928 | out_delete: |
929 | lxc_device_delete(peer); | |
930 | goto out; | |
0ad19a3f | 931 | } |
932 | ||
82d5ae15 | 933 | static int instanciate_phys(struct lxc_netdev *netdev) |
0ad19a3f | 934 | { |
82d5ae15 DL |
935 | netdev->ifindex = if_nametoindex(netdev->ifname); |
936 | if (!netdev->ifindex) { | |
937 | ERROR("failed to retrieve the index for %s", netdev->ifname); | |
0ad19a3f | 938 | return -1; |
939 | } | |
940 | ||
82d5ae15 | 941 | return 0; |
0ad19a3f | 942 | } |
943 | ||
82d5ae15 | 944 | static int instanciate_empty(struct lxc_netdev *netdev) |
0ad19a3f | 945 | { |
82d5ae15 DL |
946 | netdev->ifindex = 0; |
947 | return 0; | |
0ad19a3f | 948 | } |
949 | ||
5f4535a3 | 950 | int lxc_create_network(struct lxc_list *network) |
0ad19a3f | 951 | { |
82d5ae15 | 952 | struct lxc_list *iterator; |
82d5ae15 | 953 | struct lxc_netdev *netdev; |
0ad19a3f | 954 | |
5f4535a3 | 955 | lxc_list_for_each(iterator, network) { |
0ad19a3f | 956 | |
5f4535a3 | 957 | netdev = iterator->elem; |
13954cce | 958 | |
5f4535a3 | 959 | if (netdev->type < 0 || netdev->type > MAXCONFTYPE) { |
82d5ae15 | 960 | ERROR("invalid network configuration type '%d'", |
5f4535a3 | 961 | netdev->type); |
82d5ae15 DL |
962 | return -1; |
963 | } | |
0ad19a3f | 964 | |
5f4535a3 | 965 | if (netdev_conf[netdev->type](netdev)) { |
82d5ae15 DL |
966 | ERROR("failed to create netdev"); |
967 | return -1; | |
968 | } | |
0ad19a3f | 969 | } |
970 | ||
971 | return 0; | |
972 | } | |
973 | ||
5f4535a3 | 974 | int lxc_assign_network(struct lxc_list *network, pid_t pid) |
0ad19a3f | 975 | { |
82d5ae15 | 976 | struct lxc_list *iterator; |
82d5ae15 | 977 | struct lxc_netdev *netdev; |
0ad19a3f | 978 | |
5f4535a3 | 979 | lxc_list_for_each(iterator, network) { |
82d5ae15 | 980 | |
5f4535a3 | 981 | netdev = iterator->elem; |
82d5ae15 DL |
982 | |
983 | if (lxc_device_move(netdev->ifindex, pid)) { | |
984 | ERROR("failed to move '%s' to the container", | |
985 | netdev->ifname); | |
986 | return -1; | |
987 | } | |
988 | ||
989 | DEBUG("move '%s' to '%d'", netdev->ifname, pid); | |
0ad19a3f | 990 | } |
991 | ||
992 | return 0; | |
993 | } | |
994 | ||
5e4a62bf | 995 | int lxc_create_tty(const char *name, struct lxc_conf *conf) |
b0a33c1e | 996 | { |
5e4a62bf | 997 | struct lxc_tty_info *tty_info = &conf->tty_info; |
b0a33c1e | 998 | int i, ret = -1; |
999 | ||
5e4a62bf DL |
1000 | /* no tty in the configuration */ |
1001 | if (!conf->tty) | |
b0a33c1e | 1002 | return 0; |
1003 | ||
5e4a62bf | 1004 | tty_info->nbtty = conf->tty; |
13954cce | 1005 | tty_info->pty_info = |
b0a33c1e | 1006 | malloc(sizeof(*tty_info->pty_info)*tty_info->nbtty); |
13954cce | 1007 | |
b0a33c1e | 1008 | if (!tty_info->pty_info) { |
36eb9bde | 1009 | SYSERROR("failed to allocate pty_info"); |
b0a33c1e | 1010 | goto out; |
1011 | } | |
1012 | ||
1013 | for (i = 0; i < tty_info->nbtty; i++) { | |
13954cce | 1014 | |
b0a33c1e | 1015 | struct lxc_pty_info *pty_info = &tty_info->pty_info[i]; |
1016 | ||
13954cce | 1017 | if (openpty(&pty_info->master, &pty_info->slave, |
b0a33c1e | 1018 | pty_info->name, NULL, NULL)) { |
36eb9bde | 1019 | SYSERROR("failed to create pty #%d", i); |
b0a33c1e | 1020 | goto out_free; |
1021 | } | |
1022 | ||
b035ad62 MS |
1023 | /* Prevent leaking the file descriptors to the container */ |
1024 | fcntl(pty_info->master, F_SETFD, FD_CLOEXEC); | |
1025 | fcntl(pty_info->slave, F_SETFD, FD_CLOEXEC); | |
1026 | ||
b0a33c1e | 1027 | pty_info->busy = 0; |
1028 | } | |
1029 | ||
1030 | ret = 0; | |
1ac470c0 DL |
1031 | |
1032 | INFO("tty's configured"); | |
1033 | ||
b0a33c1e | 1034 | out: |
1035 | return ret; | |
1036 | ||
1037 | out_free: | |
1038 | free(tty_info->pty_info); | |
1039 | goto out; | |
1040 | } | |
1041 | ||
1042 | void lxc_delete_tty(struct lxc_tty_info *tty_info) | |
1043 | { | |
1044 | int i; | |
1045 | ||
1046 | for (i = 0; i < tty_info->nbtty; i++) { | |
1047 | struct lxc_pty_info *pty_info = &tty_info->pty_info[i]; | |
1048 | ||
1049 | close(pty_info->master); | |
1050 | close(pty_info->slave); | |
1051 | } | |
1052 | ||
1053 | free(tty_info->pty_info); | |
1054 | tty_info->nbtty = 0; | |
1055 | } | |
1056 | ||
571e6ec8 | 1057 | int lxc_setup(const char *name, struct lxc_conf *lxc_conf) |
0ad19a3f | 1058 | { |
571e6ec8 | 1059 | if (setup_utsname(lxc_conf->utsname)) { |
36eb9bde | 1060 | ERROR("failed to setup the utsname for '%s'", name); |
95b5ffaf | 1061 | return -1; |
0ad19a3f | 1062 | } |
1063 | ||
5f4535a3 | 1064 | if (setup_network(&lxc_conf->network)) { |
36eb9bde | 1065 | ERROR("failed to setup the network for '%s'", name); |
95b5ffaf | 1066 | return -1; |
0ad19a3f | 1067 | } |
1068 | ||
571e6ec8 | 1069 | if (setup_cgroup(name, &lxc_conf->cgroup)) { |
36eb9bde | 1070 | ERROR("failed to setup the cgroups for '%s'", name); |
95b5ffaf | 1071 | return -1; |
0ad19a3f | 1072 | } |
1073 | ||
571e6ec8 | 1074 | if (setup_mount(lxc_conf->fstab)) { |
36eb9bde | 1075 | ERROR("failed to setup the mounts for '%s'", name); |
95b5ffaf | 1076 | return -1; |
576f946d | 1077 | } |
1078 | ||
e7938e9e MN |
1079 | if (setup_mount_entries(&lxc_conf->mount_list)) { |
1080 | ERROR("failed to setup the mount entries for '%s'", name); | |
1081 | return -1; | |
1082 | } | |
1083 | ||
571e6ec8 | 1084 | if (setup_console(lxc_conf->rootfs, lxc_conf->console)) { |
36eb9bde | 1085 | ERROR("failed to setup the console for '%s'", name); |
95b5ffaf | 1086 | return -1; |
6e590161 | 1087 | } |
1088 | ||
571e6ec8 | 1089 | if (setup_tty(lxc_conf->rootfs, &lxc_conf->tty_info)) { |
36eb9bde | 1090 | ERROR("failed to setup the ttys for '%s'", name); |
95b5ffaf | 1091 | return -1; |
b0a33c1e | 1092 | } |
1093 | ||
571e6ec8 | 1094 | if (setup_rootfs(lxc_conf->rootfs)) { |
36eb9bde | 1095 | ERROR("failed to set rootfs for '%s'", name); |
95b5ffaf | 1096 | return -1; |
ed502555 | 1097 | } |
1098 | ||
571e6ec8 | 1099 | if (setup_pts(lxc_conf->pts)) { |
36eb9bde | 1100 | ERROR("failed to setup the new pts instance"); |
95b5ffaf | 1101 | return -1; |
3c26f34e | 1102 | } |
1103 | ||
cd54d859 DL |
1104 | NOTICE("'%s' is setup.", name); |
1105 | ||
0ad19a3f | 1106 | return 0; |
1107 | } |