1 /*
2    Copyright (c) 1991-2005 Thomas T. Wetmore IV
3    "The MIT license"
4    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
7 */
8 
9 #include "llstdlib.h"
10 #include "gedcom.h"
11 #include "gedcomi.h"
12 #include "vtable.h"
13 #include "cache.h"
14 #include "lloptions.h"
15 
16 
17 /*==============================
18  * RECORD -- in-memory representation of GEDCOM record
19  *============================*/
20 struct tag_record { /* RECORD */
21 	/* a LIST is an OBJECT */
22 	struct tag_vtable * vtable; /* generic object table (see vtable.h) */
23 	INT refcnt; /* reference counted object */
24 	NODE rec_top;           /* for non-cache records */
25 	NKEY rec_nkey;
26 	CACHEEL rec_cel; /* cache wrapper */
27 };
28 
29 /*********************************************
30  * local function prototypes
31  *********************************************/
32 
33 static RECORD alloc_new_record(void);
34 static void free_rec(RECORD rec);
35 static BOOLEAN is_record_loaded (RECORD rec);
36 static void record_destructor(VTABLE *obj);
37 
38 /*********************************************
39  * local variables
40  *********************************************/
41 
42 static struct tag_vtable vtable_for_record = {
43 	VTABLE_MAGIC
44 	, "record"
45 	, &record_destructor
46 	, &refcountable_isref
47 	, &refcountable_addref
48 	, &refcountable_release
49 	, 0 /* copy_fnc */
50 	, &generic_get_type_name
51 };
52 static int f_nrecs=0;
53 
54 /*===================================
55  * alloc_new_record -- record allocator
56  *  perhaps should use special allocator like nodes
57  * returns addref'd record
58  *=================================*/
59 static RECORD
alloc_new_record(void)60 alloc_new_record (void)
61 {
62 	RECORD rec;
63 	++f_nrecs;
64 	rec = (RECORD)stdalloc(sizeof(*rec));
65 	memset(rec, 0, sizeof(*rec));
66 	/* these must be filled in by caller */
67 	rec->vtable = &vtable_for_record;
68 	strcpy(rec->rec_nkey.key, "");
69 	rec->rec_nkey.keynum = 0;
70 	rec->rec_nkey.ntype = 0;
71 	++rec->refcnt;
72 	return rec;
73 }
74 /*===================================
75  * free_rec -- record deallocator
76  *  Works for both free records (not in cache, have own node tree)
77  *  and bound records (in cache, point to cache element)
78  * Created: 2000/12/30, Perry Rapp
79  *=================================*/
80 static void
free_rec(RECORD rec)81 free_rec (RECORD rec)
82 {
83 	--f_nrecs;
84 	if (rec->rec_cel) {
85 		/* cached record */
86 		/* cel memory belongs to cache, but we must tell it
87 		that we're dying, so it doesn't point to us anymore */
88 		cel_remove_record(rec->rec_cel, rec);
89 		rec->rec_cel = 0; /* cel memory belongs to cache */
90 	}
91 	if (rec->rec_top) {
92 		/* free node tree */
93 		free_nodes(rec->rec_top);
94 		rec->rec_top = 0;
95 	}
96 	strcpy(rec->rec_nkey.key, "");
97 	stdfree(rec);
98 }
99 /*==============================================
100  * nztop -- Return first NODE of a RECORD
101  *  handle NULL input
102  *============================================*/
103 NODE
nztop(RECORD rec)104 nztop (RECORD rec)
105 {
106 	if (!rec) return 0;
107 	if (rec->rec_top) {
108 		/* This is only for records not in the cache */
109 		ASSERT(!rec->rec_cel);
110 		return rec->rec_top;
111 	}
112 	if (!is_record_loaded(rec)) {
113 		/* Presumably we're out-of-date because our record fell out of cache */
114 		/* Anyway, load via cache (actually just point to cache data) */
115 		CACHEEL cel = key_to_unknown_cacheel(rec->rec_nkey.key);
116 		rec->rec_cel = cel;
117 	}
118 	return is_cel_loaded(rec->rec_cel);
119 }
120 /*==============================================
121  * nzkey -- Return key of record
122  *  handle NULL input
123  * eg, "I85" for a person I85
124  *============================================*/
125 CNSTRING
nzkey(RECORD rec)126 nzkey (RECORD rec)
127 {
128 	if (!rec) return 0;
129 	return rec->rec_nkey.key;
130 }
131 /*==============================================
132  * nzkeynum -- Return record number of record
133  *  handle NULL input
134  * eg, 85 for a person I85
135  *============================================*/
136 INT
nzkeynum(RECORD rec)137 nzkeynum (RECORD rec)
138 {
139 	if (!rec) return 0;
140 	return rec->rec_nkey.keynum;
141 }
142 /*==============================================
143  * nztype -- Return type number (char) of record
144  *  handle NULL input
145  * eg, 'I' for a person I85
146  *============================================*/
147 char
nztype(RECORD rec)148 nztype (RECORD rec)
149 {
150 	if (!rec) return 0;
151 	return rec->rec_nkey.ntype;
152 }
153 /*==============================================
154  * nzcel -- Return cache element associated with record
155  *  handle NULL input
156  * does not load cache if not associated
157  *============================================*/
158 CACHEEL
nzcel(RECORD rec)159 nzcel (RECORD rec)
160 {
161 	if (!rec) return 0;
162 	return rec->rec_cel;
163 }
164 /*==============================================
165  * record_set_cel -- Assign cache element to record
166  *  both inputs must be valid (non-null)
167  *============================================*/
168 void
record_set_cel(RECORD rec,CACHEEL cel)169 record_set_cel (RECORD rec, CACHEEL cel)
170 {
171 	ASSERT(rec);
172 	ASSERT(cel);
173 	rec->rec_top = 0;
174 	rec->rec_cel = cel;
175 }
176 /*==============================================
177  * record_remove_cel -- cache element is being cleared
178  *  both inputs must be valid (non-null)
179  *============================================*/
180 void
record_remove_cel(RECORD rec,CACHEEL cel)181 record_remove_cel (RECORD rec, CACHEEL cel)
182 {
183 	ASSERT(rec);
184 	ASSERT(cel);
185 	ASSERT(rec->rec_cel == cel);
186 	rec->rec_cel = 0;
187 }
188 /*===================================
189  * make_new_record_with_info -- creates new record
190  *  with key and cacheel set
191  * returns addref'd record
192  *=================================*/
193 RECORD
make_new_record_with_info(CNSTRING key,CACHEEL cel)194 make_new_record_with_info (CNSTRING key, CACHEEL cel)
195 {
196 	RECORD rec = alloc_new_record();
197 	set_record_key_info(rec, key);
198 	record_set_cel(rec, cel);
199 	return rec;
200 }
201 /*===================================
202  * set_record_key_info -- put key info into record
203  * Also set the xref of the root node correctly
204  *=================================*/
205 void
set_record_key_info(RECORD rec,CNSTRING key)206 set_record_key_info (RECORD rec, CNSTRING key)
207 {
208 	char xref[12];
209 	NODE node=0;
210 	INT keynum = atoi(key+1);
211 	char ntype = key[0];
212 	sprintf(xref, "@%s@", key);
213 	strcpy(rec->rec_nkey.key, key);
214 	rec->rec_nkey.keynum = keynum;
215 	rec->rec_nkey.ntype = ntype;
216 	if ((node = rec->rec_top) != 0) {
217 		if (!nxref(node) || !eqstr(nxref(node), xref)) {
218 			if (nxref(node)) stdfree(nxref(node));
219 			nxref(node) = strsave(xref);
220 		}
221 	}
222 }
223 /*==============================================
224  * is_record_loaded -- Check if record has its node tree
225  *============================================*/
226 static BOOLEAN
is_record_loaded(RECORD rec)227 is_record_loaded (RECORD rec)
228 {
229 	STRING xref=0, temp=0;
230 	NODE root=0;
231 
232 		/* does the record have a nodetree ? */
233 	if (!rec || !rec->rec_cel)
234 		return FALSE;
235 	root = is_cel_loaded(rec->rec_cel);
236 	if (!root)
237 		return FALSE;
238 
239 		/* Now, is it the correct nodetree ? */
240 
241 	temp = rec->rec_nkey.key; /* eg, "I50" */
242 
243 	xref = nxref(root); /* eg, "@I50@" */
244 	ASSERT(xref[0] == '@');
245 
246 	/* now xref="@I50@" and temp="I50" */
247 	++xref;
248 
249 	/* now xref="I50@" and temp="I50" */
250 
251 	while (1) {
252 			/* distinguish I50@ vs I50 from I50@ vs I500 */
253 		if (*xref == '@') return (*temp == 0);
254 			/* distinguish I50@ vs I50 from I500@ vs I50 */
255 		if (*temp == 0) return (*xref == '@');
256 		if (*xref != *temp) return FALSE;
257 		++xref;
258 		++temp;
259 	}
260 }
261 /*==============================================
262  * is_record_temp -- Return node tree root if
263  *  this is a temporary record (one not in cache)
264  *============================================*/
265 NODE
is_record_temp(RECORD rec)266 is_record_temp (RECORD rec)
267 {
268 	if (!rec) return NULL;
269 	return rec->rec_top;
270 }
271 /*===================================
272  * init_new_record -- put key info into record
273  *  of brand new record
274  * Created: 2001/02/04, Perry Rapp
275  *=================================*/
276 void
init_new_record(RECORD rec,CNSTRING key)277 init_new_record (RECORD rec, CNSTRING key)
278 {
279 	set_record_key_info(rec, key);
280 }
281 /*===================================
282  * create_record_for_keyed_node --
283  *  Given a node just read from disk, wrap it in an uncached record
284  * returns addref'd record
285  *=================================*/
286 RECORD
create_record_for_keyed_node(NODE node,CNSTRING key)287 create_record_for_keyed_node (NODE node, CNSTRING key)
288 {
289 	RECORD rec = alloc_new_record();
290 	if (!key)
291 		key = nxref(node);
292 	rec->rec_top = node;
293 	init_new_record(rec, key);
294 	return rec;
295 }
296 /*===================================
297  * create_record_for_unkeyed_node --
298  *  Given a new node tree with no key, wrap it in an uncached record
299  * returns addref'd record
300  *=================================*/
301 RECORD
create_record_for_unkeyed_node(NODE node)302 create_record_for_unkeyed_node (NODE node)
303 {
304 	RECORD rec = alloc_new_record();
305 	rec->rec_top = node;
306 	return rec;
307 }
308 /*=================================================
309  * addref_record -- increment reference count of record
310  *===============================================*/
311 void
addref_record(RECORD rec)312 addref_record (RECORD rec)
313 {
314 	if (!rec) return;
315 	ASSERT(rec->vtable == &vtable_for_record);
316 	++rec->refcnt;
317 }
318 /*=================================================
319  * release_record -- decrement reference count of record
320  *  and free if appropriate (ref count hits zero)
321  *===============================================*/
322 void
release_record(RECORD rec)323 release_record (RECORD rec)
324 {
325 	if (!rec) return;
326 	ASSERT(rec->vtable == &vtable_for_record);
327 	--rec->refcnt;
328 	if (!rec->refcnt) {
329 		free_rec(rec);
330 	}
331 }
332 /*=================================================
333  * record_destructor -- destructor for record
334  *  (destructor entry in vtable)
335  *===============================================*/
336 static void
record_destructor(VTABLE * obj)337 record_destructor (VTABLE *obj)
338 {
339 	RECORD rec = (RECORD)obj;
340 	ASSERT((*obj) == &vtable_for_record);
341 	free_rec(rec);
342 }
343 /*=================================================
344  * check_record_leaks -- Called when database closing
345  *  for debugging
346  *===============================================*/
347 void
check_record_leaks(void)348 check_record_leaks (void)
349 {
350 	if (f_nrecs) {
351 		STRING report_leak_path = getlloptstr("ReportLeakLog", NULL);
352 		FILE * fp=0;
353 		if (report_leak_path)
354 			fp = fopen(report_leak_path, LLAPPENDTEXT);
355 		if (fp) {
356 			LLDATE date;
357 			get_current_lldate(&date);
358 			fprintf(fp, _("Record memory leaks:"));
359 			fprintf(fp, " %s", date.datestr);
360 			fprintf(fp, "\n  ");
361 			fprintf(fp, _pl("%d item leaked", "%d items leaked", f_nrecs), f_nrecs);
362 			fprintf(fp, "\n");
363 			fclose(fp);
364 			fp = 0;
365 		}
366 	}
367 }
368