Update to db 2.3.10.
[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.25 (Sleepycat) 9/23/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 && (dbmp->dbenv == NULL ||
91             dbmp->dbenv->db_yield == NULL || dbmp->dbenv->db_yield() != 0))
92                 __db_sleep(0, 1);
93 #endif
94
95         mp = dbmp->mp;
96         mfp = dbmfp->mfp;
97         mf_offset = 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 = 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 = __db_stat(dbmp->dbenv,
163                     dbmfp->path, dbmfp->fd, &size, NULL)) != 0)
164                         goto err;
165
166                 *pgnoaddr = size == 0 ? 0 : (size - 1) / mfp->stat.st_pagesize;
167
168                 /*
169                  * Walk the list of BH's, looking for later pages.  Save the
170                  * pointer if a later page is found so that we don't have to
171                  * search the list twice.
172                  *
173                  * If requesting a new page, return the page one after the last
174                  * page -- which we'll have to create.
175                  */
176                 for (tbhp = SH_TAILQ_FIRST(&mp->bhq, __bh);
177                     tbhp != NULL; tbhp = SH_TAILQ_NEXT(tbhp, q, __bh))
178                         if (tbhp->pgno >= *pgnoaddr &&
179                             tbhp->mf_offset == mf_offset) {
180                                 bhp = tbhp;
181                                 *pgnoaddr = bhp->pgno;
182                         }
183                 if (LF_ISSET(DB_MPOOL_NEW))
184                         ++*pgnoaddr;
185         }
186
187         /* If we already found the right buffer, return it. */
188         if (LF_ISSET(DB_MPOOL_LAST) && bhp != NULL) {
189                 addr = bhp->buf;
190                 goto found;
191         }
192
193         /* If we haven't checked the BH list yet, do the search. */
194         if (!LF_ISSET(DB_MPOOL_LAST | DB_MPOOL_NEW)) {
195                 ++mp->stat.st_hash_searches;
196                 bucket = BUCKET(mp, mf_offset, *pgnoaddr);
197                 for (cnt = 0,
198                     bhp = SH_TAILQ_FIRST(&dbmp->htab[bucket], __bh);
199                     bhp != NULL; bhp = SH_TAILQ_NEXT(bhp, mq, __bh)) {
200                         ++cnt;
201                         if (bhp->pgno == *pgnoaddr &&
202                             bhp->mf_offset == mf_offset) {
203                                 addr = bhp->buf;
204                                 if (cnt > mp->stat.st_hash_longest)
205                                         mp->stat.st_hash_longest = cnt;
206                                 mp->stat.st_hash_examined += cnt;
207                                 goto found;
208                         }
209                 }
210                 if (cnt > mp->stat.st_hash_longest)
211                         mp->stat.st_hash_longest = cnt;
212                 mp->stat.st_hash_examined += cnt;
213         }
214
215         /*
216          * Allocate a new buffer header and data space, and mark the contents
217          * as useless.
218          */
219         if ((ret = __memp_ralloc(dbmp, sizeof(BH) -
220             sizeof(u_int8_t) + mfp->stat.st_pagesize, NULL, &bhp)) != 0)
221                 goto err;
222         addr = bhp->buf;
223 #ifdef DEBUG
224         if ((ALIGNTYPE)addr & (sizeof(size_t) - 1)) {
225                 __db_err(dbmp->dbenv,
226                     "Internal error: BH data NOT size_t aligned.");
227                 abort();
228         }
229 #endif
230         memset(bhp, 0, sizeof(BH));
231         LOCKINIT(dbmp, &bhp->mutex);
232
233         /*
234          * Prepend the bucket header to the head of the appropriate MPOOL
235          * bucket hash list.  Append the bucket header to the tail of the
236          * MPOOL LRU chain.
237          *
238          * We have to do this before we read in the page so we can discard
239          * our region lock without screwing up the world.
240          */
241         bucket = BUCKET(mp, mf_offset, *pgnoaddr);
242         SH_TAILQ_INSERT_HEAD(&dbmp->htab[bucket], bhp, mq, __bh);
243         SH_TAILQ_INSERT_TAIL(&mp->bhq, bhp, q);
244         b_inserted = 1;
245
246         /* Set the page number, and associated MPOOLFILE. */
247         bhp->mf_offset = mf_offset;
248         bhp->pgno = *pgnoaddr;
249
250         /*
251          * If we know we created the page, zero it out and continue.
252          *
253          * !!!
254          * Note: DB_MPOOL_NEW deliberately doesn't call the pgin function.
255          * If DB_MPOOL_CREATE is used, then the application's pgin function
256          * has to be able to handle pages of 0's -- if it uses DB_MPOOL_NEW,
257          * it can detect all of its page creates, and not bother.
258          *
259          * Otherwise, read the page into memory, optionally creating it if
260          * DB_MPOOL_CREATE is set.
261          *
262          * Increment the reference count for created buffers, but importantly,
263          * increment the reference count for buffers we're about to read so
264          * that the buffer can't move.
265          */
266         ++bhp->ref;
267         b_incr = 1;
268
269         if (LF_ISSET(DB_MPOOL_NEW))
270                 memset(addr, 0, mfp->stat.st_pagesize);
271         else {
272                 /*
273                  * It's possible for the read function to fail, which means
274                  * that we fail as well.
275                  */
276 reread:         if ((ret = __memp_pgread(dbmfp,
277                     bhp, LF_ISSET(DB_MPOOL_CREATE | DB_MPOOL_NEW))) != 0)
278                         goto err;
279
280                 /*
281                  * !!!
282                  * The __memp_pgread call discarded and reacquired the region
283                  * lock.  Because the buffer reference count was incremented
284                  * before the region lock was discarded the buffer didn't move.
285                  */
286                 ++mp->stat.st_cache_miss;
287                 ++mfp->stat.st_cache_miss;
288         }
289
290         if (0) {
291 found:          /* Increment the reference count. */
292                 if (bhp->ref == UINT16_T_MAX) {
293                         __db_err(dbmp->dbenv,
294                             "%s: too many references to page %lu",
295                             dbmfp->path, bhp->pgno);
296                         ret = EINVAL;
297                         goto err;
298                 }
299                 ++bhp->ref;
300                 b_incr = 1;
301
302                 /*
303                  * Any found buffer might be trouble.
304                  *
305                  * BH_LOCKED --
306                  * I/O in progress, wait for it to finish.  Because the buffer
307                  * reference count was incremented before the region lock was
308                  * discarded we know the buffer didn't move.
309                  */
310                 if (F_ISSET(bhp, BH_LOCKED)) {
311                         UNLOCKREGION(dbmp);
312                         LOCKBUFFER(dbmp, bhp);
313                         /* Waiting for I/O to finish... */
314                         UNLOCKBUFFER(dbmp, bhp);
315                         LOCKREGION(dbmp);
316                 }
317
318                 /*
319                  * BH_TRASH --
320                  * The buffer is garbage.
321                  */
322                 if (F_ISSET(bhp, BH_TRASH))
323                         goto reread;
324
325                 /*
326                  * BH_CALLPGIN --
327                  * The buffer was written, and the contents need to be
328                  * converted again.
329                  */
330                 if (F_ISSET(bhp, BH_CALLPGIN)) {
331                         if ((ret = __memp_pg(dbmfp, bhp, 1)) != 0)
332                                 goto err;
333                         F_CLR(bhp, BH_CALLPGIN);
334                 }
335
336                 ++mp->stat.st_cache_hit;
337                 ++mfp->stat.st_cache_hit;
338         }
339
340 mapret: LOCKHANDLE(dbmp, dbmfp->mutexp);
341         ++dbmfp->pinref;
342         UNLOCKHANDLE(dbmp, dbmfp->mutexp);
343
344         if (0) {
345 err:            /*
346                  * If no other process is already waiting on a created buffer,
347                  * go ahead and discard it, it's not useful.
348                  */
349                 if (b_incr)
350                         --bhp->ref;
351                 if (b_inserted && bhp->ref == 0)
352                         __memp_bhfree(dbmp, mfp, bhp, 1);
353         }
354
355         UNLOCKREGION(dbmp);
356
357         *(void **)addrp = addr;
358         return (ret);
359 }