Description: eal: provide option to set vhost_user socket owner/permissions

The API doesn't hold a way to specify a owner/permission set for vhost_user
created sockets.

Projects consuming DPDK started to do 'their own workarounds' like openvswitch
https://patchwork.ozlabs.org/patch/559043/
https://patchwork.ozlabs.org/patch/559045/
But for this specific example they are blocked/stalled behind a bigger
rework (https://patchwork.ozlabs.org/patch/604898/).

We need something now for existing code linking against DPDK. That implies to
avoid changing API/ABI. So I created a DPDK EAL commandline option based ideas
in the former patches.

Fixes LP: #1546565

*Update*
 - with the split libs it now nees to be listed in
   lib/librte_eal/linuxapp/eal/rte_eal_version.map to work on link steps
 - please note that upstream gravitates towards not extending but creating a
   new the API in DPDK as long term solution (will take a while)
 - also as listed before most affected projects seem to create their own
   workaround.
 So over time we have to check when we can drop it at the price of a config
 transition - likely OVS 2.6 won't need it anymore.

*Update*
 - the handling and lifecycle of this changed in Openvswitch 2.7 so we can no
   more use internal_config.
 - Also the upstreaming was aborted as that now clearly goes towards client
   mode vhost sockets for this (and other issues).
 - But until that is fully working we have to carry the workaround.
 - Updated to work with Openvswitch 2.7 (and backward compatible to 2.6)

Forwarded: yes
Author: Christian Ehrhardt <christian.ehrhardt@canonical.com>
Last-Update: 2017-05-23

--- a/doc/guides/testpmd_app_ug/run_app.rst
+++ b/doc/guides/testpmd_app_ug/run_app.rst
@@ -156,6 +156,25 @@
 
     Use malloc instead of hugetlbfs.
 
+*   ``--vhost-owner``
+
+     When creating vhost_user sockets change owner and group to the specified value.
+     This can be given as ``user:group``, but also only ``user`` or ``:group`` are supported.
+
+    Examples::
+
+       --vhost-owner 'libvirt-qemu:kvm'
+       --vhost-owner 'libvirt-qemu'
+       --vhost-owner ':kvm'
+
+*   ``--vhost-perm``
+
+    When creating vhost_user sockets set them up with these permissions.
+
+    For example::
+
+       --vhost-perm '0664'
+
 
 Testpmd Command-line Options
 ----------------------------
--- a/lib/librte_eal/common/eal_common_options.c
+++ b/lib/librte_eal/common/eal_common_options.c
@@ -95,6 +95,8 @@
 	{OPT_VFIO_INTR,         1, NULL, OPT_VFIO_INTR_NUM        },
 	{OPT_VMWARE_TSC_MAP,    0, NULL, OPT_VMWARE_TSC_MAP_NUM   },
 	{OPT_XEN_DOM0,          0, NULL, OPT_XEN_DOM0_NUM         },
+	{OPT_VHOST_OWNER,       1, NULL, OPT_VHOST_OWNER_NUM      },
+	{OPT_VHOST_PERM,        1, NULL, OPT_VHOST_PERM_NUM       },
 	{0,                     0, NULL, 0                        }
 };
 
--- a/lib/librte_eal/common/eal_options.h
+++ b/lib/librte_eal/common/eal_options.h
@@ -83,6 +83,10 @@
 	OPT_VMWARE_TSC_MAP_NUM,
 #define OPT_XEN_DOM0          "xen-dom0"
 	OPT_XEN_DOM0_NUM,
+#define OPT_VHOST_OWNER       "vhost-owner"
+	OPT_VHOST_OWNER_NUM,
+#define OPT_VHOST_PERM        "vhost-perm"
+	OPT_VHOST_PERM_NUM,
 	OPT_LONG_MAX_NUM
 };
 
--- a/lib/librte_eal/common/include/rte_eal.h
+++ b/lib/librte_eal/common/include/rte_eal.h
@@ -256,6 +256,11 @@
 #define RTE_INIT(func) \
 static void __attribute__((constructor, used)) func(void)
 
+/**
+ * Set owner/permissions on sockets if requested on EAL commandline
+ */
+void rte_eal_set_socket_permissions(const char *);
+
 #ifdef __cplusplus
 }
 #endif
--- a/lib/librte_eal/linuxapp/eal/eal.c
+++ b/lib/librte_eal/linuxapp/eal/eal.c
@@ -53,6 +53,9 @@
 #if defined(RTE_ARCH_X86)
 #include <sys/io.h>
 #endif
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
 
 #include <rte_common.h>
 #include <rte_debug.h>
@@ -117,6 +120,12 @@
 /* internal configuration */
 struct internal_config internal_config;
 
+/* workaround to be able to create the sockets under a certain set of
+ * owner/permissions as specified to EAL until solved upstream */
+static uid_t debian_vhost_sock_uid = (uid_t)-1;
+static gid_t debian_vhost_sock_gid = (gid_t)-1;
+static mode_t debian_vhost_sock_perm = 0;
+
 /* used by rte_rdtsc() */
 int rte_cycles_vmware_tsc_map;
 
@@ -354,6 +363,8 @@
 	       "  --"OPT_CREATE_UIO_DEV"    Create /dev/uioX (usually done by hotplug)\n"
 	       "  --"OPT_VFIO_INTR"         Interrupt mode for VFIO (legacy|msi|msix)\n"
 	       "  --"OPT_XEN_DOM0"          Support running on Xen dom0 without hugetlbfs\n"
+	       "  --"OPT_VHOST_OWNER"       Create vhost-user sockets with this owner:group\n"
+	       "  --"OPT_VHOST_PERM"        Create vhost-user sockets with these permissions\n"
 	       "\n");
 	/* Allow the application to print its usage message too if hook is set */
 	if ( rte_application_usage_hook ) {
@@ -515,6 +526,121 @@
 	optarg = old_optarg;
 }
 
+/* Try to double the size of '*buf', return true
+ * if successful, and '*sizep' will be updated with
+ * the new size. Otherwise, return false.  */
+static int
+enlarge_buffer(char **buf, size_t *sizep)
+{
+    size_t newsize = *sizep * 2;
+
+    if (newsize > *sizep) {
+        *buf = realloc(*buf, newsize);
+        *sizep = newsize;
+        return 1;
+    }
+
+    return 0;
+}
+
+static int
+get_owners_from_str(const char *user_spec, uid_t *uid, gid_t *gid)
+{
+	size_t bufsize = 4096;
+
+	char *pos = strchr(user_spec, ':');
+	user_spec += strspn(user_spec, " \t\r\n");
+	size_t len = pos ? (size_t)(pos - user_spec) : strlen(user_spec);
+
+	char *buf = NULL;
+	struct passwd pwd, *res;
+	int e;
+
+	buf = malloc(bufsize);
+	char *user_search = NULL;
+	if (len) {
+		user_search = malloc(len + 1);
+		memcpy(user_search, user_spec, len);
+		user_search[len] = '\0';
+		while ((e = getpwnam_r(user_search, &pwd, buf, bufsize, &res)) == ERANGE) {
+			if (!enlarge_buffer(&buf, &bufsize)) {
+				break;
+			}
+		}
+
+		if (e != 0) {
+			RTE_LOG(ERR, EAL,"Failed to retrive user %s's uid (%s), aborting.",
+				user_search, strerror(e));
+			goto release;
+		}
+		if (res == NULL) {
+			RTE_LOG(ERR, EAL,"user %s not found,  aborting.",
+				user_search);
+			e = -1;
+			goto release;
+		}
+	} else {
+		/* User name is not specified, use current user.  */
+		while ((e = getpwuid_r(getuid(), &pwd, buf, bufsize, &res)) == ERANGE) {
+			if (!enlarge_buffer(&buf, &bufsize)) {
+				break;
+			}
+		}
+
+		if (e != 0) {
+			RTE_LOG(ERR, EAL,"Failed to retrive current user's uid "
+				"(%s), aborting.", strerror(e));
+			goto release;
+		}
+		user_search = strdup(pwd.pw_name);
+	}
+
+	if (uid)
+		*uid = pwd.pw_uid;
+
+	free(buf);
+	buf = NULL;
+
+	if (pos) {
+		char *grpstr = pos + 1;
+		grpstr += strspn(grpstr, " \t\r\n");
+
+		if (*grpstr) {
+			struct group grp, *res;
+
+			bufsize = 4096;
+			buf = malloc(bufsize);
+			while ((e = getgrnam_r(grpstr, &grp, buf, bufsize, &res))
+					 == ERANGE) {
+				if (!enlarge_buffer(&buf, &bufsize)) {
+					break;
+				}
+			}
+
+			if (e) {
+				RTE_LOG(ERR, EAL,"Failed to get group entry for %s, "
+					"(%s), aborting.", grpstr,
+					strerror(e));
+				goto release;
+			}
+			if (res == NULL) {
+				RTE_LOG(ERR, EAL,"Group %s not found, aborting.",
+					grpstr);
+				e = -1;
+				goto release;
+			}
+
+			if (gid)
+				*gid = grp.gr_gid;
+		}
+	}
+
+ release:
+	free(buf);
+	free(user_search);
+	return e;
+}
+
 /* Parse the argument given in the command line of the application */
 static int
 eal_parse_args(int argc, char **argv)
@@ -611,6 +737,26 @@
 			internal_config.create_uio_dev = 1;
 			break;
 
+		case OPT_VHOST_OWNER_NUM:
+			if (get_owners_from_str(optarg, &debian_vhost_sock_uid,
+									&debian_vhost_sock_gid)) {
+				RTE_LOG(ERR, EAL,"vhost-user socket unable to get"
+					" specified user/group: %s\n", optarg);
+				debian_vhost_sock_uid = (uid_t)-1;
+				debian_vhost_sock_gid = (gid_t)-1;
+			}
+			else {
+				RTE_LOG(INFO, EAL,"socket owner specified as %s (%d:%d)\n",
+					optarg, debian_vhost_sock_uid, debian_vhost_sock_gid);
+			}
+			break;
+
+		case OPT_VHOST_PERM_NUM:
+			debian_vhost_sock_perm = (mode_t)strtoul(optarg, NULL, 0);
+			RTE_LOG(INFO, EAL,"socket perm specified as '%#o' from '%s'\n",
+					debian_vhost_sock_perm, optarg);
+			break;
+
 		default:
 			if (opt < OPT_LONG_MIN_NUM && isprint(opt)) {
 				RTE_LOG(ERR, EAL, "Option %c is not supported "
@@ -943,3 +1089,47 @@
 	/* Module has been found */
 	return 1;
 }
+
+static void
+vhost_set_permissions(const char *vhost_sock_location)
+{
+	int err = chmod(vhost_sock_location, debian_vhost_sock_perm);
+	if (err) {
+		RTE_LOG(ERR, EAL,"vhost-user socket cannot set"
+			" permissions to %#o (%s).\n",
+			debian_vhost_sock_perm, strerror(err));
+		return;
+	}
+	RTE_LOG(INFO, EAL,"Socket %s changed permissions"
+			" to %#o\n", vhost_sock_location,
+			debian_vhost_sock_perm);
+}
+
+static void
+vhost_set_ownership(const char *vhost_sock_location)
+{
+	int err = chown(vhost_sock_location, debian_vhost_sock_uid, debian_vhost_sock_gid);
+	if (err) {
+		RTE_LOG(ERR, EAL,"vhost-user socket unable to set"
+			" ownership to %d:%d (%s).\n",
+			debian_vhost_sock_uid, debian_vhost_sock_gid,
+			strerror(err));
+		return;
+	}
+
+	RTE_LOG(INFO, EAL,"Socket %s changed ownership"
+			" to %d:%d.\n", vhost_sock_location,
+			debian_vhost_sock_uid, debian_vhost_sock_gid);
+}
+
+void
+rte_eal_set_socket_permissions(const char *path)
+{
+	if (debian_vhost_sock_perm != 0) {
+		vhost_set_permissions(path);
+	}
+
+	if (debian_vhost_sock_uid != (uid_t)-1 || debian_vhost_sock_gid != (gid_t)-1) {
+		vhost_set_ownership(path);
+	}
+}
--- a/lib/librte_eal/linuxapp/eal/rte_eal_version.map
+++ b/lib/librte_eal/linuxapp/eal/rte_eal_version.map
@@ -139,6 +139,7 @@
 	rte_keepalive_register_core;
 	rte_xen_dom0_supported;
 	rte_xen_mem_phy2mch;
+	rte_eal_set_socket_permissions;
 
 } DPDK_2.1;
 
--- a/lib/librte_vhost/socket.c
+++ b/lib/librte_vhost/socket.c
@@ -78,6 +78,8 @@
 	pthread_mutex_t mutex;
 };
 
+#include <rte_eal.h>
+
 #define MAX_VIRTIO_BACKLOG 128
 
 static void vhost_user_server_new_connection(int fd, void *data, int *remove);
@@ -519,6 +521,7 @@
 		vsocket->is_server = true;
 		ret = vhost_user_create_server(vsocket);
 	}
+    rte_eal_set_socket_permissions(path);
 	if (ret < 0) {
 		free(vsocket->path);
 		free(vsocket);
