1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2000,2008 Oracle.  All rights reserved.
5 *
6 * $Id: db_setid.c,v 12.32 2008/01/08 20:58:10 bostic Exp $
7 */
8
9#include "db_config.h"
10
11#include "db_int.h"
12#include "dbinc/db_page.h"
13#include "dbinc/db_swap.h"
14#include "dbinc/db_am.h"
15#include "dbinc/mp.h"
16
17static int __env_fileid_reset __P((ENV *,
18	    DB_THREAD_INFO *, const char *, int));
19
20/*
21 * __env_fileid_reset_pp --
22 *	ENV->fileid_reset pre/post processing.
23 *
24 * PUBLIC: int __env_fileid_reset_pp __P((DB_ENV *, const char *, u_int32_t));
25 */
26int
27__env_fileid_reset_pp(dbenv, name, flags)
28	DB_ENV *dbenv;
29	const char *name;
30	u_int32_t flags;
31{
32	DB_THREAD_INFO *ip;
33	ENV *env;
34	int ret;
35
36	env = dbenv->env;
37
38	ENV_ILLEGAL_BEFORE_OPEN(env, "DB_ENV->fileid_reset");
39
40	/*
41	 * !!!
42	 * The actual argument checking is simple, do it inline, outside of
43	 * the replication block.
44	 */
45	if (flags != 0 && flags != DB_ENCRYPT)
46		return (__db_ferr(env, "DB_ENV->fileid_reset", 0));
47
48	ENV_ENTER(env, ip);
49	REPLICATION_WRAP(env,
50	    (__env_fileid_reset(env, ip, name, LF_ISSET(DB_ENCRYPT) ? 1 : 0)),
51	    1, ret);
52	ENV_LEAVE(env, ip);
53	return (ret);
54}
55
56/*
57 * __env_fileid_reset --
58 *	Reset the file IDs for every database in the file.
59 */
60static int
61__env_fileid_reset(env, ip, name, encrypted)
62	ENV *env;
63	DB_THREAD_INFO *ip;
64	const char *name;
65	int encrypted;
66{
67	DB *dbp;
68	DBC *dbcp;
69	DBT key, data;
70	DB_FH *fhp;
71	DB_MPOOLFILE *mpf;
72	DB_PGINFO cookie;
73	db_pgno_t pgno;
74	int t_ret, ret;
75	size_t n;
76	char *real_name;
77	u_int8_t fileid[DB_FILE_ID_LEN], mbuf[DBMETASIZE];
78	void *pagep;
79
80	dbp = NULL;
81	dbcp = NULL;
82	fhp = NULL;
83	real_name = NULL;
84
85	/* Get the real backing file name. */
86	if ((ret =
87	    __db_appname(env, DB_APP_DATA, name, 0, NULL, &real_name)) != 0)
88		return (ret);
89
90	/* Get a new file ID. */
91	if ((ret = __os_fileid(env, real_name, 1, fileid)) != 0)
92		goto err;
93
94	/*
95	 * The user may have physically copied a file currently open in the
96	 * cache, which means if we open this file through the cache before
97	 * updating the file ID on page 0, we might connect to the file from
98	 * which the copy was made.
99	 */
100	if ((ret = __os_open(env, real_name, 0, 0, 0, &fhp)) != 0) {
101		__db_err(env, ret, "%s", real_name);
102		goto err;
103	}
104	if ((ret = __os_read(env, fhp, mbuf, sizeof(mbuf), &n)) != 0)
105		goto err;
106
107	if (n != sizeof(mbuf)) {
108		ret = EINVAL;
109		__db_errx(env,
110		    "%s: unexpected file type or format", real_name);
111		goto err;
112	}
113
114	/*
115	 * Create the DB object.
116	 */
117	if ((ret = __db_create_internal(&dbp, env, 0)) != 0)
118		goto err;
119
120	/* If configured with a password, the databases are encrypted. */
121	if (encrypted && (ret = __db_set_flags(dbp, DB_ENCRYPT)) != 0)
122		goto err;
123
124	if ((ret = __db_meta_setup(env,
125	    dbp, real_name, (DBMETA *)mbuf, 0, DB_CHK_META)) != 0)
126		goto err;
127	memcpy(((DBMETA *)mbuf)->uid, fileid, DB_FILE_ID_LEN);
128	cookie.db_pagesize = sizeof(mbuf);
129	cookie.flags = dbp->flags;
130	cookie.type = dbp->type;
131	key.data = &cookie;
132
133	if ((ret = __db_pgout(env->dbenv, 0, mbuf, &key)) != 0)
134		goto err;
135	if ((ret = __os_seek(env, fhp, 0, 0, 0)) != 0)
136		goto err;
137	if ((ret = __os_write(env, fhp, mbuf, sizeof(mbuf), &n)) != 0)
138		goto err;
139	if ((ret = __os_fsync(env, fhp)) != 0)
140		goto err;
141
142	/*
143	 * Page 0 of the file has an updated file ID, and we can open it in
144	 * the cache without connecting to a different, existing file.  Open
145	 * the file in the cache, and update the file IDs for subdatabases.
146	 * (No existing code, as far as I know, actually uses the file ID of
147	 * a subdatabase, but it's cleaner to get them all.)
148	 */
149
150	/*
151	 * Open the DB file.
152	 *
153	 * !!!
154	 * Note DB_RDWRMASTER flag, we need to open the master database file
155	 * for writing in this case.
156	 */
157	if ((ret = __db_open(dbp, ip, NULL,
158	    name, NULL, DB_UNKNOWN, DB_RDWRMASTER, 0, PGNO_BASE_MD)) != 0)
159		goto err;
160
161	/*
162	 * If the database file doesn't support subdatabases, we only have
163	 * to update a single metadata page.  Otherwise, we have to open a
164	 * cursor and step through the master database, and update all of
165	 * the subdatabases' metadata pages.
166	 */
167	if (!F_ISSET(dbp, DB_AM_SUBDB))
168		goto err;
169
170	mpf = dbp->mpf;
171	memset(&key, 0, sizeof(key));
172	memset(&data, 0, sizeof(data));
173	if ((ret = __db_cursor(dbp, ip, NULL, &dbcp, 0)) != 0)
174		goto err;
175	while ((ret = __dbc_get(dbcp, &key, &data, DB_NEXT)) == 0) {
176		/*
177		 * XXX
178		 * We're handling actual data, not on-page meta-data, so it
179		 * hasn't been converted to/from opposite endian architectures.
180		 * Do it explicitly, now.
181		 */
182		memcpy(&pgno, data.data, sizeof(db_pgno_t));
183		DB_NTOHL_SWAP(env, &pgno);
184		if ((ret = __memp_fget(mpf, &pgno, ip, NULL,
185		    DB_MPOOL_DIRTY, &pagep)) != 0)
186			goto err;
187		memcpy(((DBMETA *)pagep)->uid, fileid, DB_FILE_ID_LEN);
188		if ((ret = __memp_fput(mpf, ip, pagep, dbcp->priority)) != 0)
189			goto err;
190	}
191	if (ret == DB_NOTFOUND)
192		ret = 0;
193
194err:	if (dbcp != NULL && (t_ret = __dbc_close(dbcp)) != 0 && ret == 0)
195		ret = t_ret;
196	if (dbp != NULL && (t_ret = __db_close(dbp, NULL, 0)) != 0 && ret == 0)
197		ret = t_ret;
198	if (fhp != NULL &&
199	    (t_ret = __os_closehandle(env, fhp)) != 0 && ret == 0)
200		ret = t_ret;
201	if (real_name != NULL)
202		__os_free(env, real_name);
203
204	return (ret);
205}
206