1/*
2 * Copyright (c) 2008 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23//
24//	wipefs.cpp
25//
26
27#include <fcntl.h>
28#include <unistd.h>
29#include <sys/uio.h>
30#include <sys/ioctl.h>
31#include <sys/disk.h>
32#include <sys/stat.h>
33
34#include "ExtentManager.h"
35#include "wipefs.h"
36
37#define	roundup(x, y)	((((x)+((y)-1))/(y))*(y))
38
39struct __wipefs_ctx {
40	int fd;
41	class ExtentManager extMan;
42};
43
44static void
45AddExtentsForFutureFS(class ExtentManager *extMan)
46{
47	// we don't know what blocks future FS will use to recognize itself.  But we'd better be safe than sorry and write
48	// the first and last 2MB of the volume
49	off_t size = 2 * 1024 * 1024;
50	extMan->AddByteRangeExtent(0, size);
51	extMan->AddByteRangeExtent(extMan->totalBytes - size, size);
52}
53
54static void
55AddExtentsForHFS(class ExtentManager *extMan)
56{
57	// first 1KB is boot block, last 512B is reserved
58	// the Volume Header (512B) is after 1KB and before the last 512B
59	extMan->AddByteRangeExtent(0, 1024 + 512);
60	extMan->AddByteRangeExtent(extMan->totalBytes - 1024, 1024);
61}
62
63static void
64AddExtentsForMSDOS(class ExtentManager *extMan)
65{
66	// MSDOS needs the first block (in theory, up to 32KB)
67	extMan->AddByteRangeExtent(0, 32 * 1024);
68}
69
70static void
71AddExtentsForNTFS(class ExtentManager *extMan)
72{
73	// NTFS supports block size from 256B to 32768B.  The first, middle and last block are needed
74	extMan->AddByteRangeExtent(0, 32 * 1024);
75	extMan->AddByteRangeExtent(extMan->totalBytes - 32 * 1024, 32 * 1024);
76	// to be safe, add the rage from (mid_point - 32KB) to (mid_point + 32KB)
77	extMan->AddByteRangeExtent(extMan->totalBytes / 2 - 32 * 1024, 64 * 1024);
78}
79
80static void
81AddExtentsForUDF(class ExtentManager *extMan)
82{
83	off_t lastBlockAddr = extMan->totalBlocks - 1;
84
85	// Volume Recognization Sequence (VRS) starts at 32KB, usually less than 7 Volume Structure Descriptors (2KB each)
86	extMan->AddByteRangeExtent(32 * 1024, 14 * 1024);
87
88	// AVDP is on 256, 512, last block, last block - 256
89	extMan->AddBlockRangeExtent(256, 1);
90	extMan->AddBlockRangeExtent(512, 1);
91	extMan->AddBlockRangeExtent(lastBlockAddr, 1);
92	extMan->AddBlockRangeExtent(lastBlockAddr - 256, 1);
93
94	// to be safe, assume the device has 2KB block size and do it again
95	if (extMan->blockSize != 2048) {
96		off_t blockSize = 2048;
97		// AVDP is on 256, 512, last block, last block - 256
98		extMan->AddByteRangeExtent(256 * blockSize, blockSize);
99		extMan->AddByteRangeExtent(512 * blockSize, blockSize);
100		extMan->AddByteRangeExtent(extMan->totalBytes - blockSize, blockSize);
101		extMan->AddByteRangeExtent(extMan->totalBytes - 256 * blockSize, blockSize);
102	}
103}
104
105static void
106AddExtentsForUFS(class ExtentManager *extMan)
107{
108	// UFS super block is 8KB at offset 8KB
109	extMan->AddByteRangeExtent(8192, 8192);
110}
111
112static void
113AddExtentsForZFS(class ExtentManager *extMan)
114{
115	// ZFS needs the first 512KB and last 512KB for all the 4 disk labels
116	extMan->AddByteRangeExtent(0, 512 * 1024);
117	extMan->AddByteRangeExtent(extMan->totalBytes - 512 * 1024, 512 * 1024);
118}
119
120static void
121AddExtentsForPartitions(class ExtentManager *extMan)
122{
123	// MBR (Master Boot Record) needs the first sector
124	// APM (Apple Partition Map) needs the second sector
125	// GPT (GUID Partition Table) needs the first 34 and last 33 sectors
126	extMan->AddByteRangeExtent(0, 512 * 34);
127	extMan->AddByteRangeExtent(extMan->totalBytes - 512 * 33, 512 * 33);
128}
129
130extern "C" int
131wipefs_alloc(int fd, size_t block_size, wipefs_ctx *handle)
132{
133	int err = 0;
134	uint64_t numBlocks = 0;
135	uint32_t nativeBlockSize = 0;
136	off_t totalSizeInBytes = 0;
137	class ExtentManager *extMan = NULL;
138	struct stat sbuf = { 0 };
139
140	*handle = NULL;
141	(void)fstat(fd, &sbuf);
142	switch (sbuf.st_mode & S_IFMT) {
143	case S_IFCHR:
144	case S_IFBLK:
145		if (ioctl(fd, DKIOCGETBLOCKSIZE, (char *)&nativeBlockSize) < 0) {
146			err = errno;
147			goto labelExit;
148		}
149		if (ioctl(fd, DKIOCGETBLOCKCOUNT, (char *)&numBlocks) < 0) {
150			err = errno;
151			goto labelExit;
152		}
153		totalSizeInBytes = numBlocks * nativeBlockSize;
154		break;
155	case S_IFREG:
156		nativeBlockSize = sbuf.st_blksize;
157		numBlocks = sbuf.st_size / sbuf.st_blksize;
158		totalSizeInBytes = sbuf.st_size;
159		break;
160	default:
161		errno = EINVAL;
162		goto labelExit;
163	}
164	if (block_size == 0) {
165		block_size = nativeBlockSize;
166	}
167	if (block_size == 0 || totalSizeInBytes == 0) {
168		err = EINVAL;
169		goto labelExit;
170	}
171
172	try {
173		*handle = new __wipefs_ctx;
174		if (*handle == NULL) {
175			bad_alloc e;
176			throw e;
177		}
178
179		(*handle)->fd = fd;
180		extMan = &(*handle)->extMan;
181
182		extMan->Init(block_size, nativeBlockSize, totalSizeInBytes);
183		AddExtentsForFutureFS(extMan);
184		AddExtentsForHFS(extMan);
185		AddExtentsForMSDOS(extMan);
186		AddExtentsForNTFS(extMan);
187		AddExtentsForUDF(extMan);
188		AddExtentsForUFS(extMan);
189		AddExtentsForZFS(extMan);
190		AddExtentsForPartitions(extMan);
191	}
192	catch (bad_alloc &e) {
193		err = ENOMEM;
194	}
195	catch (...) { // currently only ENOMEM is possible
196		err = ENOMEM;
197	}
198
199  labelExit:
200	if (err != 0) {
201		wipefs_free(handle);
202	}
203	return err;
204} // wipefs_alloc
205
206extern "C" int
207wipefs_include_blocks(wipefs_ctx handle, off_t block_offset, off_t nblocks)
208{
209	int err = 0;
210	try {
211		handle->extMan.AddBlockRangeExtent(block_offset, nblocks);
212	}
213	catch (bad_alloc &e) {
214		err = ENOMEM;
215	}
216	catch (...) { // currently only ENOMEM is possible
217		err = ENOMEM;
218	}
219	return err;
220}
221
222extern "C" int
223wipefs_except_blocks(wipefs_ctx handle, off_t block_offset, off_t nblocks)
224{
225	int err = 0;
226	try {
227		handle->extMan.RemoveBlockRangeExtent(block_offset, nblocks);
228	}
229	catch (bad_alloc &e) {
230		err = ENOMEM;
231	}
232	catch (...) { // currently only ENOMEM is possible
233		err = ENOMEM;
234	}
235	return err;
236}
237
238extern "C" int
239wipefs_wipe(wipefs_ctx handle)
240{
241	int err = 0;
242	uint8_t *bufZero = NULL;
243	ListExtIt curExt;
244	size_t bufSize;
245	dk_extent_t extent;
246	dk_unmap_t unmap;
247
248	memset(&extent, 0, sizeof(dk_extent_t));
249	extent.length = handle->extMan.totalBytes;
250
251	memset(&unmap, 0, sizeof(dk_unmap_t));
252	unmap.extents = &extent;
253	unmap.extentsCount = 1;
254
255	//
256	// Don't bother to check the return value since this is mostly
257	// informational for the lower-level drivers.
258	//
259	ioctl(handle->fd, DKIOCUNMAP, (caddr_t)&unmap);
260
261
262	bufSize = 128 * 1024; // issue large I/O to get better performance
263	if (handle->extMan.nativeBlockSize > bufSize) {
264	    bufSize = handle->extMan.nativeBlockSize;
265	}
266	bufZero = new uint8_t[bufSize];
267	bzero(bufZero, bufSize);
268
269	off_t byteOffset, totalBytes;
270	size_t numBytes, numBytesToWrite, blockSize;
271
272	blockSize = handle->extMan.blockSize;
273	totalBytes = handle->extMan.totalBytes;
274	// write zero to all extents
275	for (curExt = handle->extMan.extentList.begin(); curExt != handle->extMan.extentList.end(); curExt++) {
276		byteOffset = curExt->blockAddr * blockSize;
277		numBytes = curExt->numBlocks * blockSize;
278		// make both offset and numBytes on native block boundary
279		if (byteOffset % handle->extMan.nativeBlockSize != 0 ||
280			numBytes % handle->extMan.nativeBlockSize != 0) {
281			size_t nativeBlockSize = handle->extMan.nativeBlockSize;
282			off_t newOffset, newEndOffset;
283			newOffset = byteOffset / nativeBlockSize * nativeBlockSize;
284			newEndOffset = roundup(byteOffset + numBytes, nativeBlockSize);
285			byteOffset = newOffset;
286			numBytes = newEndOffset - newOffset;
287		}
288		if (byteOffset + (off_t)numBytes > totalBytes) {
289			numBytes = totalBytes - byteOffset;
290		}
291		while (numBytes > 0) {
292			numBytesToWrite = min(numBytes, bufSize);
293			if (pwrite(handle->fd, bufZero, numBytesToWrite, byteOffset) != (ssize_t)numBytesToWrite) {
294				err = errno;
295				goto labelExit;
296			}
297			numBytes -= numBytesToWrite;
298			byteOffset += numBytesToWrite;
299		}
300	}
301
302  labelExit:
303	if (bufZero != NULL)
304		delete[] bufZero;
305	return err;
306} // wipefs_wipe
307
308extern "C" void
309wipefs_free(wipefs_ctx *handle)
310{
311	if (*handle != NULL) {
312		delete *handle;
313		*handle = NULL;
314	}
315}
316