1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 1998,2008 Oracle.  All rights reserved.
5 *
6 * This code is derived from software contributed to Sleepycat Software by
7 * Frederick G.M. Roeber of Netscape Communications Corp.
8 *
9 * $Id: os_vx_map.c,v 12.14 2008/01/30 16:42:21 bostic Exp $
10 */
11
12#include "db_config.h"
13
14#include "db_int.h"
15
16/*
17 * DB uses memory-mapped files for two things:
18 *	faster access of read-only databases, and
19 *	shared memory for process synchronization and locking.
20 * The code carefully does not mix the two uses.  The first-case uses are
21 * actually written such that memory-mapping isn't really required -- it's
22 * merely a convenience -- so we don't have to worry much about it.  In the
23 * second case, it's solely used as a shared memory mechanism, so that's
24 * all we have to replace.
25 *
26 * All memory in VxWorks is shared, and a task can allocate memory and keep
27 * notes.  So I merely have to allocate memory, remember the "filename" for
28 * that memory, and issue small-integer segment IDs which index the list of
29 * these shared-memory segments. Subsequent opens are checked against the
30 * list of already open segments.
31 */
32typedef struct {
33	void *segment;			/* Segment address. */
34	u_int32_t size;			/* Segment size. */
35	char *name;			/* Segment name. */
36	long segid;			/* Segment ID. */
37} os_segdata_t;
38
39static os_segdata_t *__os_segdata;	/* Segment table. */
40static int __os_segdata_size;		/* Segment table size. */
41
42#define	OS_SEGDATA_STARTING_SIZE 16
43#define	OS_SEGDATA_INCREMENT	 16
44
45static int __os_segdata_allocate
46	       __P((ENV *, const char *, REGINFO *, REGION *));
47static int __os_segdata_find_byname
48	       __P((ENV *, const char *, REGINFO *, REGION *));
49static int __os_segdata_init __P((ENV *));
50static int __os_segdata_new __P((ENV *, int *));
51static int __os_segdata_release __P((ENV *, REGION *, int));
52
53/*
54 * __os_attach --
55 *	Create/join a shared memory region.
56 */
57int
58__os_attach(env, infop, rp)
59	ENV *env;
60	REGINFO *infop;
61	REGION *rp;
62{
63	DB_ENV *dbenv;
64	int ret;
65
66	dbenv = env->dbenv;
67
68	if (__os_segdata == NULL)
69		__os_segdata_init(env);
70
71	DB_BEGIN_SINGLE_THREAD;
72
73	/* Try to find an already existing segment. */
74	ret = __os_segdata_find_byname(env, infop->name, infop, rp);
75
76	/*
77	 * If we are trying to join a region, it is easy, either we
78	 * found it and we return, or we didn't find it and we return
79	 * an error that it doesn't exist.
80	 */
81	if (!F_ISSET(infop, REGION_CREATE)) {
82		if (ret != 0) {
83			__db_errx(env, "segment %s does not exist",
84			    infop->name);
85			ret = EAGAIN;
86		}
87		goto out;
88	}
89
90	/*
91	 * If we get here, we are trying to create the region.
92	 * There are several things to consider:
93	 * - if we have an error (not a found or not-found value), return.
94	 * - they better have shm_key set.
95	 * - if the region is already there (ret == 0 from above),
96	 * assume the application crashed and we're restarting.
97	 * Delete the old region.
98	 * - try to create the region.
99	 */
100	if (ret != 0 && ret != ENOENT)
101		goto out;
102
103	if (dbenv->shm_key == INVALID_REGION_SEGID) {
104		__db_errx(env, "no base shared memory ID specified");
105		ret = EAGAIN;
106		goto out;
107	}
108	if (ret == 0 && __os_segdata_release(env, rp, 1) != 0) {
109		__db_errx(env,
110		    "key: %ld: shared memory region already exists",
111		    dbenv->shm_key + (infop->id - 1));
112		ret = EAGAIN;
113		goto out;
114	}
115
116	ret = __os_segdata_allocate(env, infop->name, infop, rp);
117out:
118	DB_END_SINGLE_THREAD;
119	return (ret);
120}
121
122/*
123 * __os_detach --
124 *	Detach from a shared region.
125 */
126int
127__os_detach(env, infop, destroy)
128	ENV *env;
129	REGINFO *infop;
130	int destroy;
131{
132	/*
133	 * If just detaching, there is no mapping to discard.
134	 * If destroying, remove the region.
135	 */
136	if (destroy)
137		return (__os_segdata_release(env, infop->rp, 0));
138	return (0);
139}
140
141/*
142 * __os_mapfile --
143 *	Map in a shared memory file.
144 */
145int
146__os_mapfile(env, path, fhp, len, is_rdonly, addrp)
147	ENV *env;
148	char *path;
149	DB_FH *fhp;
150	int is_rdonly;
151	size_t len;
152	void **addrp;
153{
154	/* We cannot map in regular files in VxWorks. */
155	COMPQUIET(env, NULL);
156	COMPQUIET(path, NULL);
157	COMPQUIET(fhp, NULL);
158	COMPQUIET(is_rdonly, 0);
159	COMPQUIET(len, 0);
160	COMPQUIET(addrp, NULL);
161	return (DB_OPNOTSUP);
162}
163
164/*
165 * __os_unmapfile --
166 *	Unmap the shared memory file.
167 */
168int
169__os_unmapfile(env, addr, len)
170	ENV *env;
171	void *addr;
172	size_t len;
173{
174	/* We cannot map in regular files in VxWorks. */
175	COMPQUIET(env, NULL);
176	COMPQUIET(addr, NULL);
177	COMPQUIET(len, 0);
178	return (DB_OPNOTSUP);
179}
180
181/*
182 * __os_segdata_init --
183 *	Initializes the library's table of shared memory segments.
184 *	Called once on the first time through __os_segdata_new().
185 */
186static int
187__os_segdata_init(env)
188	ENV *env;
189{
190	int ret;
191
192	if (__os_segdata != NULL) {
193		__db_errx(env, "shared memory segment already exists");
194		return (EEXIST);
195	}
196
197	/*
198	 * The lock init call returns a locked lock.
199	 */
200	DB_BEGIN_SINGLE_THREAD;
201	__os_segdata_size = OS_SEGDATA_STARTING_SIZE;
202	ret = __os_calloc(env,
203	    __os_segdata_size, sizeof(os_segdata_t), &__os_segdata);
204	DB_END_SINGLE_THREAD;
205	return (ret);
206}
207
208/*
209 * __os_segdata_destroy --
210 *	Destroys the library's table of shared memory segments.  It also
211 *	frees all linked data: the segments themselves, and their names.
212 *	Currently not called.  This function should be called if the
213 *	user creates a function to unload or shutdown.
214 */
215int
216__os_segdata_destroy(env)
217	ENV *env;
218{
219	os_segdata_t *p;
220	int i;
221
222	if (__os_segdata == NULL)
223		return (0);
224
225	DB_BEGIN_SINGLE_THREAD;
226	for (i = 0; i < __os_segdata_size; i++) {
227		p = &__os_segdata[i];
228		if (p->name != NULL) {
229			__os_free(env, p->name);
230			p->name = NULL;
231		}
232		if (p->segment != NULL) {
233			__os_free(env, p->segment);
234			p->segment = NULL;
235		}
236		p->size = 0;
237	}
238
239	__os_free(env, __os_segdata);
240	__os_segdata = NULL;
241	__os_segdata_size = 0;
242	DB_END_SINGLE_THREAD;
243
244	return (0);
245}
246
247/*
248 * __os_segdata_allocate --
249 *	Creates a new segment of the specified size, optionally with the
250 *	specified name.
251 *
252 * Assumes it is called with the SEGDATA lock taken.
253 */
254static int
255__os_segdata_allocate(env, name, infop, rp)
256	ENV *env;
257	const char *name;
258	REGINFO *infop;
259	REGION *rp;
260{
261	DB_ENV *dbenv;
262	os_segdata_t *p;
263	int id, ret;
264
265	dbenv = env->dbenv;
266
267	if ((ret = __os_segdata_new(env, &id)) != 0)
268		return (ret);
269
270	p = &__os_segdata[id];
271	if ((ret = __os_calloc(env, 1, rp->size, &p->segment)) != 0)
272		return (ret);
273	if ((ret = __os_strdup(env, name, &p->name)) != 0) {
274		__os_free(env, p->segment);
275		p->segment = NULL;
276		return (ret);
277	}
278	p->size = rp->size;
279	p->segid = dbenv->shm_key + infop->id - 1;
280
281	infop->addr = p->segment;
282	rp->segid = id;
283
284	return (0);
285}
286
287/*
288 * __os_segdata_new --
289 *	Finds a new segdata slot.  Does not initialise it, so the fd returned
290 *	is only valid until you call this again.
291 *
292 * Assumes it is called with the SEGDATA lock taken.
293 */
294static int
295__os_segdata_new(env, segidp)
296	ENV *env;
297	int *segidp;
298{
299	os_segdata_t *p;
300	int i, newsize, ret;
301
302	if (__os_segdata == NULL) {
303		__db_errx(env, "shared memory segment not initialized");
304		return (EAGAIN);
305	}
306
307	for (i = 0; i < __os_segdata_size; i++) {
308		p = &__os_segdata[i];
309		if (p->segment == NULL) {
310			*segidp = i;
311			return (0);
312		}
313	}
314
315	/*
316	 * No more free slots, expand.
317	 */
318	newsize = __os_segdata_size + OS_SEGDATA_INCREMENT;
319	if ((ret = __os_realloc(env, newsize * sizeof(os_segdata_t),
320	    &__os_segdata)) != 0)
321		return (ret);
322	memset(&__os_segdata[__os_segdata_size],
323	    0, OS_SEGDATA_INCREMENT * sizeof(os_segdata_t));
324
325	*segidp = __os_segdata_size;
326	__os_segdata_size = newsize;
327
328	return (0);
329}
330
331/*
332 * __os_segdata_find_byname --
333 *	Finds a segment by its name and shm_key.
334 *
335 * Assumes it is called with the SEGDATA lock taken.
336 */
337static int
338__os_segdata_find_byname(env, name, infop, rp)
339	ENV *env;
340	const char *name;
341	REGINFO *infop;
342	REGION *rp;
343{
344	DB_ENV *dbenv;
345	os_segdata_t *p;
346	long segid;
347	int i;
348
349	dbenv = env->dbenv;
350
351	if (__os_segdata == NULL) {
352		__db_errx(env, "shared memory segment not initialized");
353		return (EAGAIN);
354	}
355
356	if (name == NULL) {
357		__db_errx(env, "no segment name given");
358		return (EAGAIN);
359	}
360
361	/*
362	 * If we are creating the region, compute the segid.
363	 * If we are joining the region, we use the segid in the
364	 * index we are given.
365	 */
366	if (F_ISSET(infop, REGION_CREATE))
367		segid = dbenv->shm_key + (infop->id - 1);
368	else {
369		if (rp->segid >= __os_segdata_size ||
370		    rp->segid == INVALID_REGION_SEGID) {
371			__db_errx(env, "Invalid segment id given");
372			return (EAGAIN);
373		}
374		segid = __os_segdata[rp->segid].segid;
375	}
376	for (i = 0; i < __os_segdata_size; i++) {
377		p = &__os_segdata[i];
378		if (p->name != NULL && strcmp(name, p->name) == 0 &&
379		    p->segid == segid) {
380			infop->addr = p->segment;
381			rp->segid = i;
382			return (0);
383		}
384	}
385	return (ENOENT);
386}
387
388/*
389 * __os_segdata_release --
390 *	Free a segdata entry.
391 */
392static int
393__os_segdata_release(env, rp, is_locked)
394	ENV *env;
395	REGION *rp;
396	int is_locked;
397{
398	os_segdata_t *p;
399
400	if (__os_segdata == NULL) {
401		__db_errx(env, "shared memory segment not initialized");
402		return (EAGAIN);
403	}
404
405	if (rp->segid < 0 || rp->segid >= __os_segdata_size) {
406		__db_errx(env, "segment id %ld out of range", rp->segid);
407		return (EINVAL);
408	}
409
410	if (is_locked == 0)
411		DB_BEGIN_SINGLE_THREAD;
412	p = &__os_segdata[rp->segid];
413	if (p->name != NULL) {
414		__os_free(env, p->name);
415		p->name = NULL;
416	}
417	if (p->segment != NULL) {
418		__os_free(env, p->segment);
419		p->segment = NULL;
420	}
421	p->size = 0;
422	if (is_locked == 0)
423		DB_END_SINGLE_THREAD;
424
425	/* Any shrink-table logic could go here */
426
427	return (0);
428}
429