2007-01-18 Marcus Brinkmann <marcus@g10code.de>
[gpgme.git] / gpgme / data-mem.c
1 /* data-mem.c - A memory based data object.
2    Copyright (C) 2002, 2003, 2004 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
34 \f
35 static ssize_t
36 mem_read (gpgme_data_t dh, void *buffer, size_t size)
37 {
38   size_t amt = dh->data.mem.length - dh->data.mem.offset;
39   const char *src;
40
41   if (!amt)
42     return 0;
43
44   if (size < amt)
45     amt = size;
46
47   src = dh->data.mem.buffer ? dh->data.mem.buffer : dh->data.mem.orig_buffer;
48   memcpy (buffer, src + dh->data.mem.offset, amt);
49   dh->data.mem.offset += amt;
50   return amt;
51 }
52
53
54 static ssize_t
55 mem_write (gpgme_data_t dh, const void *buffer, size_t size)
56 {
57   size_t unused;
58
59   if (!dh->data.mem.buffer && dh->data.mem.orig_buffer)
60     {
61       size_t new_size = dh->data.mem.size;
62       char *new_buffer;
63
64       if (new_size < dh->data.mem.offset + size)
65         new_size = dh->data.mem.offset + size;
66
67       new_buffer = malloc (new_size);
68       if (!new_buffer)
69         return -1;
70       memcpy (new_buffer, dh->data.mem.orig_buffer, dh->data.mem.length);
71
72       dh->data.mem.buffer = new_buffer;      
73       dh->data.mem.size = new_size;
74     }
75
76   unused = dh->data.mem.size - dh->data.mem.offset;
77   if (unused < size)
78     {
79       /* Allocate a large enough buffer with exponential backoff.  */
80 #define INITIAL_ALLOC 512
81       size_t new_size = dh->data.mem.size
82         ? (2 * dh->data.mem.size) : INITIAL_ALLOC;
83       char *new_buffer;
84
85       if (new_size < dh->data.mem.offset + size)
86         new_size = dh->data.mem.offset + size;
87
88       new_buffer = realloc (dh->data.mem.buffer, new_size);
89       if (!new_buffer && new_size > dh->data.mem.offset + size)
90         {
91           /* Maybe we were too greedy, try again.  */
92           new_size = dh->data.mem.offset + size;
93           new_buffer = realloc (dh->data.mem.buffer, new_size);
94         }
95       if (!new_buffer)
96         return -1;
97       dh->data.mem.buffer = new_buffer;
98       dh->data.mem.size = new_size;
99     }
100
101   memcpy (dh->data.mem.buffer + dh->data.mem.offset, buffer, size);
102   dh->data.mem.offset += size;
103   if (dh->data.mem.length < dh->data.mem.offset)
104     dh->data.mem.length = dh->data.mem.offset;
105   return size;
106 }
107
108
109 static off_t
110 mem_seek (gpgme_data_t dh, off_t offset, int whence)
111 {
112   switch (whence)
113     {
114     case SEEK_SET:
115       if (offset < 0 || offset > dh->data.mem.length)
116         {
117           errno = EINVAL;
118           return -1;
119         }
120       dh->data.mem.offset = offset;
121       break;
122     case SEEK_CUR:
123       if ((offset > 0 && dh->data.mem.length - dh->data.mem.offset < offset)
124           || (offset < 0 && dh->data.mem.offset < -offset)) 
125         {
126           errno = EINVAL;
127           return -1;
128         }
129       dh->data.mem.offset += offset;
130       break;
131     case SEEK_END:
132       if (offset > 0 || -offset > dh->data.mem.length)
133         {
134           errno = EINVAL;
135           return -1;
136         }
137       dh->data.mem.offset = dh->data.mem.length - offset;
138       break;
139     default:
140       errno = EINVAL;
141       return -1;
142     }
143   return dh->data.mem.offset;
144 }
145
146
147 static void
148 mem_release (gpgme_data_t dh)
149 {
150   if (dh->data.mem.buffer)
151     free (dh->data.mem.buffer);
152 }
153
154
155 static struct _gpgme_data_cbs mem_cbs =
156   {
157     mem_read,
158     mem_write,
159     mem_seek,
160     mem_release,
161     NULL
162   };
163
164 \f
165 /* Create a new data buffer and return it in R_DH.  */
166 gpgme_error_t
167 gpgme_data_new (gpgme_data_t *dh)
168 {
169   gpgme_error_t err = _gpgme_data_new (dh, &mem_cbs);
170   if (err)
171     return err;
172
173   return 0;
174 }
175
176
177 /* Create a new data buffer filled with SIZE bytes starting from
178    BUFFER.  If COPY is zero, copying is delayed until necessary, and
179    the data is taken from the original location when needed.  */
180 gpgme_error_t
181 gpgme_data_new_from_mem (gpgme_data_t *dh, const char *buffer,
182                          size_t size, int copy)
183 {
184   gpgme_error_t err = _gpgme_data_new (dh, &mem_cbs);
185   if (err)
186     return err;
187
188   if (copy)
189     {
190       char *bufcpy = malloc (size);
191       if (!bufcpy)
192         _gpgme_data_release (*dh);
193       memcpy (bufcpy, buffer, size);
194       (*dh)->data.mem.buffer = bufcpy;
195     }
196   else
197     (*dh)->data.mem.orig_buffer = buffer;
198   
199   (*dh)->data.mem.size = size;
200   (*dh)->data.mem.length = size;
201   return 0;
202 }
203
204
205 /* Destroy the data buffer DH and return a pointer to its content.
206    The memory has be to released with gpgme_free() by the user.  It's
207    size is returned in R_LEN.  */
208 char *
209 gpgme_data_release_and_get_mem (gpgme_data_t dh, size_t *r_len)
210 {
211   char *str = NULL;
212
213   if (!dh || dh->cbs != &mem_cbs)
214     {
215       gpgme_data_release (dh);
216       return NULL;
217     }
218
219   str = dh->data.mem.buffer;
220   if (!str && dh->data.mem.orig_buffer)
221     {
222       str = malloc (dh->data.mem.length);
223       if (!str)
224         {
225           gpgme_data_release (dh);
226           return NULL;
227         }
228       memcpy (str, dh->data.mem.orig_buffer, dh->data.mem.length);
229     }
230   else
231     /* Prevent mem_release from releasing the buffer memory.  We must
232        not fail from this point.  */
233     dh->data.mem.buffer = NULL;
234
235   if (r_len)
236     *r_len = dh->data.mem.length;
237
238   gpgme_data_release (dh);
239
240   return str;
241 }
242
243
244 /* Release the memory returned by gpgme_data_release_and_get_mem().  */
245 void
246 gpgme_free (void *buffer)
247 {
248   if (buffer)
249     free (buffer);
250 }
251