2010-05-06 Marcus Brinkmann <marcus@g10code.de>
[gpgme.git] / src / data-mem.c
1 /* data-mem.c - A memory based data object.
2    Copyright (C) 2002, 2003, 2004, 2007 g10 Code GmbH
3  
4    This file is part of GPGME.
5  
6    GPGME is free software; you can redistribute it and/or modify it
7    under the terms of the GNU Lesser General Public License as
8    published by the Free Software Foundation; either version 2.1 of
9    the License, or (at your option) any later version.
10    
11    GPGME is distributed in the hope that it will be useful, but
12    WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Lesser General Public License for more details.
15    
16    You should have received a copy of the GNU Lesser General Public
17    License along with this program; if not, write to the Free Software
18    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19    02111-1307, USA.  */
20
21 #if HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <errno.h>
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <assert.h>
29 #include <string.h>
30
31 #include "data.h"
32 #include "util.h"
33 #include "debug.h"
34
35 \f
36 static ssize_t
37 mem_read (gpgme_data_t dh, void *buffer, size_t size)
38 {
39   size_t amt = dh->data.mem.length - dh->data.mem.offset;
40   const char *src;
41
42   if (!amt)
43     return 0;
44
45   if (size < amt)
46     amt = size;
47
48   src = dh->data.mem.buffer ? dh->data.mem.buffer : dh->data.mem.orig_buffer;
49   memcpy (buffer, src + dh->data.mem.offset, amt);
50   dh->data.mem.offset += amt;
51   return amt;
52 }
53
54
55 static ssize_t
56 mem_write (gpgme_data_t dh, const void *buffer, size_t size)
57 {
58   size_t unused;
59
60   if (!dh->data.mem.buffer && dh->data.mem.orig_buffer)
61     {
62       size_t new_size = dh->data.mem.size;
63       char *new_buffer;
64
65       if (new_size < dh->data.mem.offset + size)
66         new_size = dh->data.mem.offset + size;
67
68       new_buffer = malloc (new_size);
69       if (!new_buffer)
70         return -1;
71       memcpy (new_buffer, dh->data.mem.orig_buffer, dh->data.mem.length);
72
73       dh->data.mem.buffer = new_buffer;      
74       dh->data.mem.size = new_size;
75     }
76
77   unused = dh->data.mem.size - dh->data.mem.offset;
78   if (unused < size)
79     {
80       /* Allocate a large enough buffer with exponential backoff.  */
81 #define INITIAL_ALLOC 512
82       size_t new_size = dh->data.mem.size
83         ? (2 * dh->data.mem.size) : INITIAL_ALLOC;
84       char *new_buffer;
85
86       if (new_size < dh->data.mem.offset + size)
87         new_size = dh->data.mem.offset + size;
88
89       new_buffer = realloc (dh->data.mem.buffer, new_size);
90       if (!new_buffer && new_size > dh->data.mem.offset + size)
91         {
92           /* Maybe we were too greedy, try again.  */
93           new_size = dh->data.mem.offset + size;
94           new_buffer = realloc (dh->data.mem.buffer, new_size);
95         }
96       if (!new_buffer)
97         return -1;
98       dh->data.mem.buffer = new_buffer;
99       dh->data.mem.size = new_size;
100     }
101
102   memcpy (dh->data.mem.buffer + dh->data.mem.offset, buffer, size);
103   dh->data.mem.offset += size;
104   if (dh->data.mem.length < dh->data.mem.offset)
105     dh->data.mem.length = dh->data.mem.offset;
106   return size;
107 }
108
109
110 static off_t
111 mem_seek (gpgme_data_t dh, off_t offset, int whence)
112 {
113   switch (whence)
114     {
115     case SEEK_SET:
116       if (offset < 0 || offset > dh->data.mem.length)
117         {
118           gpg_err_set_errno (EINVAL);
119           return -1;
120         }
121       dh->data.mem.offset = offset;
122       break;
123     case SEEK_CUR:
124       if ((offset > 0 && dh->data.mem.length - dh->data.mem.offset < offset)
125           || (offset < 0 && dh->data.mem.offset < -offset)) 
126         {
127           gpg_err_set_errno (EINVAL);
128           return -1;
129         }
130       dh->data.mem.offset += offset;
131       break;
132     case SEEK_END:
133       if (offset > 0 || -offset > dh->data.mem.length)
134         {
135           gpg_err_set_errno (EINVAL);
136           return -1;
137         }
138       dh->data.mem.offset = dh->data.mem.length - offset;
139       break;
140     default:
141       gpg_err_set_errno (EINVAL);
142       return -1;
143     }
144   return dh->data.mem.offset;
145 }
146
147
148 static void
149 mem_release (gpgme_data_t dh)
150 {
151   if (dh->data.mem.buffer)
152     free (dh->data.mem.buffer);
153 }
154
155
156 static struct _gpgme_data_cbs mem_cbs =
157   {
158     mem_read,
159     mem_write,
160     mem_seek,
161     mem_release,
162     NULL
163   };
164
165 \f
166 /* Create a new data buffer and return it in R_DH.  */
167 gpgme_error_t
168 gpgme_data_new (gpgme_data_t *r_dh)
169 {
170   gpgme_error_t err;
171   TRACE_BEG (DEBUG_DATA, "gpgme_data_new", r_dh);
172
173   err = _gpgme_data_new (r_dh, &mem_cbs);
174
175   if (err)
176     return TRACE_ERR (err);
177
178   return TRACE_SUC1 ("dh=%p", *r_dh);
179 }
180
181
182 /* Create a new data buffer filled with SIZE bytes starting from
183    BUFFER.  If COPY is zero, copying is delayed until necessary, and
184    the data is taken from the original location when needed.  */
185 gpgme_error_t
186 gpgme_data_new_from_mem (gpgme_data_t *r_dh, const char *buffer,
187                          size_t size, int copy)
188 {
189   gpgme_error_t err;
190   TRACE_BEG4 (DEBUG_DATA, "gpgme_data_new_from_mem", r_dh,
191               "buffer=%p, size=%u, copy=%i (%s)", buffer, size,
192               copy, copy ? "yes" : "no");
193
194   err = _gpgme_data_new (r_dh, &mem_cbs);
195   if (err)
196     return TRACE_ERR (err);
197
198   if (copy)
199     {
200       char *bufcpy = malloc (size);
201       if (!bufcpy)
202         {
203           int saved_errno = errno;
204           _gpgme_data_release (*r_dh);
205           return TRACE_ERR (gpg_error_from_errno (saved_errno));
206         }
207       memcpy (bufcpy, buffer, size);
208       (*r_dh)->data.mem.buffer = bufcpy;
209     }
210   else
211     (*r_dh)->data.mem.orig_buffer = buffer;
212   
213   (*r_dh)->data.mem.size = size;
214   (*r_dh)->data.mem.length = size;
215   return TRACE_SUC1 ("dh=%p", *r_dh);
216 }
217
218
219 /* Destroy the data buffer DH and return a pointer to its content.
220    The memory has be to released with gpgme_free() by the user.  It's
221    size is returned in R_LEN.  */
222 char *
223 gpgme_data_release_and_get_mem (gpgme_data_t dh, size_t *r_len)
224 {
225   char *str = NULL;
226
227   TRACE_BEG1 (DEBUG_DATA, "gpgme_data_release_and_get_mem", dh,
228               "r_len=%p", r_len);
229
230   if (!dh || dh->cbs != &mem_cbs)
231     {
232       gpgme_data_release (dh);
233       TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
234       return NULL;
235     }
236
237   str = dh->data.mem.buffer;
238   if (!str && dh->data.mem.orig_buffer)
239     {
240       str = malloc (dh->data.mem.length);
241       if (!str)
242         {
243           int saved_errno = errno;
244           gpgme_data_release (dh);
245           TRACE_ERR (gpg_error_from_errno (saved_errno));
246           return NULL;
247         }
248       memcpy (str, dh->data.mem.orig_buffer, dh->data.mem.length);
249     }
250   else
251     /* Prevent mem_release from releasing the buffer memory.  We must
252        not fail from this point.  */
253     dh->data.mem.buffer = NULL;
254
255   if (r_len)
256     *r_len = dh->data.mem.length;
257
258   gpgme_data_release (dh);
259
260   if (r_len)
261     {
262       TRACE_SUC2 ("buffer=%p, len=%u", str, *r_len);
263     }
264   else
265     {
266       TRACE_SUC1 ("buffer=%p", str);
267     }
268   return str;
269 }
270
271
272 /* Release the memory returned by gpgme_data_release_and_get_mem().  */
273 void
274 gpgme_free (void *buffer)
275 {
276   TRACE (DEBUG_DATA, "gpgme_free", buffer);
277
278   if (buffer)
279     free (buffer);
280 }