Update from db-2.3.12.
[kopensolaris-gnu/glibc.git] / db2 / mp / mp_fget.c
1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 1996, 1997
5  *      Sleepycat Software.  All rights reserved.
6  */
7 #include "config.h"
8
9 #ifndef lint
10 static const char sccsid[] = "@(#)mp_fget.c     10.30 (Sleepycat) 10/25/97";
11 #endif /* not lint */
12
13 #ifndef NO_SYSTEM_INCLUDES
14 #include <sys/types.h>
15 #include <sys/stat.h>
16
17 #include <errno.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #endif
21
22 #include "db_int.h"
23 #include "shqueue.h"
24 #include "db_shash.h"
25 #include "mp.h"
26 #include "common_ext.h"
27
28 int __sleep_on_every_page_get;          /* XXX: thread debugging option. */
29
30 /*
31  * memp_fget --
32  *      Get a page from the file.
33  */
34 int
35 memp_fget(dbmfp, pgnoaddr, flags, addrp)
36         DB_MPOOLFILE *dbmfp;
37         db_pgno_t *pgnoaddr;
38         int flags;
39         void *addrp;
40 {
41         BH *bhp, *tbhp;
42         DB_MPOOL *dbmp;
43         MPOOL *mp;
44         MPOOLFILE *mfp;
45         db_pgno_t lastpgno;
46         size_t bucket, mf_offset;
47         off_t size;
48         u_long cnt;
49         int b_incr, b_inserted, readonly_alloc, ret;
50         void *addr;
51
52         dbmp = dbmfp->dbmp;
53
54         /*
55          * Validate arguments.
56          *
57          * !!!
58          * Don't test for DB_MPOOL_CREATE and DB_MPOOL_NEW flags for readonly
59          * files here, and create non-existent pages in readonly files if the
60          * flags are set, later.  The reason is that the hash access method
61          * wants to get empty pages that don't really exist in readonly files.
62          * The only alternative is for hash to write the last "bucket" all the
63          * time, which we don't want to do because one of our big goals in life
64          * is to keep database files small.  It's sleazy as hell, but we catch
65          * any attempt to actually write the file in memp_fput().
66          */
67 #define OKFLAGS (DB_MPOOL_CREATE | DB_MPOOL_LAST | DB_MPOOL_NEW)
68         if (flags != 0) {
69                 if ((ret =
70                     __db_fchk(dbmp->dbenv, "memp_fget", flags, OKFLAGS)) != 0)
71                         return (ret);
72
73                 switch (flags) {
74                 case DB_MPOOL_CREATE:
75                 case DB_MPOOL_LAST:
76                 case DB_MPOOL_NEW:
77                 case 0:
78                         break;
79                 default:
80                         return (__db_ferr(dbmp->dbenv, "memp_fget", 1));
81                 }
82         }
83
84 #ifdef DEBUG
85         /*
86          * XXX
87          * We want to switch threads as often as possible.  Sleep every time
88          * we get a new page to make it more likely.
89          */
90         if (__sleep_on_every_page_get &&
91             (__db_yield == NULL || __db_yield() != 0))
92                 __db_sleep(0, 1);
93 #endif
94
95         mp = dbmp->mp;
96         mfp = dbmfp->mfp;
97         mf_offset = R_OFFSET(dbmp, mfp);
98         addr = NULL;
99         bhp = NULL;
100         b_incr = b_inserted = readonly_alloc = ret = 0;
101
102         LOCKREGION(dbmp);
103
104         /*
105          * If mmap'ing the file, just return a pointer.  However, if another
106          * process has opened the file for writing since we mmap'd it, start
107          * playing the game by their rules, i.e. everything goes through the
108          * cache.  All pages previously returned should be safe, as long as
109          * a locking protocol was observed.
110          *
111          * XXX
112          * We don't discard the map because we don't know when all of the
113          * pages will have been discarded from the process' address space.
114          * It would be possible to do so by reference counting the open
115          * pages from the mmap, but it's unclear to me that it's worth it.
116          */
117         if (dbmfp->addr != NULL && dbmfp->mfp->can_mmap) {
118                 lastpgno = dbmfp->len == 0 ?
119                     0 : (dbmfp->len - 1) / mfp->stat.st_pagesize;
120                 if (LF_ISSET(DB_MPOOL_LAST))
121                         *pgnoaddr = lastpgno;
122                 else {
123                         /*
124                          * !!!
125                          * Allocate a page that can never really exist.  See
126                          * the comment above about non-existent pages and the
127                          * hash access method.
128                          */
129                         if (LF_ISSET(DB_MPOOL_CREATE | DB_MPOOL_NEW))
130                                 readonly_alloc = 1;
131                         else if (*pgnoaddr > lastpgno) {
132                                 __db_err(dbmp->dbenv,
133                                     "%s: page %lu doesn't exist",
134                                     dbmfp->path, (u_long)*pgnoaddr);
135                                 ret = EINVAL;
136                                 goto err;
137                         }
138                 }
139                 if (!readonly_alloc) {
140                         addr = R_ADDR(dbmfp, *pgnoaddr * mfp->stat.st_pagesize);
141
142                         ++mp->stat.st_map;
143                         ++mfp->stat.st_map;
144
145                         goto mapret;
146                 }
147         }
148
149         /*
150          * If requesting the last page or a new page, find the last page.  The
151          * tricky thing is that the user may have created a page already that's
152          * after any page that exists in the file.
153          */
154         if (LF_ISSET(DB_MPOOL_LAST | DB_MPOOL_NEW)) {
155                 /*
156                  * Temporary files may not yet have been created.
157                  *
158                  * Don't lock -- there are no atomicity issues for stat(2).
159                  */
160                 if (dbmfp->fd == -1)
161                         size = 0;
162                 else if ((ret =
163                     __db_ioinfo(dbmfp->path, dbmfp->fd, &size, NULL)) != 0) {
164                         __db_err(dbmp->dbenv,
165                             "%s: %s", dbmfp->path, strerror(ret));
166                         goto err;
167                 }
168
169                 *pgnoaddr = size == 0 ? 0 : (size - 1) / mfp->stat.st_pagesize;
170
171                 /*
172                  * Walk the list of BH's, looking for later pages.  Save the
173                  * pointer if a later page is found so that we don't have to
174                  * search the list twice.
175                  *
176                  * If requesting a new page, return the page one after the last
177                  * page -- which we'll have to create.
178                  */
179                 for (tbhp = SH_TAILQ_FIRST(&mp->bhq, __bh);
180                     tbhp != NULL; tbhp = SH_TAILQ_NEXT(tbhp, q, __bh))
181                         if (tbhp->pgno >= *pgnoaddr &&
182                             tbhp->mf_offset == mf_offset) {
183                                 bhp = tbhp;
184                                 *pgnoaddr = bhp->pgno;
185                         }
186                 if (LF_ISSET(DB_MPOOL_NEW))
187                         ++*pgnoaddr;
188         }
189
190         /* If we already found the right buffer, return it. */
191         if (LF_ISSET(DB_MPOOL_LAST) && bhp != NULL) {
192                 addr = bhp->buf;
193                 goto found;
194         }
195
196         /* If we haven't checked the BH hash bucket queue, do the search. */
197         if (!LF_ISSET(DB_MPOOL_LAST | DB_MPOOL_NEW)) {
198                 bucket = BUCKET(mp, mf_offset, *pgnoaddr);
199                 for (cnt = 0,
200                     bhp = SH_TAILQ_FIRST(&dbmp->htab[bucket], __bh);
201                     bhp != NULL; bhp = SH_TAILQ_NEXT(bhp, hq, __bh)) {
202                         ++cnt;
203                         if (bhp->pgno == *pgnoaddr &&
204                             bhp->mf_offset == mf_offset) {
205                                 addr = bhp->buf;
206                                 ++mp->stat.st_hash_searches;
207                                 if (cnt > mp->stat.st_hash_longest)
208                                         mp->stat.st_hash_longest = cnt;
209                                 mp->stat.st_hash_examined += cnt;
210                                 goto found;
211                         }
212                 }
213                 if (cnt != 0) {
214                         ++mp->stat.st_hash_searches;
215                         if (cnt > mp->stat.st_hash_longest)
216                                 mp->stat.st_hash_longest = cnt;
217                         mp->stat.st_hash_examined += cnt;
218                 }
219         }
220
221         /*
222          * Allocate a new buffer header and data space, and mark the contents
223          * as useless.
224          */
225         if ((ret = __memp_ralloc(dbmp, sizeof(BH) -
226             sizeof(u_int8_t) + mfp->stat.st_pagesize, NULL, &bhp)) != 0)
227                 goto err;
228         addr = bhp->buf;
229 #ifdef DEBUG
230         if ((ALIGNTYPE)addr & (sizeof(size_t) - 1)) {
231                 __db_err(dbmp->dbenv,
232                     "Internal error: BH data NOT size_t aligned.");
233                 abort();
234         }
235 #endif
236         memset(bhp, 0, sizeof(BH));
237         LOCKINIT(dbmp, &bhp->mutex);
238
239         /*
240          * Prepend the bucket header to the head of the appropriate MPOOL
241          * bucket hash list.  Append the bucket header to the tail of the
242          * MPOOL LRU chain.
243          *
244          * We have to do this before we read in the page so we can discard
245          * our region lock without screwing up the world.
246          */
247         bucket = BUCKET(mp, mf_offset, *pgnoaddr);
248         SH_TAILQ_INSERT_HEAD(&dbmp->htab[bucket], bhp, hq, __bh);
249         SH_TAILQ_INSERT_TAIL(&mp->bhq, bhp, q);
250         ++mp->stat.st_page_clean;
251         b_inserted = 1;
252
253         /* Set the page number, and associated MPOOLFILE. */
254         bhp->mf_offset = mf_offset;
255         bhp->pgno = *pgnoaddr;
256
257         /*
258          * If we know we created the page, zero it out and continue.
259          *
260          * !!!
261          * Note: DB_MPOOL_NEW deliberately doesn't call the pgin function.
262          * If DB_MPOOL_CREATE is used, then the application's pgin function
263          * has to be able to handle pages of 0's -- if it uses DB_MPOOL_NEW,
264          * it can detect all of its page creates, and not bother.
265          *
266          * Otherwise, read the page into memory, optionally creating it if
267          * DB_MPOOL_CREATE is set.
268          *
269          * Increment the reference count for created buffers, but importantly,
270          * increment the reference count for buffers we're about to read so
271          * that the buffer can't move.
272          */
273         ++bhp->ref;
274         b_incr = 1;
275
276         if (LF_ISSET(DB_MPOOL_NEW))
277                 memset(addr, 0, mfp->stat.st_pagesize);
278         else {
279                 /*
280                  * It's possible for the read function to fail, which means
281                  * that we fail as well.
282                  */
283 reread:         if ((ret = __memp_pgread(dbmfp,
284                     bhp, LF_ISSET(DB_MPOOL_CREATE | DB_MPOOL_NEW))) != 0)
285                         goto err;
286
287                 /*
288                  * !!!
289                  * The __memp_pgread call discarded and reacquired the region
290                  * lock.  Because the buffer reference count was incremented
291                  * before the region lock was discarded the buffer can't move
292                  * and its contents can't change.
293                  */
294                 ++mp->stat.st_cache_miss;
295                 ++mfp->stat.st_cache_miss;
296         }
297
298         if (0) {
299 found:          /* Increment the reference count. */
300                 if (bhp->ref == UINT16_T_MAX) {
301                         __db_err(dbmp->dbenv,
302                             "%s: too many references to page %lu",
303                             dbmfp->path, bhp->pgno);
304                         ret = EINVAL;
305                         goto err;
306                 }
307                 ++bhp->ref;
308                 b_incr = 1;
309
310                 /*
311                  * Any found buffer might be trouble.
312                  *
313                  * BH_LOCKED --
314                  * I/O in progress, wait for it to finish.  Because the buffer
315                  * reference count was incremented before the region lock was
316                  * discarded we know the buffer can't move and its contents
317                  * can't change.
318                  */
319                 if (F_ISSET(bhp, BH_LOCKED)) {
320                         UNLOCKREGION(dbmp);
321                         LOCKBUFFER(dbmp, bhp);
322                         /* Waiting for I/O to finish... */
323                         UNLOCKBUFFER(dbmp, bhp);
324                         LOCKREGION(dbmp);
325                 }
326
327                 /*
328                  * BH_TRASH --
329                  * The buffer is garbage.
330                  */
331                 if (F_ISSET(bhp, BH_TRASH))
332                         goto reread;
333
334                 /*
335                  * BH_CALLPGIN --
336                  * The buffer was written, and the contents need to be
337                  * converted again.
338                  */
339                 if (F_ISSET(bhp, BH_CALLPGIN)) {
340                         if ((ret = __memp_pg(dbmfp, bhp, 1)) != 0)
341                                 goto err;
342                         F_CLR(bhp, BH_CALLPGIN);
343                 }
344
345                 ++mp->stat.st_cache_hit;
346                 ++mfp->stat.st_cache_hit;
347         }
348
349 mapret: LOCKHANDLE(dbmp, dbmfp->mutexp);
350         ++dbmfp->pinref;
351         UNLOCKHANDLE(dbmp, dbmfp->mutexp);
352
353         if (0) {
354 err:            /*
355                  * If no other process is already waiting on a created buffer,
356                  * go ahead and discard it, it's not useful.
357                  */
358                 if (b_incr)
359                         --bhp->ref;
360                 if (b_inserted && bhp->ref == 0)
361                         __memp_bhfree(dbmp, mfp, bhp, 1);
362         }
363
364         UNLOCKREGION(dbmp);
365
366         *(void **)addrp = addr;
367         return (ret);
368 }