1/*
2 * linux/ipc/msgutil.c
3 * Copyright (C) 1999, 2004 Manfred Spraul
4 *
5 * This file is released under GNU General Public Licence version 2 or
6 * (at your option) any later version.
7 *
8 * See the file COPYING for more details.
9 */
10
11#include <linux/spinlock.h>
12#include <linux/init.h>
13#include <linux/security.h>
14#include <linux/slab.h>
15#include <linux/ipc.h>
16#include <asm/uaccess.h>
17
18#include "util.h"
19
20struct msg_msgseg {
21	struct msg_msgseg* next;
22	/* the next part of the message follows immediately */
23};
24
25#define DATALEN_MSG	(PAGE_SIZE-sizeof(struct msg_msg))
26#define DATALEN_SEG	(PAGE_SIZE-sizeof(struct msg_msgseg))
27
28struct msg_msg *load_msg(const void __user *src, int len)
29{
30	struct msg_msg *msg;
31	struct msg_msgseg **pseg;
32	int err;
33	int alen;
34
35	alen = len;
36	if (alen > DATALEN_MSG)
37		alen = DATALEN_MSG;
38
39	msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL);
40	if (msg == NULL)
41		return ERR_PTR(-ENOMEM);
42
43	msg->next = NULL;
44	msg->security = NULL;
45
46	if (copy_from_user(msg + 1, src, alen)) {
47		err = -EFAULT;
48		goto out_err;
49	}
50
51	len -= alen;
52	src = ((char __user *)src) + alen;
53	pseg = &msg->next;
54	while (len > 0) {
55		struct msg_msgseg *seg;
56		alen = len;
57		if (alen > DATALEN_SEG)
58			alen = DATALEN_SEG;
59		seg = kmalloc(sizeof(*seg) + alen,
60						 GFP_KERNEL);
61		if (seg == NULL) {
62			err = -ENOMEM;
63			goto out_err;
64		}
65		*pseg = seg;
66		seg->next = NULL;
67		if (copy_from_user(seg + 1, src, alen)) {
68			err = -EFAULT;
69			goto out_err;
70		}
71		pseg = &seg->next;
72		len -= alen;
73		src = ((char __user *)src) + alen;
74	}
75
76	err = security_msg_msg_alloc(msg);
77	if (err)
78		goto out_err;
79
80	return msg;
81
82out_err:
83	free_msg(msg);
84	return ERR_PTR(err);
85}
86
87int store_msg(void __user *dest, struct msg_msg *msg, int len)
88{
89	int alen;
90	struct msg_msgseg *seg;
91
92	alen = len;
93	if (alen > DATALEN_MSG)
94		alen = DATALEN_MSG;
95	if (copy_to_user(dest, msg + 1, alen))
96		return -1;
97
98	len -= alen;
99	dest = ((char __user *)dest) + alen;
100	seg = msg->next;
101	while (len > 0) {
102		alen = len;
103		if (alen > DATALEN_SEG)
104			alen = DATALEN_SEG;
105		if (copy_to_user(dest, seg + 1, alen))
106			return -1;
107		len -= alen;
108		dest = ((char __user *)dest) + alen;
109		seg = seg->next;
110	}
111	return 0;
112}
113
114void free_msg(struct msg_msg *msg)
115{
116	struct msg_msgseg *seg;
117
118	security_msg_msg_free(msg);
119
120	seg = msg->next;
121	kfree(msg);
122	while (seg != NULL) {
123		struct msg_msgseg *tmp = seg->next;
124		kfree(seg);
125		seg = tmp;
126	}
127}
128