1/*
2 * Copyright (c) 1992, Brian Berliner
3 *
4 * You may distribute under the terms of the GNU General Public License as
5 * specified in the README file that comes with the CVS source distribution.
6 *
7 * A simple ndbm-emulator for CVS.  It parses a text file of the format:
8 *
9 * key	value
10 *
11 * at dbm_open time, and loads the entire file into memory.  As such, it is
12 * probably only good for fairly small modules files.  Ours is about 30K in
13 * size, and this code works fine.
14 */
15
16#include <assert.h>
17#include "cvs.h"
18#include "getline.h"
19
20#ifdef MY_NDBM
21
22static void mydbm_load_file PROTO ((FILE *, List *));
23
24/* Returns NULL on error in which case errno has been set to indicate
25   the error.  Can also call error() itself.  */
26/* ARGSUSED */
27DBM *
28mydbm_open (file, flags, mode)
29    char *file;
30    int flags;
31    int mode;
32{
33    FILE *fp;
34    DBM *db;
35
36    fp = CVS_FOPEN (file, FOPEN_BINARY_READ);
37    if (fp == NULL && !(existence_error (errno) && (flags & O_CREAT)))
38	return ((DBM *) 0);
39
40    db = (DBM *) xmalloc (sizeof (*db));
41    db->dbm_list = getlist ();
42    db->modified = 0;
43    db->name = xstrdup (file);
44
45    if (fp != NULL)
46    {
47	mydbm_load_file (fp, db->dbm_list);
48	if (fclose (fp) < 0)
49	    error (0, errno, "cannot close %s", file);
50    }
51    return (db);
52}
53
54static int write_item PROTO ((Node *, void *));
55
56static int
57write_item (node, data)
58    Node *node;
59    void *data;
60{
61    FILE *fp = (FILE *)data;
62    fputs (node->key, fp);
63    fputs (" ", fp);
64    fputs (node->data, fp);
65    fputs ("\012", fp);
66    return 0;
67}
68
69void
70mydbm_close (db)
71    DBM *db;
72{
73    if (db->modified)
74    {
75	FILE *fp;
76	fp = CVS_FOPEN (db->name, FOPEN_BINARY_WRITE);
77	if (fp == NULL)
78	    error (1, errno, "cannot write %s", db->name);
79	walklist (db->dbm_list, write_item, (void *)fp);
80	if (fclose (fp) < 0)
81	    error (0, errno, "cannot close %s", db->name);
82    }
83    free (db->name);
84    dellist (&db->dbm_list);
85    free ((char *) db);
86}
87
88datum
89mydbm_fetch (db, key)
90    DBM *db;
91    datum key;
92{
93    Node *p;
94    char *s;
95    datum val;
96
97    /* make sure it's null-terminated */
98    s = xmalloc (key.dsize + 1);
99    (void) strncpy (s, key.dptr, key.dsize);
100    s[key.dsize] = '\0';
101
102    p = findnode (db->dbm_list, s);
103    if (p)
104    {
105	val.dptr = p->data;
106	val.dsize = strlen (p->data);
107    }
108    else
109    {
110	val.dptr = (char *) NULL;
111	val.dsize = 0;
112    }
113    free (s);
114    return (val);
115}
116
117datum
118mydbm_firstkey (db)
119    DBM *db;
120{
121    Node *head, *p;
122    datum key;
123
124    head = db->dbm_list->list;
125    p = head->next;
126    if (p != head)
127    {
128	key.dptr = p->key;
129	key.dsize = strlen (p->key);
130    }
131    else
132    {
133	key.dptr = (char *) NULL;
134	key.dsize = 0;
135    }
136    db->dbm_next = p->next;
137    return (key);
138}
139
140datum
141mydbm_nextkey (db)
142    DBM *db;
143{
144    Node *head, *p;
145    datum key;
146
147    head = db->dbm_list->list;
148    p = db->dbm_next;
149    if (p != head)
150    {
151	key.dptr = p->key;
152	key.dsize = strlen (p->key);
153    }
154    else
155    {
156	key.dptr = (char *) NULL;
157	key.dsize = 0;
158    }
159    db->dbm_next = p->next;
160    return (key);
161}
162
163/* Note: only updates the in-memory copy, which is written out at
164   mydbm_close time.  Note: Also differs from DBM in that on duplication,
165   it gives a warning, rather than either DBM_INSERT or DBM_REPLACE
166   behavior.  */
167int
168mydbm_store (db, key, value, flags)
169    DBM *db;
170    datum key;
171    datum value;
172    int flags;
173{
174    Node *node;
175
176    node = getnode ();
177    node->type = NDBMNODE;
178
179    node->key = xmalloc (key.dsize + 1);
180    strncpy (node->key, key.dptr, key.dsize);
181    node->key[key.dsize] = '\0';
182
183    node->data = xmalloc (value.dsize + 1);
184    strncpy (node->data, value.dptr, value.dsize);
185    node->data[value.dsize] = '\0';
186
187    db->modified = 1;
188    if (addnode (db->dbm_list, node) == -1)
189    {
190	error (0, 0, "attempt to insert duplicate key `%s'", node->key);
191	freenode (node);
192	return 0;
193    }
194    return 0;
195}
196
197static void
198mydbm_load_file (fp, list)
199    FILE *fp;
200    List *list;
201{
202    char *line = NULL;
203    size_t line_size;
204    char *value;
205    size_t value_allocated;
206    char *cp, *vp;
207    int cont;
208    int line_length;
209
210    value_allocated = 1;
211    value = xmalloc (value_allocated);
212
213    cont = 0;
214    while ((line_length =
215            getstr (&line, &line_size, fp, '\012', 0, GETLINE_NO_LIMIT)) >= 0)
216    {
217	if (line_length > 0 && line[line_length - 1] == '\012')
218	{
219	    /* Strip the newline.  */
220	    --line_length;
221	    line[line_length] = '\0';
222	}
223	if (line_length > 0 && line[line_length - 1] == '\015')
224	{
225	    /* If the file (e.g. modules) was written on an NT box, it will
226	       contain CRLF at the ends of lines.  Strip them (we can't do
227	       this by opening the file in text mode because we might be
228	       running on unix).  */
229	    --line_length;
230	    line[line_length] = '\0';
231	}
232
233	/*
234	 * Add the line to the value, at the end if this is a continuation
235	 * line; otherwise at the beginning, but only after any trailing
236	 * backslash is removed.
237	 */
238	if (!cont)
239	    value[0] = '\0';
240
241	/*
242	 * See if the line we read is a continuation line, and strip the
243	 * backslash if so.
244	 */
245	if (line_length > 0)
246	    cp = &line[line_length - 1];
247	else
248	    cp = line;
249	if (*cp == '\\')
250	{
251	    cont = 1;
252	    *cp = '\0';
253	    --line_length;
254	}
255	else
256	{
257	    cont = 0;
258	}
259	expand_string (&value,
260		       &value_allocated,
261		       strlen (value) + line_length + 5);
262	strcat (value, line);
263
264	if (value[0] == '#')
265	    continue;			/* comment line */
266	vp = value;
267	while (*vp && isspace ((unsigned char) *vp))
268	    vp++;
269	if (*vp == '\0')
270	    continue;			/* empty line */
271
272	/*
273	 * If this was not a continuation line, add the entry to the database
274	 */
275	if (!cont)
276	{
277	    Node *p = getnode ();
278	    char *kp;
279
280	    kp = vp;
281	    while (*vp && !isspace ((unsigned char) *vp))
282		vp++;
283	    *vp++ = '\0';		/* NULL terminate the key */
284	    p->type = NDBMNODE;
285	    p->key = xstrdup (kp);
286	    while (*vp && isspace ((unsigned char) *vp))
287		vp++;			/* skip whitespace to value */
288	    if (*vp == '\0')
289	    {
290		error (0, 0, "warning: NULL value for key `%s'", p->key);
291		freenode (p);
292		continue;
293	    }
294	    p->data = xstrdup (vp);
295	    if (addnode (list, p) == -1)
296	    {
297		error (0, 0, "duplicate key found for `%s'", p->key);
298		freenode (p);
299	    }
300	}
301    }
302    if (line_length < 0 && !feof (fp))
303	/* FIXME: should give the name of the file.  */
304	error (0, errno, "cannot read file in mydbm_load_file");
305
306    free (line);
307    free (value);
308}
309
310#endif				/* MY_NDBM */
311