[libvirt] 3rd pass at virt-login-shell for joining an LXC Container

All comments applied except for comments below. Also additional cleanup of error handling.
You'll need to call virGetUserDirectory() before any fork(), since it calls code which is not async-signal safe.
The reason I did this is I needed to call it after the shell setuid, if I call it earlier it sees the UID=0 and gives me /root. I added a new interface char *virGetUserDirectoryByUID(uid_t uid); Since the original interface does not take a UID.
Hmm, looking at this again, I'm wondering you need to fork() at all. In virsh we do the double-fork dance, because virsh is an interactive shell & we don't want to affect other parts of virsh.
This login shell though is different - its only job is to run inside the namespace. So can't the main process just enter the namespace directly ?
I think you still need to do the second fork to make sure the /bin/sh PID gets put in the container. Otherwise you will have a process running within the container without a local PID. [PATCH] virt-login-shell joins users into lxc container.

From: Dan Walsh <dwalsh@redhat.com> Openshift wants to have their gears stuck into a container when they login to the system. virt-login-shell will join a running gear with the username of the person running it, or attempt to start the container if it is not running. (Currently containers do not exist if they are not running, so I can not test this feature. But the code is there). This tool needs to be setuid since joining a container (nsjoin) requires privs. The root user is not allowed to execute this command. When this tool is run by a normal user it will only join the "users" container. Only users who are listed as valid_users in /etc/libvirt/virt-login-shell.conf are allowed to join containers using this tool. By default no users are allowed. --- .gitignore | 1 + libvirt.spec.in | 3 + mingw-libvirt.spec.in | 5 + po/POTFILES.in | 1 + src/libvirt_private.syms | 1 + src/util/virutil.c | 7 + src/util/virutil.h | 1 + tools/Makefile.am | 30 ++++- tools/virt-login-shell.c | 312 ++++++++++++++++++++++++++++++++++++++++++++ tools/virt-login-shell.conf | 24 ++++ tools/virt-login-shell.pod | 62 +++++++++ 11 files changed, 446 insertions(+), 1 deletion(-) create mode 100644 tools/virt-login-shell.c create mode 100755 tools/virt-login-shell.conf create mode 100644 tools/virt-login-shell.pod diff --git a/.gitignore b/.gitignore index 3efc2e4..dcb57bc 100644 --- a/.gitignore +++ b/.gitignore @@ -212,6 +212,7 @@ /tools/libvirt-guests.init /tools/libvirt-guests.service /tools/libvirt-guests.sh +/tools/virt-login-shell /tools/virsh /tools/virsh-*-edit.c /tools/virt-*-validate diff --git a/libvirt.spec.in b/libvirt.spec.in index e0e0004..ffdb9dc 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -1976,14 +1976,17 @@ fi %doc AUTHORS ChangeLog.gz NEWS README COPYING COPYING.LESSER TODO %config(noreplace) %{_sysconfdir}/libvirt/libvirt.conf +%config(noreplace) %{_sysconfdir}/libvirt/virt-login-shell.conf %{_mandir}/man1/virsh.1* %{_mandir}/man1/virt-xml-validate.1* %{_mandir}/man1/virt-pki-validate.1* %{_mandir}/man1/virt-host-validate.1* +%{_mandir}/man1/virt-login-shell.1* %{_bindir}/virsh %{_bindir}/virt-xml-validate %{_bindir}/virt-pki-validate %{_bindir}/virt-host-validate +%attr(4755, root, root) %{_bindir}/virt-login-shell %{_libdir}/lib*.so.* %if %{with_dtrace} diff --git a/mingw-libvirt.spec.in b/mingw-libvirt.spec.in index aa39231..36c9481 100644 --- a/mingw-libvirt.spec.in +++ b/mingw-libvirt.spec.in @@ -185,9 +185,11 @@ rm -rf $RPM_BUILD_ROOT%{mingw64_libexecdir}/libvirt-guests.sh %files -n mingw32-libvirt %dir %{mingw32_sysconfdir}/libvirt/ %config(noreplace) %{mingw32_sysconfdir}/libvirt/libvirt.conf +%config(noreplace) %{_sysconfdir}/libvirt/virt-login-shell.conf %{mingw32_bindir}/libvirt-0.dll %{mingw32_bindir}/virsh.exe +%{mingw32_bindir}/virt-login-shell.exe %{mingw32_bindir}/virt-xml-validate %{mingw32_bindir}/virt-pki-validate %{mingw32_bindir}/virt-host-validate.exe @@ -236,6 +238,7 @@ rm -rf $RPM_BUILD_ROOT%{mingw64_libexecdir}/libvirt-guests.sh %{mingw32_mandir}/man1/virt-xml-validate.1* %{mingw32_mandir}/man1/virt-pki-validate.1* %{mingw32_mandir}/man1/virt-host-validate.1* +%{mingw32_mandir}/man1/virt-login-shell.1* %files -n mingw32-libvirt-static %{mingw32_libdir}/libvirt.a @@ -246,9 +249,11 @@ rm -rf $RPM_BUILD_ROOT%{mingw64_libexecdir}/libvirt-guests.sh %files -n mingw64-libvirt %dir %{mingw64_sysconfdir}/libvirt/ %config(noreplace) %{mingw64_sysconfdir}/libvirt/libvirt.conf +%config(noreplace) %{_sysconfdir}/libvirt/virt-login-shell.conf %{mingw64_bindir}/libvirt-0.dll %{mingw64_bindir}/virsh.exe +%{mingw64_bindir}/virt-login-shell.exe %{mingw64_bindir}/virt-xml-validate %{mingw64_bindir}/virt-pki-validate %{mingw64_bindir}/virt-host-validate.exe diff --git a/po/POTFILES.in b/po/POTFILES.in index 1fd84af..884b70a 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -231,3 +231,4 @@ tools/virt-host-validate-common.c tools/virt-host-validate-lxc.c tools/virt-host-validate-qemu.c tools/virt-host-validate.c +tools/virt-login-shell.c diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 7790ede..9cc1670 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2026,6 +2026,7 @@ virGetUnprivSGIOSysfsPath; virGetUserCacheDirectory; virGetUserConfigDirectory; virGetUserDirectory; +virGetUserDirectoryByUID; virGetUserID; virGetUserName; virGetUserRuntimeDirectory; diff --git a/src/util/virutil.c b/src/util/virutil.c index 0b54ef7..05b6465 100644 --- a/src/util/virutil.c +++ b/src/util/virutil.c @@ -757,6 +757,13 @@ char *virGetUserDirectory(void) return ret; } +char *virGetUserDirectoryByUID(uid_t uid) +{ + char *ret; + virGetUserEnt(uid, NULL, NULL, &ret); + return ret; +} + static char *virGetXDGDirectory(const char *xdgenvname, const char *xdgdefdir) { const char *path = getenv(xdgenvname); diff --git a/src/util/virutil.h b/src/util/virutil.h index 0083c88..cde1de5 100644 --- a/src/util/virutil.h +++ b/src/util/virutil.h @@ -111,6 +111,7 @@ static inline int getgid (void) { return 0; } char *virGetHostname(void); char *virGetUserDirectory(void); +char *virGetUserDirectoryByUID(uid_t uid); char *virGetUserConfigDirectory(void); char *virGetUserCacheDirectory(void); char *virGetUserRuntimeDirectory(void); diff --git a/tools/Makefile.am b/tools/Makefile.am index 644a86d..00c582a 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -37,6 +37,7 @@ EXTRA_DIST = \ virt-pki-validate.in \ virt-sanlock-cleanup.in \ virt-sanlock-cleanup.8 \ + virt-login-shell.pod \ virsh.pod \ libvirt-guests.sysconf \ virsh-edit.c \ @@ -52,8 +53,11 @@ EXTRA_DIST = \ DISTCLEANFILES = +confdir = $(sysconfdir)/libvirt +conf_DATA = virt-login-shell.conf + bin_SCRIPTS = virt-xml-validate virt-pki-validate -bin_PROGRAMS = virsh virt-host-validate +bin_PROGRAMS = virsh virt-host-validate virt-login-shell libexec_SCRIPTS = libvirt-guests.sh if WITH_SANLOCK @@ -65,6 +69,7 @@ dist_man1_MANS = \ virt-host-validate.1 \ virt-pki-validate.1 \ virt-xml-validate.1 \ + virt-login-shell.1 \ virsh.1 if WITH_SANLOCK dist_man8_MANS = virt-sanlock-cleanup.8 @@ -128,6 +133,24 @@ virt_host_validate_CFLAGS = \ $(COVERAGE_CFLAGS) \ $(NULL) +virt_login_shell_SOURCES = \ + virt-login-shell.conf \ + virt-login-shell.c + +virt_login_shell_LDFLAGS = $(COVERAGE_LDFLAGS) +virt_login_shell_LDADD = \ + $(STATIC_BINARIES) \ + $(PIE_LDFLAGS) \ + $(RELRO_LDFLAGS) \ + ../src/libvirt.la \ + ../src/libvirt-lxc.la \ + ../gnulib/lib/libgnu.la + +virt_login_shell_CFLAGS = \ + $(WARN_CFLAGS) \ + $(PIE_CFLAGS) \ + $(COVERAGE_CFLAGS) + virsh_SOURCES = \ console.c console.h \ virsh.c virsh.h \ @@ -189,6 +212,11 @@ virsh_win_icon.$(OBJEXT): virsh_win_icon.rc --output-format coff --output $@ endif +virt-login-shell.1: virt-login-shell.pod $(top_srcdir)/configure.ac + $(AM_V_GEN)$(POD2MAN) $< $(srcdir)/$@ \ + && if grep 'POD ERROR' $(srcdir)/$@ ; then \ + rm $(srcdir)/$@; exit 1; fi + virsh.1: virsh.pod $(top_srcdir)/configure.ac $(AM_V_GEN)$(POD2MAN) $< $(srcdir)/$@ \ && if grep 'POD ERROR' $(srcdir)/$@ ; then \ diff --git a/tools/virt-login-shell.c b/tools/virt-login-shell.c new file mode 100644 index 0000000..b838ca2 --- /dev/null +++ b/tools/virt-login-shell.c @@ -0,0 +1,312 @@ +/* + * virt-login-shell.c: a shell to connect to a container + * + * Copyright (C) 2013 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + * + * Daniel Walsh <dwalsh@redhat.com> + */ +#include <config.h> +#include "virconf.h" +#include "virutil.h" +#include "virfile.h" +#include "virprocess.h" +#include "configmake.h" +#include "virstring.h" +#include "viralloc.h" +#include "vircommand.h" + +#include <stdarg.h> +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <fnmatch.h> +#define VIR_FROM_THIS VIR_FROM_NONE + +static ssize_t nfdlist = 0; +static int *fdlist = NULL; + +static void virLoginShellFini(virConnectPtr conn, virDomainPtr dom) { + size_t i; + for (i=0; i < nfdlist; i++) + VIR_FORCE_CLOSE(fdlist[i]); + VIR_FREE(fdlist); + nfdlist = 0; + if (dom) + virDomainFree(dom); + if (conn) + virConnectClose(conn); +} + +static int virLoginShellAllowedUser(virConfPtr conf, + uid_t uid, + gid_t gid, + char *name) { + virConfValuePtr p; + int ret = -1; + char *ptr = NULL; + size_t i; + gid_t *groups = NULL; + char *gname = NULL; + + p = virConfGetValue(conf, "allowed_users"); + if (!p) { + errno = EPERM; + fprintf(stderr, _("%s is not allowed to connect a container: %m\n"), name); + goto cleanup; + } + if (p && p->type == VIR_CONF_LIST) { + size_t len; + virConfValuePtr pp; + + /* Calc length and check items */ + for (len = 0, pp = p->list; pp; len++, pp = pp->next) { + if (pp->type != VIR_CONF_STRING) { + fprintf(stderr, _("shell must be a list of strings\n")); + goto cleanup; + } else { + if (fnmatch(pp->str, name, 0) == 0) { + ret = 0; + goto cleanup; + } + if (pp->str[0] == '%') { + ptr=&pp->str[1]; + if (!ptr) + continue; + if (virGetGroupList(uid, gid, &groups) < 0) { + fprintf(stderr, _("Unable to get group list for %s: %m\n"), name); + goto cleanup; + } + for (i=0; groups[i]; i++) { + if (!(gname = virGetGroupName(groups[i]))) + continue; + if (fnmatch(ptr, gname, 0) == 0) { + ret = 0; + goto cleanup; + } + VIR_FREE(gname); gname=NULL; + } + } + } + } + } + errno = EPERM; + fprintf(stderr, _("%s is not allowed to connect a container: %m\n"), name); +cleanup: + VIR_FREE(gname); + VIR_FREE(groups); + return ret; +} + +static char **virLoginShellGetShellArgv(virConfPtr conf) { + size_t i; + char **shargv; + virConfValuePtr p; + p = virConfGetValue(conf, "shell"); + if (!p) + return virStringSplit("/bin/sh --login", " ", 3); + + if (p && p->type == VIR_CONF_LIST) { + size_t len; + virConfValuePtr pp; + + /* Calc length and check items */ + for (len = 0, pp = p->list; pp; len++, pp = pp->next) { + if (pp->type != VIR_CONF_STRING) { + fprintf(stderr, _("shell must be a list of strings\n")); + goto error; + } + } + + if (VIR_ALLOC_N(shargv, len + 1) < 0) + goto error; + for (i = 0, pp = p->list; pp; i++, pp = pp->next) { + if (VIR_STRDUP(shargv[i], pp->str) < 0) + goto error; + } + shargv[len] = NULL; + } + return shargv; +error: + virStringFreeList(shargv); + return NULL; +} + +int +main(int argc, char **argv) { + virConfPtr conf = NULL; + const char *login_shell_path = SYSCONFDIR "/libvirt/virt-login-shell.conf"; + pid_t cpid; + int ret = EXIT_FAILURE; + int status; + int status2; + uid_t uid = getuid(); + gid_t gid = getgid(); + char *name; + char **shargv = NULL; + virSecurityModelPtr secmodel = NULL; + virSecurityLabelPtr seclabel = NULL; + virDomainPtr dom = NULL; + virConnectPtr conn = NULL; + char *homedir = NULL; + if (!setlocale(LC_ALL, "")) { + perror("setlocale"); + /* failure to setup locale is not fatal */ + } + if (!bindtextdomain(PACKAGE, LOCALEDIR)) { + perror("bindtextdomain"); + return ret; + } + if (!textdomain(PACKAGE)) { + perror("textdomain"); + return ret; + } + + if (uid == 0) { + errno = EPERM; + fprintf(stderr, _("%s must be run by non root users: %m\n"), argv[0]); + goto cleanup; + } + + if (argc > 1) { + errno = EINVAL; + fprintf(stderr, _("%s takes no options: %m\n"), argv[0]); + goto cleanup; + } + + name = virGetUserName(uid); + if (!name) { + fprintf(stderr, _("Failed to get username for %d: %m\n"), uid); + goto cleanup; + } + homedir = virGetUserDirectoryByUID(uid); + if (!homedir) { + fprintf(stderr, _("Can't read %s home directory: %m\n"), name); + goto cleanup; + } + + if (!(conf = virConfReadFile(login_shell_path, 0))) { + fprintf(stderr, _("Failed to read %s: %m\n"), login_shell_path); + goto cleanup; + } + if (virLoginShellAllowedUser(conf, uid, gid, name) < 0) + goto cleanup; + + if (!(shargv = virLoginShellGetShellArgv(conf))) + goto cleanup; + + conn = virConnectOpen("lxc:///"); + if (!conn) { + fprintf(stderr, _("Unable to connect to lxc:///: %m\n")); + goto cleanup; + } + + dom = virDomainLookupByName(conn, name); + if (!dom) { + fprintf(stderr, _("Container %s does not exist: %m\n"), name); + goto cleanup; + } + + if (! virDomainIsActive(dom) && virDomainCreate(dom)) { + virErrorPtr last_error; + last_error = virGetLastError(); + if (last_error->code != VIR_ERR_OPERATION_INVALID) { + fprintf(stderr,_("Can't create %s container: %s\n"), name, virGetLastErrorMessage()); + goto cleanup; + } + } + + if ((nfdlist = virDomainLxcOpenNamespace(dom, &fdlist, 0)) < 0) { + fprintf(stderr,_("Can't open %s namespace: %m\n"), name); + goto cleanup; + } + + if (VIR_ALLOC(secmodel) < 0) + goto cleanup; + if (VIR_ALLOC(seclabel) < 0) + goto cleanup; + if (virNodeGetSecurityModel(conn, secmodel) < 0) + goto cleanup; + if (virDomainGetSecurityLabel(dom, seclabel) < 0) + goto cleanup; + + if (virFork(&cpid) < 0) + goto cleanup; + + if (cpid == 0) { + gid_t *groups = NULL; + int ngroups; + pid_t ccpid; + + /* Fork once because we don't want to affect + * virt-login-shell's namespace itself + */ + if (virSetUIDGID(0, 0, 0, 0) < 0) { + fprintf(stderr, _("Unable to setresuid: %m\n")); + return EXIT_FAILURE; + } + + if (virDomainLxcEnterSecurityLabel(secmodel, + seclabel, + NULL, + 0) < 0) + return EXIT_FAILURE; + + if (nfdlist > 0) { + if (virDomainLxcEnterNamespace(dom, + nfdlist, + fdlist, + NULL, + NULL, + 0) < 0) + return EXIT_FAILURE; + } + + if ((ngroups = virGetGroupList(uid, gid, &groups)) < 0) + return EXIT_FAILURE; + + ret = virSetUIDGIDWithCaps(uid, gid, groups, ngroups, 0, 0); + VIR_FREE(groups); + if (ret < 0) + return EXIT_FAILURE; + + if (virFork(&ccpid) < 0) + return EXIT_FAILURE; + + if (ccpid == 0) { + if (chdir(homedir) < 0) { + fprintf(stderr, _("Unable chdir(%s): %m\n"), homedir); + return EXIT_FAILURE; + } + if (execv(shargv[0], (char *const*) shargv) < 0) { + fprintf(stderr, _("Unable exec shell %s: %m\n"), shargv[0]); + return EXIT_FAILURE; + } + } + return virProcessWait(ccpid, &status2); + } + ret = virProcessWait(cpid, &status); + +cleanup: + virConfFree(conf); + virLoginShellFini(conn, dom); + virStringFreeList(shargv); + VIR_FREE(name); + VIR_FREE(homedir); + VIR_FREE(seclabel); + VIR_FREE(secmodel); + return ret; +} diff --git a/tools/virt-login-shell.conf b/tools/virt-login-shell.conf new file mode 100755 index 0000000..107424a --- /dev/null +++ b/tools/virt-login-shell.conf @@ -0,0 +1,24 @@ +# Master configuration file for the virt-login-shell program. +# All settings described here are optional - if omitted, sensible +# defaults are used. + +# By default, virt-login-shell will connect you to a container running +# with the /bin/sh program. Modify the shell variable if you want your +# users to run a different shell or a setup containe when joining a +# container. Shell commands must be a list of commands/options separated by +# comma and delimited by square brackets. Defaults to: /bin/sh --login. +# Modify and uncomment the following to modify the login shell. +# shell = [ "/bin/sh", "--login" ] + +# allowed_users specifies the user name of all users that are allowed to execute +# virt-login-shell. You can specify the users as a comma separated list of usernames. +# The variable support glob syntaxt, if you specify no names +# (default) then no one except root is allowed to use this executable. +# to allow fred and joe only +# allowed_users = ["fred", "joe"] +# To allow all users within a specific group prefix the group name with %, etc +# allowed_users = ["%engineers"] +# to allow all users specify the following +# allowed_users = [ "*" ] +# disallow all users +# allowed_users = [] diff --git a/tools/virt-login-shell.pod b/tools/virt-login-shell.pod new file mode 100644 index 0000000..0cd35cf --- /dev/null +++ b/tools/virt-login-shell.pod @@ -0,0 +1,62 @@ +=head1 NAME + +virt-login-shell - tool to execute a shell within a container matching the users name + +=head1 SYNOPSIS + +B<virt-login-shell> + +=head1 DESCRIPTION + +The B<virt-login-shell> program is setuid shell that is used to join +an LXC container that matches the users name. If the container is not +running virt-login-shell will attempt to start the container. +virt-sandbox-shell is not allowed to be run by root. Normal users will get +added to a container that matches their username, if it exists. And they are +configured in /etc/libvirt/virt-login-shell.conf. + +The basic structure of most virt-login-shell usage is: + + virt-login-shell + +=head1 CONFIG + +By default, virt-login-shell will execute the /bin/sh program for the user. +You can modify this behaviour by defining the shell variable in /etc/libvirt/virt-login-shell.conf. + +eg. shell = [ "/bin/ksh", "--login"] + +By default no users are allowed to user virt-login-shell, if you want to allow +certain users to use virt-login-shell, you need to modify the allowed_users variable in /etc/libvirt/virt-login-shell.conf. + +eg. allowed_users = [ "tom", "dick", "harry" ] + +=head1 BUGS + +Report any bugs discovered to the libvirt community via the mailing +list C<http://libvirt.org/contact.html> or bug tracker C<http://libvirt.org/bugs.html>. +Alternatively report bugs to your software distributor / vendor. + +=head1 AUTHORS + + Please refer to the AUTHORS file distributed with libvirt. + + Daniel Walsh <dwalsh at redhat dot com> + +=head1 COPYRIGHT + +Copyright (C) 2013 Red Hat, Inc., and the authors listed in the +libvirt AUTHORS file. + +=head1 LICENSE + +virt-login-shell is distributed under the terms of the GNU LGPL v2+. +This is free software; see the source for copying conditions. There +is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR +PURPOSE + +=head1 SEE ALSO + +L<virsh(1)>, L<http://www.libvirt.org/> + +=cut -- 1.8.3.1

On Sat, Jul 20, 2013 at 07:46:33AM -0400, dwalsh@redhat.com wrote:
From: Dan Walsh <dwalsh@redhat.com>
Openshift wants to have their gears stuck into a container when they login to the system. virt-login-shell will join a running gear with the username of the person running it, or attempt to start the container if it is not running. (Currently containers do not exist if they are not running, so I can not test this feature. But the code is there).
This tool needs to be setuid since joining a container (nsjoin) requires privs. The root user is not allowed to execute this command. When this tool is run by a normal user it will only join the "users" container.
Only users who are listed as valid_users in /etc/libvirt/virt-login-shell.conf are allowed to join containers using this tool. By default no users are allowed. --- .gitignore | 1 + libvirt.spec.in | 3 + mingw-libvirt.spec.in | 5 + po/POTFILES.in | 1 + src/libvirt_private.syms | 1 + src/util/virutil.c | 7 + src/util/virutil.h | 1 + tools/Makefile.am | 30 ++++- tools/virt-login-shell.c | 312 ++++++++++++++++++++++++++++++++++++++++++++ tools/virt-login-shell.conf | 24 ++++ tools/virt-login-shell.pod | 62 +++++++++ 11 files changed, 446 insertions(+), 1 deletion(-) create mode 100644 tools/virt-login-shell.c create mode 100755 tools/virt-login-shell.conf create mode 100644 tools/virt-login-shell.pod
ACK to this patch. Technically since we're post freeze we shouldn't commit this until 1.1.2, but since this is an entirely new program perhaps we could make an exception here ? Thoughts ? It doesn't hugely matter either way, it'd just make life a little easier to have it in 1.1.1 Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 07/25/2013 11:06 AM, Daniel P. Berrange wrote:
On Sat, Jul 20, 2013 at 07:46:33AM -0400, dwalsh@redhat.com wrote:
From: Dan Walsh <dwalsh@redhat.com>
Openshift wants to have their gears stuck into a container when they login to the system. virt-login-shell will join a running gear with the username of the person running it, or attempt to start the container if it is not running. (Currently containers do not exist if they are not running, so I can not test this feature. But the code is there).
This tool needs to be setuid since joining a container (nsjoin) requires privs. The root user is not allowed to execute this command. When this tool is run by a normal user it will only join the "users" container.
Only users who are listed as valid_users in /etc/libvirt/virt-login-shell.conf are allowed to join containers using this tool. By default no users are allowed. ---
ACK to this patch.
Technically since we're post freeze we shouldn't commit this until 1.1.2, but since this is an entirely new program perhaps we could make an exception here ? Thoughts ?
It was posted pre-freeze; the only reason it didn't make freeze was lack of timely review.
It doesn't hugely matter either way, it'd just make life a little easier to have it in 1.1.1
I see little risk in including it in 1.1.1, since it really is a new feature without touching existing code. -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On 07/25/2013 11:09 AM, Eric Blake wrote:
ACK to this patch.
Technically since we're post freeze we shouldn't commit this until 1.1.2, but since this is an entirely new program perhaps we could make an exception here ? Thoughts ?
It was posted pre-freeze; the only reason it didn't make freeze was lack of timely review.
That said, since the patch adds a setuid binary, I would like to review it as a second set of eyes before we push it. -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On 07/25/2013 01:23 PM, Eric Blake wrote:
On 07/25/2013 11:09 AM, Eric Blake wrote:
ACK to this patch.
Technically since we're post freeze we shouldn't commit this until 1.1.2, but since this is an entirely new program perhaps we could make an exception here ? Thoughts ?
It was posted pre-freeze; the only reason it didn't make freeze was lack of timely review.
That said, since the patch adds a setuid binary, I would like to review it as a second set of eyes before we push it.
The code was sent to a few reviewers, but I have not heard back, I will resend the latest code. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.13 (GNU/Linux) Comment: Using GnuPG with Thunderbird - http://www.enigmail.net/ iEYEARECAAYFAlHxX4oACgkQrlYvE4MpobMKhgCguv0ydgBjcEkC5Kxf/rt3OAwv u4gAn0bA6VM83MJm91dc5gOSC9gJpdcu =lA4e -----END PGP SIGNATURE-----

On 07/20/2013 05:46 AM, dwalsh@redhat.com wrote:
From: Dan Walsh <dwalsh@redhat.com>
Openshift wants to have their gears stuck into a container when they login to the system. virt-login-shell will join a running gear with the username of the person running it, or attempt to start the container if it is not running. (Currently containers do not exist if they are not running, so I can not test this feature. But the code is there).
This tool needs to be setuid since joining a container (nsjoin) requires privs. The root user is not allowed to execute this command. When this tool is run by a normal user it will only join the "users" container.
Only users who are listed as valid_users in /etc/libvirt/virt-login-shell.conf are allowed to join containers using this tool. By default no users are allowed. ---
I'm afraid this needs another version, and thus I'm reluctant to take it into 1.1.1 if we can't get it fixed quickly.
+++ b/mingw-libvirt.spec.in
@@ -246,9 +249,11 @@ rm -rf $RPM_BUILD_ROOT%{mingw64_libexecdir}/libvirt-guests.sh %files -n mingw64-libvirt %dir %{mingw64_sysconfdir}/libvirt/ %config(noreplace) %{mingw64_sysconfdir}/libvirt/libvirt.conf +%config(noreplace) %{_sysconfdir}/libvirt/virt-login-shell.conf
This probably needs to be %{mingw64_sysconfdir}. For that matter, does virt-login-shell even make sense on mingw, or is it a Linux-only concept where the mingw .spec file should be ensuring that it is not compiled nor installed?
%{mingw64_bindir}/libvirt-0.dll %{mingw64_bindir}/virsh.exe +%{mingw64_bindir}/virt-login-shell.exe
In other words, is virt-login-shell.exe even capable of doing anything useful on mingw?
+++ b/src/libvirt_private.syms @@ -2026,6 +2026,7 @@ virGetUnprivSGIOSysfsPath; virGetUserCacheDirectory; virGetUserConfigDirectory; virGetUserDirectory; +virGetUserDirectoryByUID;
This looks like an independently useful function; it would be worth splitting this patch into a series where patch 1 adds the new function, and patch 2 takes advantage of it, rather than mixing it all into one patch.
+char *virGetUserDirectoryByUID(uid_t uid) +{ + char *ret; + virGetUserEnt(uid, NULL, NULL, &ret); + return ret; +}
Is it worth rewriting the existing: char *virGetUserDirectory(void) { return virGetUserDirectoryByUID(geteuid()); } Doing so would give us instant code coverage of the new function.
bin_SCRIPTS = virt-xml-validate virt-pki-validate -bin_PROGRAMS = virsh virt-host-validate +bin_PROGRAMS = virsh virt-host-validate virt-login-shell
Since the .spec file is installing this file as setuid, shouldn't 'make install' attempt to do so likewise (that is, do more than just the normal reliance on automake's bin_PROGRAMS, which doesn't set special mode bits)? Or is setuid something rare enough that only spec files need to be requesting it? I'm thinking about the case of someone that does './configure --prefix=$HOME' with the intent of installing local tools - will virt-login-shell be usable by such a user?
+++ b/tools/virt-login-shell.c @@ -0,0 +1,312 @@ +/* + * virt-login-shell.c: a shell to connect to a container + *
+#include <config.h> +#include "virconf.h" +#include "virutil.h" +#include "virfile.h" +#include "virprocess.h" +#include "configmake.h" +#include "virstring.h" +#include "viralloc.h" +#include "vircommand.h" + +#include <stdarg.h> +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <fnmatch.h>
We tend to list system headers before "" headers; but it shouldn't be a problem that you did it the other way around.
+#define VIR_FROM_THIS VIR_FROM_NONE + +static ssize_t nfdlist = 0; +static int *fdlist = NULL;
static variables are already 0-initialized without needing an explicit initializer; while gcc is smart enough to optimize explicit zero optimization into using the bss, not all compilers are that good.
+ +static void virLoginShellFini(virConnectPtr conn, virDomainPtr dom) {
unnecessary doubled space Style nit: we prefer: static void virLoginShellFini(virConnectPtr conn, virDomainPtr dom) {
+ size_t i;
Style nit: we prefer blank line between local variable declarations and expressions.
+ for (i=0; i < nfdlist; i++)
Style nit: we prefer spaces around '='.
+ VIR_FORCE_CLOSE(fdlist[i]); + VIR_FREE(fdlist);
This looks like leak prevention (good so that tools like valgrind don't complain)...
+ nfdlist = 0;
...whereas this may be dead code - if we are not supposed to use anything after virLoginShellFini() has been called (because we are about to exit or exec), then cleaning things back to a zero-initialized state is wasted cycles. On the other hand, being defensive against reuse can't hurt, and as this will be setuid, paranoia is okay.
+ if (dom) + virDomainFree(dom); + if (conn) + virConnectClose(conn); +} + +static int virLoginShellAllowedUser(virConfPtr conf, + uid_t uid, + gid_t gid, + char *name) {
Do you modify 'name'? If not, add 'const'. Style nit: static int virLoginShellAllowedUser(virConfPtr conf, uid_t uid, gid_t gid, const char *name) {
+ virConfValuePtr p; + int ret = -1; + char *ptr = NULL; + size_t i; + gid_t *groups = NULL; + char *gname = NULL; + + p = virConfGetValue(conf, "allowed_users"); + if (!p) { + errno = EPERM; + fprintf(stderr, _("%s is not allowed to connect a container: %m\n"), name);
%m is not POSIX; it is not safe to use here. Also, why do you need to use fprintf instead of more typical virReportSystemError(), which already provides strerror() as part of the logged message?
+ goto cleanup; + } + if (p && p->type == VIR_CONF_LIST) { + size_t len; + virConfValuePtr pp; + + /* Calc length and check items */ + for (len = 0, pp = p->list; pp; len++, pp = pp->next) { + if (pp->type != VIR_CONF_STRING) { + fprintf(stderr, _("shell must be a list of strings\n")); + goto cleanup; + } else { + if (fnmatch(pp->str, name, 0) == 0) { + ret = 0; + goto cleanup; + } + if (pp->str[0] == '%') { + ptr=&pp->str[1];
Style nit: space around '='
+ if (!ptr) + continue;
So if the user passes "%" as one of their strings in the list, we just ignore it - but only after checking it against fnmatch? I'm not following why % is allowed, or what it does. [\me reads the .conf file] Oh, %engineers is a way to match all users within the 'engineers' group. But if there is a %, then I think you DON'T want to fnmatch() right away.
+ if (virGetGroupList(uid, gid, &groups) < 0) { + fprintf(stderr, _("Unable to get group list for %s: %m\n"), name);
%m is unacceptable. virGetGroupList() already reported its own error message with virReportError, so why are you duplicating the error message here with fprintf? Shouldn't we instead be setting up error logging to use virError set to stderr, to take advantage of all the helper routines we call that already log their own errors?
+ goto cleanup; + } + for (i=0; groups[i]; i++) {
space around '='
+ if (!(gname = virGetGroupName(groups[i]))) + continue; + if (fnmatch(ptr, gname, 0) == 0) { + ret = 0; + goto cleanup; + } + VIR_FREE(gname); gname=NULL;
'gname=NULL;' is spurious; VIR_FREE(gname) already sets gname to NULL.
+ } + } + } + }
Memory leak. You have an outer loop that can potentially iterate over multiple user tokens, and a call to virGetGroupList that allocates groups once per user token starting with %, but nothing that frees groups between iterations.
+ } + errno = EPERM;
You set errno...
+ fprintf(stderr, _("%s is not allowed to connect a container: %m\n"), name);
...then promptly nuke it with an fprintf(). If you want errno to be in a known state after this function returns failure, then you must set it after anything else that might alter errno.
+cleanup: + VIR_FREE(gname); + VIR_FREE(groups); + return ret; +} + +static char **virLoginShellGetShellArgv(virConfPtr conf) {
static char ** virLoginShellGetShellArgv(virConfPtr conf) {
+ size_t i; + char **shargv; + virConfValuePtr p; + p = virConfGetValue(conf, "shell"); + if (!p) + return virStringSplit("/bin/sh --login", " ", 3);
Use the POSIX spelling s/--login/-l/. After all, on debian, people install dash as /bin/sh, but dash doesn't like long options. Just because /bin/sh is bash on Fedora doesn't mean it is safe to abuse that fact.
+ + if (p && p->type == VIR_CONF_LIST) { + size_t len; + virConfValuePtr pp; + + /* Calc length and check items */ + for (len = 0, pp = p->list; pp; len++, pp = pp->next) { + if (pp->type != VIR_CONF_STRING) { + fprintf(stderr, _("shell must be a list of strings\n")); + goto error; + } + }
I'm wondering if we should have a helper function that makes it easier to parse a list of strings out of a conf file, instead of having to open-code our own iterator (this is the second time you've done a string-list iteration in this patch alone).
+ + if (VIR_ALLOC_N(shargv, len + 1) < 0) + goto error; + for (i = 0, pp = p->list; pp; i++, pp = pp->next) { + if (VIR_STRDUP(shargv[i], pp->str) < 0) + goto error; + } + shargv[len] = NULL; + } + return shargv; +error: + virStringFreeList(shargv); + return NULL; +} + +int +main(int argc, char **argv) {
main(int argc, char **argv) {
+ virConfPtr conf = NULL; + const char *login_shell_path = SYSCONFDIR "/libvirt/virt-login-shell.conf"; + pid_t cpid; + int ret = EXIT_FAILURE; + int status; + int status2; + uid_t uid = getuid(); + gid_t gid = getgid();
Maybe I'm asking a stupid question, but why getuid() instead of geteuid()? Ditto for getegid().
+ char *name; + char **shargv = NULL; + virSecurityModelPtr secmodel = NULL; + virSecurityLabelPtr seclabel = NULL; + virDomainPtr dom = NULL; + virConnectPtr conn = NULL; + char *homedir = NULL; + if (!setlocale(LC_ALL, "")) { + perror("setlocale"); + /* failure to setup locale is not fatal */ + } + if (!bindtextdomain(PACKAGE, LOCALEDIR)) { + perror("bindtextdomain"); + return ret; + } + if (!textdomain(PACKAGE)) { + perror("textdomain"); + return ret; + } + + if (uid == 0) { + errno = EPERM; + fprintf(stderr, _("%s must be run by non root users: %m\n"), argv[0]);
%m is bad.
+ goto cleanup; + }
By placing this here, we give this failure message even for innocent usage like 'virt-login-shell --help' done as root. Would it make more sense to defer user enforcement until after option parsing, so that even root can at least probe the version of this software instead of being blindly rejected?
+ + if (argc > 1) { + errno = EINVAL; + fprintf(stderr, _("%s takes no options: %m\n"), argv[0]);
%m is bad.
+ goto cleanup; + }
Evil. All good programs should support --help and --program at a minimum - especially setuid programs in order to explain why they are setuid.
+ + name = virGetUserName(uid); + if (!name) { + fprintf(stderr, _("Failed to get username for %d: %m\n"), uid);
%m - and back to my question about why you aren't initializing the existing virError framework to use that to print to stderr instead of repeating it yourself. Remember, virGetUserName() does NOT guarantee a sane errno value on failure.
+ goto cleanup; + } + homedir = virGetUserDirectoryByUID(uid); + if (!homedir) { + fprintf(stderr, _("Can't read %s home directory: %m\n"), name);
%m, and virGetUserDirectoryByUID() does NOT guarantee a sane errno value on failure.
+ goto cleanup; + } + + if (!(conf = virConfReadFile(login_shell_path, 0))) { + fprintf(stderr, _("Failed to read %s: %m\n"), login_shell_path);
%m
+ goto cleanup; + } + if (virLoginShellAllowedUser(conf, uid, gid, name) < 0) + goto cleanup; + + if (!(shargv = virLoginShellGetShellArgv(conf))) + goto cleanup; + + conn = virConnectOpen("lxc:///");
This hard-codes us to LXC. We should allow a -c option, similar to other tools in the virt-stack, to let the user specify an alternate connection name. virConnectOpen(NULL) seems more future-proof, in case we ever change our default container hypervisor away from lxc:/// over to some other URI.
+ if (!conn) { + fprintf(stderr, _("Unable to connect to lxc:///: %m\n"));
%m
+ goto cleanup; + } + + dom = virDomainLookupByName(conn, name); + if (!dom) { + fprintf(stderr, _("Container %s does not exist: %m\n"), name);
%m
+ goto cleanup; + } + + if (! virDomainIsActive(dom) && virDomainCreate(dom)) {
Style nit: no space after '!'
+ virErrorPtr last_error; + last_error = virGetLastError(); + if (last_error->code != VIR_ERR_OPERATION_INVALID) { + fprintf(stderr,_("Can't create %s container: %s\n"), name, virGetLastErrorMessage());
Long line; wrap to stay within 80 columns.
+ goto cleanup; + } + } + + if ((nfdlist = virDomainLxcOpenNamespace(dom, &fdlist, 0)) < 0) {
Ah, we ARE hard-coding ourselves to lxc, by relying on an API that can only be successful when talking to lxc.
+ fprintf(stderr,_("Can't open %s namespace: %m\n"), name);
%m
+ goto cleanup; + } + + if (VIR_ALLOC(secmodel) < 0) + goto cleanup; + if (VIR_ALLOC(seclabel) < 0) + goto cleanup; + if (virNodeGetSecurityModel(conn, secmodel) < 0) + goto cleanup; + if (virDomainGetSecurityLabel(dom, seclabel) < 0) + goto cleanup; + + if (virFork(&cpid) < 0) + goto cleanup; + + if (cpid == 0) { + gid_t *groups = NULL; + int ngroups; + pid_t ccpid; + + /* Fork once because we don't want to affect + * virt-login-shell's namespace itself + */ + if (virSetUIDGID(0, 0, 0, 0) < 0) {
virSetUIDGID(0, 0, NULL, 0) - I don't like the use of '0' when NULL is intended.
+ fprintf(stderr, _("Unable to setresuid: %m\n"));
%m - and you are neither guaranteed that errno is reliable on failure, nor that the failure of virSetUIDGID was caused by setresuid.
+ return EXIT_FAILURE; + } + + if (virDomainLxcEnterSecurityLabel(secmodel, + seclabel, + NULL, + 0) < 0) + return EXIT_FAILURE;
What, no error message on why you exited? Again, I think you need to wire up virError handling to report to stderr by default.
+ + if (nfdlist > 0) { + if (virDomainLxcEnterNamespace(dom, + nfdlist, + fdlist, + NULL, + NULL, + 0) < 0) + return EXIT_FAILURE; + } + + if ((ngroups = virGetGroupList(uid, gid, &groups)) < 0) + return EXIT_FAILURE; + + ret = virSetUIDGIDWithCaps(uid, gid, groups, ngroups, 0, 0);
Since you aren't setting caps, why not use the simpler virSetUIDGID()?
+ VIR_FREE(groups); + if (ret < 0) + return EXIT_FAILURE; + + if (virFork(&ccpid) < 0) + return EXIT_FAILURE; + + if (ccpid == 0) { + if (chdir(homedir) < 0) { + fprintf(stderr, _("Unable chdir(%s): %m\n"), homedir);
%m
+ return EXIT_FAILURE; + } + if (execv(shargv[0], (char *const*) shargv) < 0) { + fprintf(stderr, _("Unable exec shell %s: %m\n"), shargv[0]);
%m
+ return EXIT_FAILURE;
Generally, you want to exit with status 126 or 127 after execv() failure, not 1; although I'm not sure it matters much here.
+ } + } + return virProcessWait(ccpid, &status2); + } + ret = virProcessWait(cpid, &status); + +cleanup: + virConfFree(conf); + virLoginShellFini(conn, dom); + virStringFreeList(shargv); + VIR_FREE(name); + VIR_FREE(homedir); + VIR_FREE(seclabel); + VIR_FREE(secmodel); + return ret; +} diff --git a/tools/virt-login-shell.conf b/tools/virt-login-shell.conf new file mode 100755 index 0000000..107424a --- /dev/null +++ b/tools/virt-login-shell.conf @@ -0,0 +1,24 @@ +# Master configuration file for the virt-login-shell program. +# All settings described here are optional - if omitted, sensible +# defaults are used. + +# By default, virt-login-shell will connect you to a container running +# with the /bin/sh program. Modify the shell variable if you want your +# users to run a different shell or a setup containe when joining a
s/containe/container/
+# container. Shell commands must be a list of commands/options separated by +# comma and delimited by square brackets. Defaults to: /bin/sh --login.
s/--login/-l/
+# Modify and uncomment the following to modify the login shell. +# shell = [ "/bin/sh", "--login" ]
s/--login/-l/
+ +# allowed_users specifies the user name of all users that are allowed to execute +# virt-login-shell. You can specify the users as a comma separated list of usernames. +# The variable support glob syntaxt, if you specify no names
s/syntaxt/syntax/
+# (default) then no one except root is allowed to use this executable.
Umm, but you just coded it so that the executable always fails when run as root.
+# to allow fred and joe only +# allowed_users = ["fred", "joe"] +# To allow all users within a specific group prefix the group name with %, etc +# allowed_users = ["%engineers"] +# to allow all users specify the following +# allowed_users = [ "*" ] +# disallow all users +# allowed_users = [] diff --git a/tools/virt-login-shell.pod b/tools/virt-login-shell.pod new file mode 100644 index 0000000..0cd35cf --- /dev/null +++ b/tools/virt-login-shell.pod @@ -0,0 +1,62 @@ +=head1 NAME + +virt-login-shell - tool to execute a shell within a container matching the users name + +=head1 SYNOPSIS + +B<virt-login-shell> + +=head1 DESCRIPTION + +The B<virt-login-shell> program is setuid shell that is used to join +an LXC container that matches the users name. If the container is not +running virt-login-shell will attempt to start the container. +virt-sandbox-shell is not allowed to be run by root. Normal users will get +added to a container that matches their username, if it exists. And they are
s/exists. And they are/exists, and as/
+configured in /etc/libvirt/virt-login-shell.conf. + +The basic structure of most virt-login-shell usage is: + + virt-login-shell + +=head1 CONFIG + +By default, virt-login-shell will execute the /bin/sh program for the user. +You can modify this behaviour by defining the shell variable in /etc/libvirt/virt-login-shell.conf. + +eg. shell = [ "/bin/ksh", "--login"] + +By default no users are allowed to user virt-login-shell, if you want to allow +certain users to use virt-login-shell, you need to modify the allowed_users variable in /etc/libvirt/virt-login-shell.conf.
Long line; wrap to keep in 80 columns.
+ +eg. allowed_users = [ "tom", "dick", "harry" ] + +=head1 BUGS + +Report any bugs discovered to the libvirt community via the mailing +list C<http://libvirt.org/contact.html> or bug tracker C<http://libvirt.org/bugs.html>. +Alternatively report bugs to your software distributor / vendor. + +=head1 AUTHORS + + Please refer to the AUTHORS file distributed with libvirt. + + Daniel Walsh <dwalsh at redhat dot com> + +=head1 COPYRIGHT + +Copyright (C) 2013 Red Hat, Inc., and the authors listed in the +libvirt AUTHORS file. + +=head1 LICENSE + +virt-login-shell is distributed under the terms of the GNU LGPL v2+. +This is free software; see the source for copying conditions. There +is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR +PURPOSE + +=head1 SEE ALSO + +L<virsh(1)>, L<http://www.libvirt.org/> + +=cut
-- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 I can't seem to get the error reporting to turn on, what am I doing wrong., if (virInitialize() < 0) { fprintf(stderr, _("Failed to initialize libvirt")); return EXIT_FAILURE; } if (virErrorInitialize() < 0) { fprintf(stderr, _("Failed to initialize libvirt Error Handling")); return EXIT_FAILURE; } virSetErrorFunc(NULL, NULL); virReportSystemError(EINVAL, "%s", _("Test")); And I get no output, I thought I would get error on stderr? -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.13 (GNU/Linux) Comment: Using GnuPG with Thunderbird - http://www.enigmail.net/ iEYEARECAAYFAlHyX7cACgkQrlYvE4MpobNrbQCgmR5hp4Lz9pgCv93V2Fb6r0ec VZsAn13t/fiFqoOEmb6PIb5xa3Gzbr9o =fhdb -----END PGP SIGNATURE-----

On Fri, Jul 26, 2013 at 07:38:31AM -0400, Daniel J Walsh wrote:
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
I can't seem to get the error reporting to turn on, what am I doing wrong.,
if (virInitialize() < 0) { fprintf(stderr, _("Failed to initialize libvirt")); return EXIT_FAILURE; }
if (virErrorInitialize() < 0) { fprintf(stderr, _("Failed to initialize libvirt Error Handling")); return EXIT_FAILURE; }
virSetErrorFunc(NULL, NULL);
virReportSystemError(EINVAL, "%s", _("Test"));
And I get no output, I thought I would get error on stderr?
You would, except that you just turned off printing to stderr by calling virSetErrorFunc in that way. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On 07/26/2013 07:40 AM, Daniel P. Berrange wrote:
On Fri, Jul 26, 2013 at 07:38:31AM -0400, Daniel J Walsh wrote:
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
I can't seem to get the error reporting to turn on, what am I doing wrong.,
if (virInitialize() < 0) { fprintf(stderr, _("Failed to initialize libvirt")); return EXIT_FAILURE; }
if (virErrorInitialize() < 0) { fprintf(stderr, _("Failed to initialize libvirt Error Handling")); return EXIT_FAILURE; }
virSetErrorFunc(NULL, NULL);
virReportSystemError(EINVAL, "%s", _("Test"));
And I get no output, I thought I would get error on stderr?
You would, except that you just turned off printing to stderr by calling virSetErrorFunc in that way.
Daniel
Am I misreading this? * virSetErrorFunc: * @userData: pointer to the user data provided in the handler callback * @handler: the function to get called in case of error or NULL * * Set a library global error handling function, if @handler is NULL, * it will reset to default printing on stderr. The error raised there * are those for which no handler at the connection level could caught. */ Looks like setting handler to Null reset default printing on stderr? But I am getting no output whether or not I set this. I am attaching a hacked virt-login-shell.c which gives me no output. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.13 (GNU/Linux) Comment: Using GnuPG with Thunderbird - http://www.enigmail.net/ iEYEARECAAYFAlHyozwACgkQrlYvE4MpobMjAACePralBci9M6O0wshnO1+bXXVC a4EAn1/cfC8ng8XlLTO9DpiFetmDr9wv =+h5o -----END PGP SIGNATURE-----

On Fri, Jul 26, 2013 at 12:26:36PM -0400, Daniel J Walsh wrote:
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
On 07/26/2013 07:40 AM, Daniel P. Berrange wrote:
On Fri, Jul 26, 2013 at 07:38:31AM -0400, Daniel J Walsh wrote:
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
I can't seem to get the error reporting to turn on, what am I doing wrong.,
if (virInitialize() < 0) { fprintf(stderr, _("Failed to initialize libvirt")); return EXIT_FAILURE; }
if (virErrorInitialize() < 0) { fprintf(stderr, _("Failed to initialize libvirt Error Handling")); return EXIT_FAILURE; }
virSetErrorFunc(NULL, NULL);
virReportSystemError(EINVAL, "%s", _("Test"));
And I get no output, I thought I would get error on stderr?
You would, except that you just turned off printing to stderr by calling virSetErrorFunc in that way.
Daniel
Am I misreading this? * virSetErrorFunc: * @userData: pointer to the user data provided in the handler callback * @handler: the function to get called in case of error or NULL * * Set a library global error handling function, if @handler is NULL, * it will reset to default printing on stderr. The error raised there * are those for which no handler at the connection level could caught. */ Looks like setting handler to Null reset default printing on stderr? But I am getting no output whether or not I set this.
Oh, whoops, I'm back to front. The actual reason you're having trouble is that you're using virReportError from outside the context of any public API calls. virReportError does not actually print any errors, it just records them. It requires a seprate call to virDispatchError to trigger the print to stderr. This is usually taken care of by the public API methods. You'll have to take care of it yourself though. If you have a centralized error handling return point eg ...success... return 0; error: return -1; } Then you'd put a virDispatchError call just after the 'error:' label Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|
participants (4)
-
Daniel J Walsh
-
Daniel P. Berrange
-
dwalsh@redhat.com
-
Eric Blake