Upgrade diffutils. 1/2
[dragonfly.git] / lib / libc / stdio / open_wmemstream.c
1 /*-
2  * Copyright (c) 2013 Hudson River Trading LLC
3  * Written by: John H. Baldwin <jhb@FreeBSD.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  *
27  * $FreeBSD: head/lib/libc/stdio/open_wmemstream.c 281887 2015-04-23 14:22:20Z jhb $
28  */
29
30 #include "namespace.h"
31 #include <assert.h>
32 #include <errno.h>
33 #include <limits.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <wchar.h>
38 #include "un-namespace.h"
39
40 /* XXX: There is no FPOS_MAX.  This assumes fpos_t is an off_t. */
41 #define FPOS_MAX        OFF_MAX
42
43 struct wmemstream {
44         wchar_t **bufp;
45         size_t *sizep;
46         ssize_t len;
47         fpos_t offset;
48         mbstate_t mbstate;
49 };
50
51 static int
52 wmemstream_grow(struct wmemstream *ms, fpos_t newoff)
53 {
54         wchar_t *buf;
55         ssize_t newsize;
56
57         if (newoff < 0 || newoff >= SSIZE_MAX / sizeof(wchar_t))
58                 newsize = SSIZE_MAX / sizeof(wchar_t) - 1;
59         else
60                 newsize = newoff;
61         if (newsize > ms->len) {
62                 buf = realloc(*ms->bufp, (newsize + 1) * sizeof(wchar_t));
63                 if (buf != NULL) {
64 #ifdef DEBUG
65                         fprintf(stderr, "WMS: %p growing from %zd to %zd\n",
66                             ms, ms->len, newsize);
67 #endif
68                         wmemset(buf + ms->len + 1, 0, newsize - ms->len);
69                         *ms->bufp = buf;
70                         ms->len = newsize;
71                         return (1);
72                 }
73                 return (0);
74         }
75         return (1);
76 }
77
78 static void
79 wmemstream_update(struct wmemstream *ms)
80 {
81
82         assert(ms->len >= 0 && ms->offset >= 0);
83         *ms->sizep = ms->len < ms->offset ? ms->len : ms->offset;
84 }
85
86 /*
87  * Based on a starting multibyte state and an input buffer, determine
88  * how many wchar_t's would be output.  This doesn't use mbsnrtowcs()
89  * so that it can handle embedded null characters.
90  */
91 static size_t
92 wbuflen(const mbstate_t *state, const char *buf, int len)
93 {
94         mbstate_t lenstate;
95         size_t charlen, count;
96
97         count = 0;
98         lenstate = *state;
99         while (len > 0) {
100                 charlen = mbrlen(buf, len, &lenstate);
101                 if (charlen == (size_t)-1)
102                         return (-1);
103                 if (charlen == (size_t)-2)
104                         break;
105                 if (charlen == 0)
106                         /* XXX: Not sure how else to handle this. */
107                         charlen = 1;
108                 len -= charlen;
109                 buf += charlen;
110                 count++;
111         }
112         return (count);
113 }
114
115 static int
116 wmemstream_write(void *cookie, const char *buf, int len)
117 {
118         struct wmemstream *ms;
119         ssize_t consumed, wlen;
120         size_t charlen;
121
122         ms = cookie;
123         wlen = wbuflen(&ms->mbstate, buf, len);
124         if (wlen < 0) {
125                 errno = EILSEQ;
126                 return (-1);
127         }
128         if (!wmemstream_grow(ms, ms->offset + wlen))
129                 return (-1);
130
131         /*
132          * This copies characters one at a time rather than using
133          * mbsnrtowcs() so it can properly handle embedded null
134          * characters.
135          */
136         consumed = 0;
137         while (len > 0 && ms->offset < ms->len) {
138                 charlen = mbrtowc(*ms->bufp + ms->offset, buf, len,
139                     &ms->mbstate);
140                 if (charlen == (size_t)-1) {
141                         if (consumed == 0) {
142                                 errno = EILSEQ;
143                                 return (-1);
144                         }
145                         /* Treat it as a successful short write. */
146                         break;
147                 }
148                 if (charlen == 0)
149                         /* XXX: Not sure how else to handle this. */
150                         charlen = 1;
151                 if (charlen == (size_t)-2) {
152                         consumed += len;
153                         len = 0;
154                 } else {
155                         consumed += charlen;
156                         buf += charlen;
157                         len -= charlen;
158                         ms->offset++;
159                 }
160         }
161         wmemstream_update(ms);
162 #ifdef DEBUG
163         fprintf(stderr, "WMS: write(%p, %d) = %zd\n", ms, len, consumed);
164 #endif
165         return (consumed);
166 }
167
168 static fpos_t
169 wmemstream_seek(void *cookie, fpos_t pos, int whence)
170 {
171         struct wmemstream *ms;
172         fpos_t old;
173
174         ms = cookie;
175         old = ms->offset;
176         switch (whence) {
177         case SEEK_SET:
178                 /* _fseeko() checks for negative offsets. */
179                 assert(pos >= 0);
180                 ms->offset = pos;
181                 break;
182         case SEEK_CUR:
183                 /* This is only called by _ftello(). */
184                 assert(pos == 0);
185                 break;
186         case SEEK_END:
187                 if (pos < 0) {
188                         if (pos + ms->len < 0) {
189 #ifdef DEBUG
190                                 fprintf(stderr,
191                                     "WMS: bad SEEK_END: pos %jd, len %zd\n",
192                                     (intmax_t)pos, ms->len);
193 #endif
194                                 errno = EINVAL;
195                                 return (-1);
196                         }
197                 } else {
198                         if (FPOS_MAX - ms->len < pos) {
199 #ifdef DEBUG
200                                 fprintf(stderr,
201                                     "WMS: bad SEEK_END: pos %jd, len %zd\n",
202                                     (intmax_t)pos, ms->len);
203 #endif
204                                 errno = EOVERFLOW;
205                                 return (-1);
206                         }
207                 }
208                 ms->offset = ms->len + pos;
209                 break;
210         }
211         /* Reset the multibyte state if a seek changes the position. */
212         if (ms->offset != old)
213                 memset(&ms->mbstate, 0, sizeof(ms->mbstate));
214         wmemstream_update(ms);
215 #ifdef DEBUG
216         fprintf(stderr, "WMS: seek(%p, %jd, %d) %jd -> %jd\n", ms,
217             (intmax_t)pos, whence, (intmax_t)old, (intmax_t)ms->offset);
218 #endif
219         return (ms->offset);
220 }
221
222 static int
223 wmemstream_close(void *cookie)
224 {
225
226         free(cookie);
227         return (0);
228 }
229
230 FILE *
231 open_wmemstream(wchar_t **bufp, size_t *sizep)
232 {
233         struct wmemstream *ms;
234         int save_errno;
235         FILE *fp;
236
237         if (bufp == NULL || sizep == NULL) {
238                 errno = EINVAL;
239                 return (NULL);
240         }
241         *bufp = calloc(1, sizeof(wchar_t));
242         if (*bufp == NULL)
243                 return (NULL);
244         ms = malloc(sizeof(*ms));
245         if (ms == NULL) {
246                 save_errno = errno;
247                 free(*bufp);
248                 *bufp = NULL;
249                 errno = save_errno;
250                 return (NULL);
251         }
252         ms->bufp = bufp;
253         ms->sizep = sizep;
254         ms->len = 0;
255         ms->offset = 0;
256         memset(&ms->mbstate, 0, sizeof(mbstate_t));
257         wmemstream_update(ms);
258         fp = funopen(ms, NULL, wmemstream_write, wmemstream_seek,
259             wmemstream_close);
260         if (fp == NULL) {
261                 save_errno = errno;
262                 free(ms);
263                 free(*bufp);
264                 *bufp = NULL;
265                 errno = save_errno;
266                 return (NULL);
267         }
268         fwide(fp, 1);
269         return (fp);
270 }