|
| 1 | +/****************************** Module Header ******************************\ |
| 2 | +* Module Name: CppSparseFile.cpp |
| 3 | +* Project: CppSparseFile |
| 4 | +* URL: http://code.msdn.microsoft.com/windowsapps/CppSparseFile-7f28156b |
| 5 | +* Copyright (c) Microsoft Corporation. |
| 6 | +* |
| 7 | +* CppSparseFile demonstrates the common operations on sparse files. A sparse |
| 8 | +* file is a type of computer file that attempts to use file system space more |
| 9 | +* efficiently when blocks allocated to the file are mostly empty. This is |
| 10 | +* achieved by writing brief information (metadata) representing the empty |
| 11 | +* blocks to disk instead of the actual "empty" space which makes up the |
| 12 | +* block, using less disk space. You can find in this example the creation of |
| 13 | +* sparse file, the detection of sparse attribute, the retrieval of sparse |
| 14 | +* file size, and the query of sparse file layout. |
| 15 | +* |
| 16 | +* This source is subject to the Microsoft Public License. |
| 17 | +* See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL. |
| 18 | +* All other rights reserved. |
| 19 | +* |
| 20 | +* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, |
| 21 | +* EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED |
| 22 | +* WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. |
| 23 | +\***************************************************************************/ |
| 24 | + |
| 25 | +#pragma region Includes |
| 26 | +#include "stdafx.h" |
| 27 | +#include "CppSparseFile.h" |
| 28 | +#pragma endregion |
| 29 | + |
| 30 | + |
| 31 | +/*! |
| 32 | +* VolumeSupportsSparseFiles determines if the volume supports sparse streams. |
| 33 | +* |
| 34 | +* \param lpRootPathName |
| 35 | +* Volume root path e.g. C:\ |
| 36 | +*/ |
| 37 | +BOOL VolumeSupportsSparseFiles(LPCTSTR lpRootPathName) |
| 38 | +{ |
| 39 | + DWORD dwVolFlags; |
| 40 | + GetVolumeInformation(lpRootPathName, NULL, MAX_PATH, NULL, NULL, |
| 41 | + &dwVolFlags, NULL, MAX_PATH); |
| 42 | + |
| 43 | + return (dwVolFlags & FILE_SUPPORTS_SPARSE_FILES) ? TRUE : FALSE; |
| 44 | +} |
| 45 | + |
| 46 | + |
| 47 | +/*! |
| 48 | +* IsSparseFile determines if a file is sparse. |
| 49 | +* |
| 50 | +* \param lpFileName |
| 51 | +* File name |
| 52 | +*/ |
| 53 | +BOOL IsSparseFile(LPCTSTR lpFileName) |
| 54 | +{ |
| 55 | + // Open the file for read |
| 56 | + HANDLE hFile = CreateFile(lpFileName, GENERIC_READ, 0, NULL, |
| 57 | + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); |
| 58 | + if (hFile == INVALID_HANDLE_VALUE) |
| 59 | + return FALSE; |
| 60 | + |
| 61 | + // Get file information |
| 62 | + BY_HANDLE_FILE_INFORMATION bhfi; |
| 63 | + GetFileInformationByHandle(hFile, &bhfi); |
| 64 | + CloseHandle(hFile); |
| 65 | + |
| 66 | + return (bhfi.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) ? TRUE : FALSE; |
| 67 | +} |
| 68 | + |
| 69 | + |
| 70 | +/*! |
| 71 | +* Get sparse file sizes. |
| 72 | +* |
| 73 | +* \param lpFileName |
| 74 | +* File name |
| 75 | +* |
| 76 | +* \see |
| 77 | +* http://msdn.microsoft.com/en-us/library/aa365276.aspx |
| 78 | +*/ |
| 79 | +BOOL GetSparseFileSize(LPCTSTR lpFileName) |
| 80 | +{ |
| 81 | + // Retrieves the size of the specified file, in bytes. The size includes |
| 82 | + // both allocated ranges and sparse ranges. |
| 83 | + HANDLE hFile = CreateFile(lpFileName, GENERIC_READ, 0, NULL, |
| 84 | + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); |
| 85 | + if (hFile == INVALID_HANDLE_VALUE) |
| 86 | + return FALSE; |
| 87 | + LARGE_INTEGER liSparseFileSize; |
| 88 | + GetFileSizeEx(hFile, &liSparseFileSize); |
| 89 | + |
| 90 | + // Retrieves the file's actual size on disk, in bytes. The size does not |
| 91 | + // include the sparse ranges. |
| 92 | + LARGE_INTEGER liSparseFileCompressedSize; |
| 93 | + liSparseFileCompressedSize.LowPart = GetCompressedFileSize(lpFileName, |
| 94 | + (LPDWORD)&liSparseFileCompressedSize.HighPart); |
| 95 | + |
| 96 | + // Print the result |
| 97 | + wprintf(L"\nFile total size: %I64uKB\nActual size on disk: %I64uKB\n", |
| 98 | + liSparseFileSize.QuadPart / 1024, |
| 99 | + liSparseFileCompressedSize.QuadPart / 1024); |
| 100 | + |
| 101 | + CloseHandle(hFile); |
| 102 | + return TRUE; |
| 103 | +} |
| 104 | + |
| 105 | + |
| 106 | +/*! |
| 107 | +* Create a sparse file. |
| 108 | +* |
| 109 | +* \param lpFileName |
| 110 | +* The name of the sparse file |
| 111 | +*/ |
| 112 | +HANDLE CreateSparseFile(LPCTSTR lpFileName) |
| 113 | +{ |
| 114 | + // Create a normal file |
| 115 | + HANDLE hSparseFile = CreateFile(lpFileName, GENERIC_WRITE, 0, NULL, |
| 116 | + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); |
| 117 | + |
| 118 | + if (hSparseFile == INVALID_HANDLE_VALUE) |
| 119 | + return hSparseFile; |
| 120 | + |
| 121 | + // Use the DeviceIoControl function with the FSCTL_SET_SPARSE control |
| 122 | + // code to mark the file as sparse. If you don't mark the file as sparse, |
| 123 | + // the FSCTL_SET_ZERO_DATA control code will actually write zero bytes to |
| 124 | + // the file instead of marking the region as sparse zero area. |
| 125 | + DWORD dwTemp; |
| 126 | + DeviceIoControl(hSparseFile, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &dwTemp, |
| 127 | + NULL); |
| 128 | + |
| 129 | + return hSparseFile; |
| 130 | +} |
| 131 | + |
| 132 | + |
| 133 | +/*! |
| 134 | +* Converting a file region to A sparse zero area. |
| 135 | +* |
| 136 | +* \param hSparseFile |
| 137 | +* Handle of the sparse file |
| 138 | +* |
| 139 | +* \param start |
| 140 | +* Start address of the sparse zero area |
| 141 | +* |
| 142 | +* \param size |
| 143 | +* Size of the sparse zero block. The minimum sparse size is 64KB. |
| 144 | +* |
| 145 | +* \remarks |
| 146 | +* Note that SetSparseRange does not perform actual file I/O, and unlike the |
| 147 | +* WriteFile function, it does not move the current file I/O pointer or sets |
| 148 | +* the end-of-file pointer. That is, if you want to place a sparse zero block |
| 149 | +* in the end of the file, you must move the file pointer accordingly using |
| 150 | +* the FileStream.Seek function, otherwise DeviceIoControl will have no effect |
| 151 | +*/ |
| 152 | +void SetSparseRange(HANDLE hSparseFile, LONGLONG start, LONGLONG size) |
| 153 | +{ |
| 154 | + // Specify the starting and the ending address (not the size) of the |
| 155 | + // sparse zero block |
| 156 | + FILE_ZERO_DATA_INFORMATION fzdi; |
| 157 | + fzdi.FileOffset.QuadPart = start; |
| 158 | + fzdi.BeyondFinalZero.QuadPart = start + size; |
| 159 | + |
| 160 | + // Mark the range as sparse zero block |
| 161 | + DWORD dwTemp; |
| 162 | + DeviceIoControl(hSparseFile, FSCTL_SET_ZERO_DATA, &fzdi, sizeof(fzdi), |
| 163 | + NULL, 0, &dwTemp, NULL); |
| 164 | +} |
| 165 | + |
| 166 | + |
| 167 | +/*! |
| 168 | +* Query the sparse file layout. |
| 169 | +* |
| 170 | +* \param lpFileName |
| 171 | +* File name |
| 172 | +*/ |
| 173 | +BOOL GetSparseRanges(LPCTSTR lpFileName) |
| 174 | +{ |
| 175 | + // Open the file for read |
| 176 | + HANDLE hFile = CreateFile(lpFileName, GENERIC_READ, 0, NULL, |
| 177 | + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); |
| 178 | + if (hFile == INVALID_HANDLE_VALUE) |
| 179 | + return FALSE; |
| 180 | + |
| 181 | + LARGE_INTEGER liFileSize; |
| 182 | + GetFileSizeEx(hFile, &liFileSize); |
| 183 | + |
| 184 | + // Range to be examined (the whole file) |
| 185 | + FILE_ALLOCATED_RANGE_BUFFER queryRange; |
| 186 | + queryRange.FileOffset.QuadPart = 0; |
| 187 | + queryRange.Length = liFileSize; |
| 188 | + |
| 189 | + // Allocated areas info |
| 190 | + FILE_ALLOCATED_RANGE_BUFFER allocRanges[1024]; |
| 191 | + |
| 192 | + DWORD nbytes; |
| 193 | + BOOL fFinished; |
| 194 | + _putws(L"\nAllocated ranges in the file:"); |
| 195 | + do |
| 196 | + { |
| 197 | + fFinished = DeviceIoControl(hFile, FSCTL_QUERY_ALLOCATED_RANGES, |
| 198 | + &queryRange, sizeof(queryRange), allocRanges, |
| 199 | + sizeof(allocRanges), &nbytes, NULL); |
| 200 | + |
| 201 | + if (!fFinished) |
| 202 | + { |
| 203 | + DWORD dwError = GetLastError(); |
| 204 | + |
| 205 | + // ERROR_MORE_DATA is the only error that is normal |
| 206 | + if (dwError != ERROR_MORE_DATA) |
| 207 | + { |
| 208 | + wprintf(L"DeviceIoControl failed w/err 0x%08lx\n", dwError); |
| 209 | + CloseHandle(hFile); |
| 210 | + return FALSE; |
| 211 | + } |
| 212 | + } |
| 213 | + |
| 214 | + // Calculate the number of records returned |
| 215 | + DWORD dwAllocRangeCount = nbytes / |
| 216 | + sizeof(FILE_ALLOCATED_RANGE_BUFFER); |
| 217 | + |
| 218 | + // Print each allocated range |
| 219 | + for (DWORD i = 0; i < dwAllocRangeCount; i++) |
| 220 | + { |
| 221 | + wprintf(L"allocated range: [%I64u] [%I64u]\n", |
| 222 | + allocRanges[i].FileOffset.QuadPart, |
| 223 | + allocRanges[i].Length.QuadPart); |
| 224 | + } |
| 225 | + |
| 226 | + // Set starting address and size for the next query |
| 227 | + if (!fFinished && dwAllocRangeCount > 0) |
| 228 | + { |
| 229 | + queryRange.FileOffset.QuadPart = |
| 230 | + allocRanges[dwAllocRangeCount - 1].FileOffset.QuadPart + |
| 231 | + allocRanges[dwAllocRangeCount - 1].Length.QuadPart; |
| 232 | + |
| 233 | + queryRange.Length.QuadPart = liFileSize.QuadPart - |
| 234 | + queryRange.FileOffset.QuadPart; |
| 235 | + } |
| 236 | + |
| 237 | + } while (!fFinished); |
| 238 | + |
| 239 | + CloseHandle(hFile); |
| 240 | + return TRUE; |
| 241 | +} |
0 commit comments