2005-09-12 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   };
162
163 \f
164 gpgme_error_t
165 gpgme_data_new (gpgme_data_t *dh)
166 {
167   gpgme_error_t err = _gpgme_data_new (dh, &mem_cbs);
168   if (err)
169     return err;
170
171   return 0;
172 }
173
174
175 /* Create a new data buffer filled with SIZE bytes starting from
176    BUFFER.  If COPY is zero, copying is delayed until necessary, and
177    the data is taken from the original location when needed.  */
178 gpgme_error_t
179 gpgme_data_new_from_mem (gpgme_data_t *dh, const char *buffer,
180                          size_t size, int copy)
181 {
182   gpgme_error_t err = _gpgme_data_new (dh, &mem_cbs);
183   if (err)
184     return err;
185
186   if (copy)
187     {
188       char *bufcpy = malloc (size);
189       if (!bufcpy)
190         _gpgme_data_release (*dh);
191       memcpy (bufcpy, buffer, size);
192       (*dh)->data.mem.buffer = bufcpy;
193     }
194   else
195     (*dh)->data.mem.orig_buffer = buffer;
196   
197   (*dh)->data.mem.size = size;
198   (*dh)->data.mem.length = size;
199   return 0;
200 }
201
202
203 char *
204 gpgme_data_release_and_get_mem (gpgme_data_t dh, size_t *r_len)
205 {
206   char *str = NULL;
207
208   if (!dh || dh->cbs != &mem_cbs)
209     return NULL;
210
211   str = dh->data.mem.buffer;
212   if (!str && dh->data.mem.orig_buffer)
213     {
214       str = malloc (dh->data.mem.length);
215       if (!str)
216         return NULL;
217       memcpy (str, dh->data.mem.orig_buffer, dh->data.mem.length);
218     }
219
220   if (r_len)
221     *r_len = dh->data.mem.length;
222
223   return str;
224 }