1 /*-
2  * Copyright (c) 2013, 2020 Oracle and/or its affiliates.  All rights reserved.
3  *
4  * See the file LICENSE for license information.
5  */
6 
7 #include "db_config.h"
8 
9 #include "db_int.h"
10 #include "dbinc/db_page.h"
11 #include "dbinc/db_am.h"
12 #include "dbinc/fop.h"
13 
14 static int __db_stream_close __P((DB_STREAM *, u_int32_t));
15 static int __db_stream_read
16     __P((DB_STREAM *, DBT *, db_off_t, u_int32_t, u_int32_t));
17 static int __db_stream_size __P((DB_STREAM *, db_off_t *, u_int32_t));
18 static int __db_stream_write __P((DB_STREAM *, DBT *, db_off_t, u_int32_t));
19 
20 /*
21  * __db_stream_init
22  *	DB_STREAM initializer.
23  *
24  * PUBLIC: int __db_stream_init __P((DBC *, DB_STREAM **, u_int32_t));
25  */
26 int
__db_stream_init(dbc,dbsp,flags)27 __db_stream_init(dbc, dbsp, flags)
28 	DBC *dbc;
29 	DB_STREAM **dbsp;
30 	u_int32_t flags;
31 {
32 	DB_STREAM *dbs;
33 	DB_THREAD_INFO *ip;
34 	ENV *env;
35 	int ret;
36 	off_t size;
37 
38 	dbs = NULL;
39 	env = dbc->env;
40 
41 	if ((ret = __os_malloc(env, sizeof(DB_STREAM), &dbs)) != 0)
42 		return (ret);
43 	memset(dbs, 0, sizeof(DB_STREAM));
44 
45 	ENV_ENTER(env, ip);
46 	dbc->thread_info = ip;
47 	/* Should the copy be transient? */
48 	if ((ret = __dbc_idup(dbc, &dbs->dbc, DB_POSITION)) != 0)
49 		goto err;
50 	dbs->flags = flags;
51 
52 	/*
53 	 * Make sure we have a write lock on the db record if writing
54 	 * to the blob.
55 	 */
56 	if (F_ISSET(dbs, DB_FOP_WRITE))
57 		F_SET(dbc, DBC_RMW);
58 
59 	if ((ret = __dbc_get_blob_id(dbs->dbc, &dbs->blob_id)) != 0) {
60 		if (ret == EINVAL)
61 			__db_errx(env, DB_STR("0211",
62 		    "Error, cursor does not point to an external file."));
63 		goto err;
64 	}
65 
66 	if ((ret = __dbc_get_blob_size(dbs->dbc, &size)) != 0)
67 		goto err;
68 	dbs->file_size = size;
69 
70 	if ((ret = __blob_file_open(
71 	    dbs->dbc->dbp, &dbs->fhp, dbs->blob_id, flags, 1)) != 0)
72 		goto err;
73 	ENV_LEAVE(env, ip);
74 
75 	dbs->close = __db_stream_close;
76 	dbs->read = __db_stream_read;
77 	dbs->size = __db_stream_size;
78 	dbs->write = __db_stream_write;
79 
80 	*dbsp = dbs;
81 	return (0);
82 
83 err:	if (dbs != NULL && dbs->dbc != NULL)
84 		(void)__dbc_close(dbs->dbc);
85 	ENV_LEAVE(env, ip);
86 	if (dbs != NULL)
87 		__os_free(env, dbs);
88 	return (ret);
89 }
90 
91 /*
92  * __db_stream_close --
93  *
94  * DB_STREAM->close
95  */
96 static int
__db_stream_close(dbs,flags)97 __db_stream_close(dbs, flags)
98 	DB_STREAM *dbs;
99 	u_int32_t flags;
100 {
101 	DB_THREAD_INFO *ip;
102 	ENV *env;
103 	int ret;
104 
105 	env = dbs->dbc->env;
106 
107 	if ((ret = __db_fchk(env, "DB_STREAM->close", flags, 0)) != 0)
108 		return (ret);
109 
110 	ENV_ENTER(env, ip);
111 	dbs->dbc->thread_info = ip;
112 
113 	ret = __db_stream_close_int(dbs);
114 
115 	ENV_LEAVE(env, ip);
116 
117 	return (ret);
118 }
119 
120 /*
121  * __db_stream_close_int --
122  *	Close a DB_STREAM object.
123  *
124  * PUBLIC: int __db_stream_close_int __P ((DB_STREAM *));
125  */
126 int
__db_stream_close_int(dbs)127 __db_stream_close_int(dbs)
128 	DB_STREAM *dbs;
129 {
130 	DBC *dbc;
131 	ENV *env;
132 	int ret, t_ret;
133 
134 	dbc = dbs->dbc;
135 	env = dbc->env;
136 
137 	ret = __blob_file_close(dbc, dbs->fhp, dbs->flags);
138 
139 	if ((t_ret = __dbc_close(dbs->dbc)) != 0 && ret == 0)
140 		ret = t_ret;
141 
142 	__os_free(env, dbs);
143 
144 	return (ret);
145 }
146 
147 /*
148  * __db_stream_read --
149  *
150  * DB_STREAM->read
151  */
152 static int
__db_stream_read(dbs,data,offset,size,flags)153 __db_stream_read(dbs, data, offset, size, flags)
154 	DB_STREAM *dbs;
155 	DBT *data;
156 	db_off_t offset;
157 	u_int32_t size;
158 	u_int32_t flags;
159 {
160 	DBC *dbc;
161 	ENV *env;
162 	int ret;
163 	u_int32_t needed, start;
164 
165 	dbc = dbs->dbc;
166 	env = dbc->dbp->env;
167 	ret = 0;
168 
169 	if ((ret = __db_fchk(env, "DB_STREAM->read", flags, 0)) != 0)
170 		return (ret);
171 
172 	if (F_ISSET(data, DB_DBT_PARTIAL)) {
173 		ret = USR_ERR(env, EINVAL);
174 		__db_errx(env, DB_STR("0212",
175 		    "Error, do not use DB_DBT_PARTIAL with DB_STREAM."));
176 		goto err;
177 	}
178 
179 	if (offset > dbs->file_size) {
180 		data->size = 0;
181 		goto err;
182 	}
183 
184 	if ((ret = __db_alloc_dbt(
185 	    env, data, size, &needed, &start, NULL, NULL)) != 0)
186 		goto err;
187 	data->size = needed;
188 
189 	if (needed == 0)
190 		goto err;
191 
192 	ret = __blob_file_read(env, dbs->fhp, data, offset, size);
193 
194 err:	return (ret);
195 }
196 
197 /*
198  * __db_stream_size --
199  *
200  * DB_STREAM->size
201  */
202 static int
__db_stream_size(dbs,size,flags)203 __db_stream_size(dbs, size, flags)
204 	DB_STREAM *dbs;
205 	db_off_t *size;
206 	u_int32_t flags;
207 {
208 	int ret;
209 
210 	if ((ret = __db_fchk(dbs->dbc->env, "DB_STREAM->size", flags, 0)) != 0)
211 		return (ret);
212 
213 	*size = dbs->file_size;
214 
215 	return (0);
216 }
217 
218 /*
219  * __db_stream_write --
220  *
221  * DB_STREAM->write
222  */
223 static int
__db_stream_write(dbs,data,offset,flags)224 __db_stream_write(dbs, data, offset, flags)
225 	DB_STREAM *dbs;
226 	DBT *data;
227 	db_off_t offset;
228 	u_int32_t flags;
229 {
230 	DB_THREAD_INFO *ip;
231 	ENV *env;
232 	int ret;
233 	off_t file_size;
234 	u_int32_t wflags;
235 
236 	env = dbs->dbc->env;
237 
238 	if ((ret = __db_fchk(
239 	    env, "DB_STREAM->write", flags, DB_STREAM_SYNC_WRITE)) != 0)
240 		return (ret);
241 
242 	if (F_ISSET(dbs, DB_FOP_READONLY)) {
243 		ret = USR_ERR(env, EINVAL);
244 		__db_errx(env, DB_STR("0213",
245 		    "Error, external file is read only."));
246 		return (ret);
247 	}
248 	if (F_ISSET(data, DB_DBT_PARTIAL)) {
249 		ret = USR_ERR(env, EINVAL);
250 		__db_errx(env, DB_STR("0212",
251 		    "Error, do not use DB_DBT_PARTIAL with DB_STREAM."));
252 		return (ret);
253 	}
254 	if (offset < 0 ) {
255 		ret = USR_ERR(env, EINVAL);
256 		__db_errx(env, DB_STR_A("0215",
257 		    "Error, invalid offset value: %lld", "%lld"),
258 		    (long long)offset);
259 		return (ret);
260 	}
261 	/*
262 	 * Catch offset overflow. After the above test, offset >= 0 is true, and
263 	 * DB_OFF_T_MAX is defined to be the maximum signed integer of the same
264 	 * size as (db_off_t), therefore (DB_OFF_T_MAX - offset) >= 0 must also
265 	 * be true and it cannot cause an overflow. data->size is an unsigned
266 	 * 32-bit integer, while offset is a signed 32-bit or 64-bit integer, so
267 	 * the only safe way to compare the two is to promote both to
268 	 * unsigned 64-bit integers.
269 	 */
270 	if ((u_int64_t)data->size > (u_int64_t)(DB_OFF_T_MAX - offset)) {
271 		ret = USR_ERR(env, EINVAL);
272 		__db_errx(env, DB_STR_A("0216",
273     "Error, this write would exceed the maximum external file size: %lu %lld",
274 		"%lu %lld"), (u_long)data->size, (long long)offset);
275 		return (ret);
276 	}
277 
278 	ENV_ENTER(env, ip);
279 	dbs->dbc->thread_info = ip;
280 	wflags = 0;
281 	if (LF_ISSET(DB_STREAM_SYNC_WRITE) || F_ISSET(dbs, DB_FOP_SYNC_WRITE))
282 		wflags |= DB_FOP_SYNC_WRITE;
283 	file_size = dbs->file_size;
284 	if ((ret = __blob_file_write(dbs->dbc, dbs->fhp,
285 	    data, offset, dbs->blob_id, &file_size, wflags)) != 0)
286 		goto err;
287 	if (file_size != dbs->file_size) {
288 		dbs->file_size = file_size;
289 		if ((ret = __dbc_set_blob_size(dbs->dbc, dbs->file_size)) != 0)
290 			goto err;
291 	}
292 err:	ENV_LEAVE(env, ip);
293 
294 	return (ret);
295 }
296