xref: /freebsd/sys/kern/subr_msgbuf.c (revision 39beb93c)
1 /*-
2  * Copyright (c) 2003 Ian Dowse.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23  * SUCH DAMAGE.
24  *
25  * $FreeBSD$
26  */
27 
28 /*
29  * Generic message buffer support routines.
30  */
31 
32 #include <sys/param.h>
33 #include <sys/systm.h>
34 #include <sys/msgbuf.h>
35 
36 /* Read/write sequence numbers are modulo a multiple of the buffer size. */
37 #define SEQMOD(size) ((size) * 16)
38 
39 static u_int msgbuf_cksum(struct msgbuf *mbp);
40 
41 /*
42  * Initialize a message buffer of the specified size at the specified
43  * location. This also zeros the buffer area.
44  */
45 void
46 msgbuf_init(struct msgbuf *mbp, void *ptr, int size)
47 {
48 
49 	mbp->msg_ptr = ptr;
50 	mbp->msg_size = size;
51 	mbp->msg_seqmod = SEQMOD(size);
52 	msgbuf_clear(mbp);
53 	mbp->msg_magic = MSG_MAGIC;
54 }
55 
56 /*
57  * Reinitialize a message buffer, retaining its previous contents if
58  * the size and checksum are correct. If the old contents cannot be
59  * recovered, the message buffer is cleared.
60  */
61 void
62 msgbuf_reinit(struct msgbuf *mbp, void *ptr, int size)
63 {
64 	u_int cksum;
65 
66 	if (mbp->msg_magic != MSG_MAGIC || mbp->msg_size != size) {
67 		msgbuf_init(mbp, ptr, size);
68 		return;
69 	}
70 	mbp->msg_seqmod = SEQMOD(size);
71 	mbp->msg_wseq = MSGBUF_SEQNORM(mbp, mbp->msg_wseq);
72 	mbp->msg_rseq = MSGBUF_SEQNORM(mbp, mbp->msg_rseq);
73         mbp->msg_ptr = ptr;
74 	cksum = msgbuf_cksum(mbp);
75 	if (cksum != mbp->msg_cksum) {
76 		if (bootverbose) {
77 			printf("msgbuf cksum mismatch (read %x, calc %x)\n",
78 			    mbp->msg_cksum, cksum);
79 			printf("Old msgbuf not recovered\n");
80 		}
81 		msgbuf_clear(mbp);
82 	}
83 }
84 
85 /*
86  * Clear the message buffer.
87  */
88 void
89 msgbuf_clear(struct msgbuf *mbp)
90 {
91 
92 	bzero(mbp->msg_ptr, mbp->msg_size);
93 	mbp->msg_wseq = 0;
94 	mbp->msg_rseq = 0;
95 	mbp->msg_cksum = 0;
96 }
97 
98 /*
99  * Get a count of the number of unread characters in the message buffer.
100  */
101 int
102 msgbuf_getcount(struct msgbuf *mbp)
103 {
104 	u_int len;
105 
106 	len = MSGBUF_SEQSUB(mbp, mbp->msg_wseq, mbp->msg_rseq);
107 	if (len > mbp->msg_size)
108 		len = mbp->msg_size;
109 	return (len);
110 }
111 
112 /*
113  * Append a character to a message buffer.  This function can be
114  * considered fully reentrant so long as the number of concurrent
115  * callers is less than the number of characters in the buffer.
116  * However, the message buffer is only guaranteed to be consistent
117  * for reading when there are no callers in this function.
118  */
119 void
120 msgbuf_addchar(struct msgbuf *mbp, int c)
121 {
122 	u_int new_seq, pos, seq;
123 
124 	do {
125 		seq = mbp->msg_wseq;
126 		new_seq = MSGBUF_SEQNORM(mbp, seq + 1);
127 	} while (atomic_cmpset_rel_int(&mbp->msg_wseq, seq, new_seq) == 0);
128 	pos = MSGBUF_SEQ_TO_POS(mbp, seq);
129 	atomic_add_int(&mbp->msg_cksum, (u_int)(u_char)c -
130 	    (u_int)(u_char)mbp->msg_ptr[pos]);
131 	mbp->msg_ptr[pos] = c;
132 }
133 
134 /*
135  * Read and mark as read a character from a message buffer.
136  * Returns the character, or -1 if no characters are available.
137  */
138 int
139 msgbuf_getchar(struct msgbuf *mbp)
140 {
141 	u_int len, wseq;
142 	int c;
143 
144 	wseq = mbp->msg_wseq;
145 	len = MSGBUF_SEQSUB(mbp, wseq, mbp->msg_rseq);
146 	if (len == 0)
147 		return (-1);
148 	if (len > mbp->msg_size)
149 		mbp->msg_rseq = MSGBUF_SEQNORM(mbp, wseq - mbp->msg_size);
150 	c = (u_char)mbp->msg_ptr[MSGBUF_SEQ_TO_POS(mbp, mbp->msg_rseq)];
151 	mbp->msg_rseq = MSGBUF_SEQNORM(mbp, mbp->msg_rseq + 1);
152 	return (c);
153 }
154 
155 /*
156  * Read and mark as read a number of characters from a message buffer.
157  * Returns the number of characters that were placed in `buf'.
158  */
159 int
160 msgbuf_getbytes(struct msgbuf *mbp, char *buf, int buflen)
161 {
162 	u_int len, pos, wseq;
163 
164 	wseq = mbp->msg_wseq;
165 	len = MSGBUF_SEQSUB(mbp, wseq, mbp->msg_rseq);
166 	if (len == 0)
167 		return (0);
168 	if (len > mbp->msg_size) {
169 		mbp->msg_rseq = MSGBUF_SEQNORM(mbp, wseq - mbp->msg_size);
170 		len = mbp->msg_size;
171 	}
172 	pos = MSGBUF_SEQ_TO_POS(mbp, mbp->msg_rseq);
173 	len = min(len, mbp->msg_size - pos);
174 	len = min(len, (u_int)buflen);
175 
176 	bcopy(&mbp->msg_ptr[pos], buf, len);
177 	mbp->msg_rseq = MSGBUF_SEQNORM(mbp, mbp->msg_rseq + len);
178 	return (len);
179 }
180 
181 /*
182  * Peek at the full contents of a message buffer without marking any
183  * data as read. `seqp' should point to an unsigned integer that
184  * msgbuf_peekbytes() can use to retain state between calls so that
185  * the whole message buffer can be read in multiple short reads.
186  * To initialise this variable to the start of the message buffer,
187  * call msgbuf_peekbytes() with a NULL `buf' parameter.
188  *
189  * Returns the number of characters that were placed in `buf'.
190  */
191 int
192 msgbuf_peekbytes(struct msgbuf *mbp, char *buf, int buflen, u_int *seqp)
193 {
194 	u_int len, pos, wseq;
195 
196 	if (buf == NULL) {
197 		/* Just initialise *seqp. */
198 		*seqp = MSGBUF_SEQNORM(mbp, mbp->msg_wseq - mbp->msg_size);
199 		return (0);
200 	}
201 
202 	wseq = mbp->msg_wseq;
203 	len = MSGBUF_SEQSUB(mbp, wseq, *seqp);
204 	if (len == 0)
205 		return (0);
206 	if (len > mbp->msg_size) {
207 		*seqp = MSGBUF_SEQNORM(mbp, wseq - mbp->msg_size);
208 		len = mbp->msg_size;
209 	}
210 	pos = MSGBUF_SEQ_TO_POS(mbp, *seqp);
211 	len = min(len, mbp->msg_size - pos);
212 	len = min(len, (u_int)buflen);
213 	bcopy(&mbp->msg_ptr[MSGBUF_SEQ_TO_POS(mbp, *seqp)], buf, len);
214 	*seqp = MSGBUF_SEQNORM(mbp, *seqp + len);
215 	return (len);
216 }
217 
218 /*
219  * Compute the checksum for the complete message buffer contents.
220  */
221 static u_int
222 msgbuf_cksum(struct msgbuf *mbp)
223 {
224 	u_int i, sum;
225 
226 	sum = 0;
227 	for (i = 0; i < mbp->msg_size; i++)
228 		sum += (u_char)mbp->msg_ptr[i];
229 	return (sum);
230 }
231 
232 /*
233  * Copy from one message buffer to another.
234  */
235 void
236 msgbuf_copy(struct msgbuf *src, struct msgbuf *dst)
237 {
238 	int c;
239 
240 	while ((c = msgbuf_getchar(src)) >= 0)
241 		msgbuf_addchar(dst, c);
242 }
243