1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2004,2008 Oracle.  All rights reserved.
5 *
6 * $Id: os_truncate.c,v 12.19 2008/02/18 19:34:22 bostic Exp $
7 */
8
9#include "db_config.h"
10
11#include "db_int.h"
12
13/*
14 * __os_truncate --
15 *	Truncate the file.
16 */
17int
18__os_truncate(env, fhp, pgno, pgsize)
19	ENV *env;
20	DB_FH *fhp;
21	db_pgno_t pgno;
22	u_int32_t pgsize;
23{
24	/* Yes, this really is how Microsoft have designed their API */
25	union {
26		__int64 bigint;
27		struct {
28			unsigned long low;
29			long high;
30		};
31	} off;
32	DB_ENV *dbenv;
33	off_t offset;
34	int ret;
35
36	dbenv = env == NULL ? NULL : env->dbenv;
37	offset = (off_t)pgsize * pgno;
38	ret = 0;
39
40	if (dbenv != NULL &&
41	    FLD_ISSET(dbenv->verbose, DB_VERB_FILEOPS | DB_VERB_FILEOPS_ALL))
42		__db_msg(env,
43		    "fileops: truncate %s to %lu", fhp->name, (u_long)offset);
44
45#ifdef HAVE_FILESYSTEM_NOTZERO
46	/*
47	 * If the filesystem doesn't zero fill, it isn't safe to extend the
48	 * file, or we end up with junk blocks.  Just return in that case.
49	 */
50	if (__os_fs_notzero()) {
51		off_t stat_offset;
52		u_int32_t mbytes, bytes;
53
54		/* Stat the file. */
55		if ((ret =
56		    __os_ioinfo(env, NULL, fhp, &mbytes, &bytes, NULL)) != 0)
57			return (ret);
58		stat_offset = (off_t)mbytes * MEGABYTE + bytes;
59
60		if (offset > stat_offset)
61			return (0);
62	}
63#endif
64
65	LAST_PANIC_CHECK_BEFORE_IO(env);
66
67	/*
68	 * Windows doesn't provide truncate directly.  Instead, it has
69	 * SetEndOfFile, which truncates to the current position.  To
70	 * deal with that, we open a duplicate file handle for truncating.
71	 *
72	 * We want to retry the truncate call, which involves a SetFilePointer
73	 * and a SetEndOfFile, but there are several complications:
74	 *
75	 * 1) since the Windows API deals in 32-bit values, it's possible that
76	 *    the return from SetFilePointer (the low 32-bits) is
77	 *    INVALID_SET_FILE_POINTER even when the call has succeeded.  So we
78	 *    have to also check whether GetLastError() returns NO_ERROR.
79	 *
80	 * 2) when it returns, SetFilePointer overwrites the high bits of the
81	 *    offset, so if we need to retry, we have to reset the offset each
82	 *    time.
83	 *
84	 * We can't switch to SetFilePointerEx, which knows about 64-bit
85	 * offsets, because it isn't supported on Win9x/ME.
86	 */
87	RETRY_CHK((off.bigint = (__int64)pgsize * pgno,
88	    (SetFilePointer(fhp->trunc_handle, off.low, &off.high, FILE_BEGIN)
89	    == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) ||
90	    !SetEndOfFile(fhp->trunc_handle)), ret);
91
92	if (ret != 0) {
93		__db_syserr(env, ret, "SetFilePointer: %lu", pgno * pgsize);
94		ret = __os_posix_err(ret);
95	}
96
97	return (ret);
98}
99