2004-06-23 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 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 General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (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    General Public License for more details.
15  
16    You should have received a copy of the GNU General Public License
17    along with GPGME; if not, write to the Free Software Foundation,
18    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
19
20 #if HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <errno.h>
25 #include <stdlib.h>
26 #include <unistd.h>
27 #include <assert.h>
28 #include <string.h>
29
30 #include "data.h"
31 #include "util.h"
32
33 \f
34 static ssize_t
35 mem_read (gpgme_data_t dh, void *buffer, size_t size)
36 {
37   size_t amt = dh->data.mem.length - dh->data.mem.offset;
38   const char *src;
39
40   if (!amt)
41     return 0;
42
43   if (size < amt)
44     amt = size;
45
46   src = dh->data.mem.buffer ? dh->data.mem.buffer : dh->data.mem.orig_buffer;
47   memcpy (buffer, src + dh->data.mem.offset, amt);
48   dh->data.mem.offset += amt;
49   return amt;
50 }
51
52
53 static ssize_t
54 mem_write (gpgme_data_t dh, const void *buffer, size_t size)
55 {
56   size_t unused;
57
58   if (!dh->data.mem.buffer && dh->data.mem.orig_buffer)
59     {
60       size_t new_size = dh->data.mem.size;
61       char *new_buffer;
62
63       if (new_size < dh->data.mem.offset + size)
64         new_size = dh->data.mem.offset + size;
65
66       new_buffer = malloc (new_size);
67       if (!new_buffer)
68         return -1;
69       memcpy (new_buffer, dh->data.mem.orig_buffer, dh->data.mem.length);
70
71       dh->data.mem.buffer = new_buffer;      
72       dh->data.mem.size = new_size;
73     }
74
75   unused = dh->data.mem.size - dh->data.mem.offset;
76   if (unused < size)
77     {
78       /* Allocate a large enough buffer with exponential backoff.  */
79 #define INITIAL_ALLOC 512
80       size_t new_size = dh->data.mem.size
81         ? (2 * dh->data.mem.size) : INITIAL_ALLOC;
82       char *new_buffer;
83
84       if (new_size < dh->data.mem.offset + size)
85         new_size = dh->data.mem.offset + size;
86
87       new_buffer = realloc (dh->data.mem.buffer, new_size);
88       if (!new_buffer && new_size > dh->data.mem.offset + size)
89         {
90           /* Maybe we were too greedy, try again.  */
91           new_size = dh->data.mem.offset + size;
92           new_buffer = realloc (dh->data.mem.buffer, new_size);
93         }
94       if (!new_buffer)
95         return -1;
96       dh->data.mem.buffer = new_buffer;
97       dh->data.mem.size = new_size;
98     }
99
100   memcpy (dh->data.mem.buffer + dh->data.mem.offset, buffer, size);
101   dh->data.mem.offset += size;
102   if (dh->data.mem.length < dh->data.mem.offset)
103     dh->data.mem.length = dh->data.mem.offset;
104   return size;
105 }
106
107
108 static off_t
109 mem_seek (gpgme_data_t dh, off_t offset, int whence)
110 {
111   switch (whence)
112     {
113     case SEEK_SET:
114       if (offset < 0 || offset > dh->data.mem.length)
115         {
116           errno = EINVAL;
117           return -1;
118         }
119       dh->data.mem.offset = offset;
120       break;
121     case SEEK_CUR:
122       if ((offset > 0 && dh->data.mem.length - dh->data.mem.offset < offset)
123           || (offset < 0 && dh->data.mem.offset < -offset)) 
124         {
125           errno = EINVAL;
126           return -1;
127         }
128       dh->data.mem.offset += offset;
129       break;
130     case SEEK_END:
131       if (offset > 0 || -offset > dh->data.mem.length)
132         {
133           errno = EINVAL;
134           return -1;
135         }
136       dh->data.mem.offset = dh->data.mem.length - offset;
137       break;
138     default:
139       errno = EINVAL;
140       return -1;
141     }
142   return dh->data.mem.offset;
143 }
144
145
146 static void
147 mem_release (gpgme_data_t dh)
148 {
149   if (dh->data.mem.buffer)
150     free (dh->data.mem.buffer);
151 }
152
153
154 static struct _gpgme_data_cbs mem_cbs =
155   {
156     mem_read,
157     mem_write,
158     mem_seek,
159     mem_release
160   };
161
162 \f
163 gpgme_error_t
164 gpgme_data_new (gpgme_data_t *dh)
165 {
166   gpgme_error_t err = _gpgme_data_new (dh, &mem_cbs);
167   if (err)
168     return err;
169
170   return 0;
171 }
172
173
174 /* Create a new data buffer filled with SIZE bytes starting from
175    BUFFER.  If COPY is zero, copying is delayed until necessary, and
176    the data is taken from the original location when needed.  */
177 gpgme_error_t
178 gpgme_data_new_from_mem (gpgme_data_t *dh, const char *buffer,
179                          size_t size, int copy)
180 {
181   gpgme_error_t err = _gpgme_data_new (dh, &mem_cbs);
182   if (err)
183     return err;
184
185   if (copy)
186     {
187       char *bufcpy = malloc (size);
188       if (!bufcpy)
189         _gpgme_data_release (*dh);
190       memcpy (bufcpy, buffer, size);
191       (*dh)->data.mem.buffer = bufcpy;
192     }
193   else
194     (*dh)->data.mem.orig_buffer = buffer;
195   
196   (*dh)->data.mem.size = size;
197   (*dh)->data.mem.length = size;
198   return 0;
199 }
200
201
202 char *
203 gpgme_data_release_and_get_mem (gpgme_data_t dh, size_t *r_len)
204 {
205   char *str = NULL;
206
207   if (!dh || dh->cbs != &mem_cbs)
208     return NULL;
209
210   str = dh->data.mem.buffer;
211   if (!str && dh->data.mem.orig_buffer)
212     {
213       str = malloc (dh->data.mem.length);
214       if (!str)
215         return NULL;
216       memcpy (str, dh->data.mem.orig_buffer, dh->data.mem.length);
217     }
218
219   if (r_len)
220     *r_len = dh->data.mem.length;
221
222   return str;
223 }