Viewing: liblustreapi_kernelconn.c

// SPDX-License-Identifier: LGPL-2.1+
/*
 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright (c) 2012, 2013, Intel Corporation.
 */
/*
 * This file is part of Lustre, http://www.lustre.org/
 *
 * Author: Nathan Rutman <nathan.rutman@sun.com>
 *
 * Kernel <-> userspace communication routines.
 * Using pipes for all arches.
 */

#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <poll.h>

#include <lustre/lustreapi.h>

#include "lustreapi_internal.h"

/** Start the userspace side of a KUC pipe.
 * @param link Private descriptor for pipe/socket.
 * @param groups KUC broadcast group to listen to
 *          (can be null for unicast to this pid)
 * @param rfd_flags flags for read side of pipe (e.g. O_NONBLOCK)
 */
int libcfs_ukuc_start(struct lustre_kernelcomm *link,
		      int group, int rfd_flags)
{
	int pfd[2];
	int rc;

	link->lk_rfd = link->lk_wfd = LK_NOFD;

	if (pipe(pfd) < 0)
		return -errno;

	if (fcntl(pfd[0], F_SETFL, rfd_flags) < 0) {
		rc = -errno;
		close(pfd[0]);
		close(pfd[1]);
		return rc;
	}

	memset(link, 0, sizeof(*link));
	link->lk_rfd = pfd[0];
	link->lk_wfd = pfd[1];
	link->lk_group = group;
	link->lk_uid = getpid();
	return 0;
}

int libcfs_ukuc_stop(struct lustre_kernelcomm *link)
{
	int rc;

	if (link->lk_wfd != LK_NOFD)
		close(link->lk_wfd);
	rc = close(link->lk_rfd);
	link->lk_rfd = link->lk_wfd = LK_NOFD;
	return rc;
}

/** Returns the file descriptor for the read side of the pipe,
 *  to be used with poll/select.
 * @param link Private descriptor for pipe/socket.
 */
int libcfs_ukuc_get_rfd(struct lustre_kernelcomm *link)
{
	return link->lk_rfd;
}

#define lhsz sizeof(*kuch)

/* Read exactly @count bytes from the pipe into @buf, looping over short
 * reads. Returns 0 on success, a negative errno on failure.
 */
static int ukuc_full_read(int fd, char *buf, size_t count)
{
	size_t total = 0;

	while (total < count) {
		ssize_t rc = read(fd, buf + total, count - total);

		if (rc > 0) {
			total += rc;
		} else if (rc == 0) {
			return -EPIPE;
		} else if (errno == EINTR) {
			continue;
		} else if (errno == EAGAIN || errno == EWOULDBLOCK) {
			struct pollfd pfd = { .fd = fd, .events = POLLIN };

			/* No bytes yet: propagate EAGAIN so that O_NONBLOCK
			 * callers multiplexing with poll(2) keep working.
			 */
			if (total == 0)
				return -EAGAIN;

			/* Mid-message: we have already consumed part of a
			 * framed message and must finish it to stay in sync.
			 * Wait for the remainder.
			 */
			if (poll(&pfd, 1, -1) < 0 && errno != EINTR)
				return -errno;
		} else {
			return -errno;
		}
	}
	return 0;
}

/** Read a message from the link.
 * Reads one complete message into @a buf (caller-allocated).
 *
 * @param link Private descriptor for pipe/socket.
 * @param buf Buffer to read into, must include size for kuc_hdr
 * @param maxsize Maximum message size allowed
 * @param transport Only listen to messages on this transport
 *      (and the generic transport)
 */
int libcfs_ukuc_msg_get(struct lustre_kernelcomm *link, char *buf, int maxsize,
			int transport)
{
	struct kuc_hdr *kuch;
	int rc = 0;

	if (buf == NULL || maxsize < 0 || maxsize < lhsz)
		return -EINVAL;

	memset(buf, 0, maxsize);

	while (1) {
		/* Read header first to get message size */
		rc = ukuc_full_read(link->lk_rfd, buf, lhsz);
		if (rc < 0)
			break;

		kuch = (struct kuc_hdr *)buf;

		if (kuch->kuc_magic != KUC_MAGIC) {
			llapi_err_noerrno(LLAPI_MSG_ERROR,
					  "bad message magic %x != %x\n",
					  kuch->kuc_magic, KUC_MAGIC);
			rc = -EPROTO;
			break;
		}

		if (kuch->kuc_msglen < lhsz) {
			rc = -EPROTO;
			break;
		}

		if (kuch->kuc_msglen > maxsize) {
			rc = -EMSGSIZE;
			break;
		}

		/* Read payload */
		rc = ukuc_full_read(link->lk_rfd, buf + lhsz, kuch->kuc_msglen - lhsz);
		if (rc < 0)
			break;

		if (kuch->kuc_transport == transport ||
		    kuch->kuc_transport == KUC_TRANSPORT_GENERIC) {
			return 0;
		}
		/* Drop messages for other transports */
	}
	return rc;
}