mbuf.c   [plain text]


/*
 * Copyright (c) 2008 - 2010 Apple Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netsmb/upi_mbuf.h>
#include <netsmb/smb_lib.h>


/* mbuf flags */
#define M_EXT           0x0001  /* has associated external storage */
#define M_PKTHDR        0x0002  /* start of record */
#define M_EOR           0x0004  /* end of record */


struct smb_mbuf {
	uint32_t			m_type;
	uint32_t			m_flags;
	size_t				m_maxlen;
	size_t				m_len;
	size_t				m_pkthdr_len;
	char				*m_data;
	struct smb_mbuf		*m_next;
	void (*m_extfree)(caddr_t , size_t, caddr_t);
	caddr_t				m_extarg;
};

/* 
 * mbuf_free
 *
 * Frees a single mbuf. Not commonly used outside of the file because it
 * doesn't touch the rest of the mbufs on the chain.
 * params: 
 *		mbuf - The mbuf to free.
 * result:
 *		The next mbuf in the chain.
 */
mbuf_t mbuf_free(mbuf_t mbuf)
{
	mbuf_t next = NULL;
	
	if (mbuf == NULL)
		return next; /* nothing to free */
	
	next = mbuf->m_next;
	if (mbuf->m_type == MBUF_TYPE_FREE) {
		smb_log_info("%s: Double FREE", ASL_LEVEL_DEBUG, __FUNCTION__);
	}
	if (mbuf->m_flags & M_EXT) {
		if (mbuf->m_extfree)
			mbuf->m_extfree(mbuf->m_extarg, mbuf->m_maxlen, (caddr_t)mbuf->m_data);
	} else if (mbuf->m_data) {
		free(mbuf->m_data);
	}
	mbuf->m_next  = NULL;
	mbuf->m_type = MBUF_TYPE_FREE;
	mbuf->m_data = NULL;
	free(mbuf);
	return next;
}

/* 
 * smb_mbuf_get
 *
 * Internal routine that allocates all userland mbufs. Only creates
 * data storage if maxlen is not zero.
 * params: 
 *		how		- How to create the mbuf, always MBUF_WAITOK in userland.
 *		type	- Type of mbuf to create, always MBUF_TYPE_DATA in userland.
 *		mbuf	- Return location for the mbuf we created.
 *		maxlen	- The data size that should be allocated for this mbuf
 * result:
 *		Error	- Either zero or the appropriate errno
 */
static 
int smb_mbuf_get(uint32_t how, uint32_t type, mbuf_t *mbuf, size_t maxlen)
{
	struct smb_mbuf *m;
	
	if ((type != MBUF_TYPE_DATA) || (how != MBUF_WAITOK))
		return (EINVAL);
	
	m = malloc(sizeof(struct smb_mbuf));
	if (m == NULL)
		return ENOMEM;
	
	bzero(m, sizeof(struct smb_mbuf));
	m->m_type = type;
	if (maxlen) {
		m->m_data = malloc(maxlen);
		if (m->m_data == NULL) {
			(void)mbuf_free(m);
			return ENOMEM;
		}
		m->m_maxlen = maxlen;
	}
	*mbuf = m;
	return 0;
}

/*
 * mbuf_freem
 *
 * Frees a chain of mbufs link through mnext. 
 * params: 
 *		mbuf - The first mbuf in the chain to free.
 */
void mbuf_freem(mbuf_t mbuf)
{
	struct smb_mbuf *m;
	
	while (mbuf) {
		m = mbuf_free(mbuf);
		mbuf = m;
	}
}

/*
 * mbuf_gethdr
 *
 * Allocates an mbuf without a cluster for external data. Sets a flag to 
 * indicate there is a packet header and initializes the packet header.
 * params: 
 *		how		- How to create the mbuf, always MBUF_WAITOK in userland.
 *		type	- Type of mbuf to create, always MBUF_TYPE_DATA in userland.
 *		mbuf	- Return location for the mbuf we created.
 * result:
 *		Error	- Either zero or the appropriate errno
 */
int mbuf_gethdr(uint32_t how, uint32_t type, mbuf_t *mbuf)
{
	int error = smb_mbuf_get(how, type, mbuf, getpagesize());
	if (error)
		return error;
	
	(*mbuf)->m_flags |= M_PKTHDR | M_EOR;
	return 0;
}

/*
 * mbuf_get
 *
 * Allocates an mbuf without a cluster for external data.
 * params: 
 *		how		- How to create the mbuf, always MBUF_WAITOK in userland.
 *		type	- Type of mbuf to create, always MBUF_TYPE_DATA in userland.
 *		mbuf	- Return location for the mbuf we created.
 * result:
 *		Error	- Either zero or the appropriate errno
 */
int mbuf_get(uint32_t how, uint32_t type, mbuf_t *mbuf)
{
	return smb_mbuf_get(how, type, mbuf, getpagesize());
}

/*
 * mbuf_getcluster
 *
 * Allocate a cluster of the requested size and attach it to an mbuf for use as 
 * external data. If mbuf points to a NULL mbuf_t, an mbuf will be allocated for 
 * you. If mbuf points to  a non-NULL mbuf_t, mbuf_getcluster may return a different
 * mbuf_t than the one you passed in.
 * params: 
 *		how		- How to create the mbuf, always MBUF_WAITOK in userland.
 *		type	- Type of mbuf to create, always MBUF_TYPE_DATA in userland.
 *		size	- The size of the cluster to be allocated. This routine allows
 *					any size, unlike the kernel kpi mbuf code.
 *		mbuf	- The mbuf the cluster will be attached to.
 * result:
 *		Error	- Either zero or the appropriate errno
 */
int mbuf_getcluster(uint32_t how, uint32_t type, size_t size, mbuf_t *mbuf)
{
	int error;
	/* We currently relocate the mbuf always */
	if (*mbuf) {
		mbuf_freem( *mbuf);
		*mbuf = NULL;
	}
	error = smb_mbuf_get(how, type, mbuf, size);
	if (!error && *mbuf)
		(*mbuf)->m_flags |= M_PKTHDR | M_EOR;
	return error;
}

/*
 * mbuf_attachcluster
 *
 * Attach an external buffer as a cluster for an mbuf.  If mbuf points to a NULL 
 * mbuf_t, an mbuf will be allocated for you.  If mbuf points to a non-NULL mbuf_t, 
 * the user-supplied mbuf will be used instead.  
 * params: 
 *		how		- How to create the mbuf, always MBUF_WAITOK in userland.
 *		type	- Type of mbuf to create, always MBUF_TYPE_DATA in userland.
 *		mbuf	- Pointer to the address of the mbuf; if NULL, an mbuf will be
 *					allocated, otherwise, it must point to a valid mbuf address.
 *		extbuf	- Address of the external buffer.
 *		extfree	- Free routine for the external buffer; the caller is required 
 *					to defined a routine that will be invoked when the mbuf is 
 *					freed. Userland code allows this to be null.
 *		extsize	- Size of the external buffer.
 *		extarg	- Private value that will be passed to the free routine when it 
 *					is called at the time the mbuf is freed.
 * result:
 *		Error	- Either zero or the appropriate errno
 */
int mbuf_attachcluster(uint32_t how, uint32_t type,
					   mbuf_t *mbuf, void * extbuf, 
					   void (*extfree)(caddr_t , size_t, caddr_t),
					   size_t extsize, caddr_t extarg)
{
	int error = 0;
	
	if ((extbuf == NULL) || (extsize == 0)) {
		error = EINVAL;
	} else if (*mbuf == NULL) {
		error = smb_mbuf_get(how, type, mbuf, 0);
	} else if ((*mbuf)->m_data) {
		free((*mbuf)->m_data);
		(*mbuf)->m_data = NULL;
	}
	if (error)
		return error;
	
	(*mbuf)->m_flags = M_EXT | M_PKTHDR | M_EOR;
	(*mbuf)->m_maxlen = extsize;
	(*mbuf)->m_data = extbuf;
	(*mbuf)->m_extfree = extfree;
	(*mbuf)->m_extarg = extarg;
	return 0;
}

/*
 * mbuf_len
 *
 * Gets the length of data in this mbuf.
 * params: 
 *		mbuf	- The mbuf.
 * result:
 *		size_t	- The  length of data in this mbuf.
 */
size_t mbuf_len(const mbuf_t mbuf)
{
	if (mbuf) {
		return mbuf->m_len;
	} else {
		return 0;
	}
}

/*
 * mbuf_maxlen
 *
 * Retrieves the maximum length of data that may be stored in this mbuf.  
 * params: 
 *		mbuf	- The mbuf.
 * result:
 *		size_t	- The maximum length of data for this mbuf.
 */
size_t mbuf_maxlen(const mbuf_t mbuf)
{
	if (mbuf) {
		return mbuf->m_maxlen;
	} else {
		return 0;
	}
}

/*
 * mbuf_setlen
 * 
 * Sets the length of data in this packet. Be careful to not set the length over 
 * the space available in the mbuf.
 * param:
 *		mbuf	- The mbuf.
 *		len		- The new length.
 */
void mbuf_setlen(mbuf_t mbuf, size_t len)
{
	if (mbuf) {
		mbuf->m_len = len;
	}
}

/*
 * mbuf_pkthdr_len
 *
 * Returns the length as reported by the packet header.
 * params: 
 *		mbuf	- The mbuf containing the packet header with the length to
 *					be changed.
 * result:
 *		size_t	- The length, in bytes, of the packet.
 */
size_t mbuf_pkthdr_len(const mbuf_t mbuf)
{
	return mbuf->m_pkthdr_len;
}

/*
 * mbuf_pkthdr_setlen
 *
 * Sets the length of the packet in the packet header.
 * params: 
 *		mbuf	- The mbuf containing the packet header.
 */
void mbuf_pkthdr_setlen(mbuf_t mbuf, size_t len)
{
	mbuf->m_pkthdr_len = len;
}

/*
 * mbuf_pkthdr_adjustlen
 *
 * Adjusts the length of the packet in the packet header.
 * params: 
 *		mbuf	- The mbuf containing the packet header.
 *		amount	- The number of bytes to adjust the packet header length field.
 */
void mbuf_pkthdr_adjustlen(mbuf_t mbuf, int amount)
{
	mbuf->m_pkthdr_len += amount;
}

/*
 * mbuf_next
 *
 * Returns the next mbuf in the chain.
 * params: 
 *		mbuf	- The mbuf
 * result:
 *		mbuf_t	- The next mbuf in the chain.
 */
mbuf_t mbuf_next(const mbuf_t mbuf)
{
	if (mbuf) {
		return mbuf->m_next;
	} else {
		return NULL;
	}
}

/*
 * mbuf_setnext
 *
 * Sets the next mbuf in the chain.
 * params: 
 *		mbuf	- The mbuf
 *		next	- The new next mbuf.
 * result:
 *		Error	- Either zero or the appropriate errno
 */
int mbuf_setnext(mbuf_t mbuf, mbuf_t next)
{
	if (!next || (next->m_type == MBUF_TYPE_FREE)) 
		return EINVAL;
	if (mbuf->m_flags & M_EOR) {
		mbuf->m_flags &= ~M_EOR;
		next->m_flags |=M_EOR;
	}
	mbuf->m_next = next;
	return 0;
}

/*
 * mbuf_data
 *
 * Returns a pointer to the start of data in this mbuf. There may be additional 
 * data on chained mbufs. The data you're looking for may not be virtually 
 * contiguous if it spans more than one mbuf.  In addition, data that is virtually 
 * contiguous might not be represented by physically contiguous pages; see
 * further comments in mbuf_data_to_physical.  Use mbuf_len to determine the length 
 * of data available in this mbuf. If a data structure you want to access stradles 
 * two mbufs in a chain, either use mbuf_pullup to get the data contiguous in one mbuf
 * or copy the pieces of data from each mbuf in to a contiguous buffer. Using 
 * mbuf_pullup has the advantage of not having to copy the data. On the other hand, 
 * if you don't make sure there is space in the mbuf, mbuf_pullup may fail and 
 * free the mbuf.
 * params: 
 *		mbuf	- The mbuf
 *		next	- The new next mbuf.
 * result:
 *		void *	- A pointer to the data in the mbuf.
 */
void *mbuf_data(const mbuf_t mbuf)
{
	if (mbuf) {
		return (void *)mbuf->m_data;
	} else {
		return NULL;
	}
}

/*
 * mbuf_trailingspace
 * 
 * Determines the space available in the mbuf following the current data.
 * params: 
 *		mbuf	- The mbuf
 * result:
 *		size_t	- The number of unused bytes following the current data.
 */
size_t mbuf_trailingspace(const mbuf_t mbuf)
{
	return mbuf->m_maxlen - mbuf->m_len;
}

/*
 * mbuf_copydata
 *
 * Copies data out of an mbuf in to a specified buffer. If the data is stored in 
 * a chain of mbufs, the data will be copied from each mbuf in the chain until 
 * length bytes have been copied.
 * params: 
 *		mbuf	- The mbuf chain to copy data out of.
 *		offset	- The offset in to the mbuf to start copying.
 *		length	- The number of bytes to copy.
 *		out_data - A pointer to the location where the data will be copied.
 * result:
 *		Error	- Either zero or the appropriate errno
 */
int mbuf_copydata(const mbuf_t mbuf, size_t offset, size_t length, void *out_data)
{
	/* Copied m_copydata, added error handling (don't just panic) */
	size_t count;
	mbuf_t  m = mbuf;
	
	while (offset > 0) {
		if (m == 0)
			return EINVAL;
		if (offset < (size_t)m->m_len)
			break;
		offset -= m->m_len;
		m = m->m_next;
	}
	while (length > 0) {
		if (m == 0)
			return EINVAL;
		count = m->m_len - offset > length ? length : m->m_len - offset;
		bcopy((caddr_t)mbuf_data(m) + offset, out_data, count);
		length -= count;
		out_data = ((char*)out_data) + count;
		offset = 0;
		m = m->m_next;
	}
	
	return 0;
}