source: trunk/eraser/Eraser.Util.Native/FatApi.cpp @ 2516

Revision 2516, 11.1 KB checked in by lowjoel, 2 years ago (diff)

Update copyrights to this year.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Rev URL
Line 
1/*
2 * $Id$
3 * Copyright 2008-2012 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22#include <stdafx.h>
23#include <vector>
24#include <windows.h>
25
26#include "FatApi.h"
27
28using namespace System::Collections::Generic;
29using namespace System::IO;
30using namespace System::Runtime::InteropServices;
31
32namespace Eraser {
33namespace Util {
34    FatApi::FatApi(VolumeInfo^ info)
35    {
36        BootSector = new FatBootSector();
37        memset(BootSector, 0, sizeof(*BootSector));
38        Fat = NULL;
39
40        //Open the handle to the drive
41        VolumeStream = info->Open(FileAccess::Read);
42
43        //Then read the boot sector for information
44        array<Byte>^ bootSector = gcnew array<Byte>(sizeof(*BootSector));
45        VolumeStream->Seek(0, SeekOrigin::Begin);
46        VolumeStream->Read(bootSector, 0, sizeof(*BootSector));
47        Marshal::Copy(bootSector, 0, static_cast<IntPtr>(BootSector), bootSector->Length);
48
49        //Then load the FAT
50        LoadFat();
51    }
52
53    FatApi::FatApi(Stream^ stream)
54    {
55        BootSector = new FatBootSector();
56        memset(BootSector, 0, sizeof(*BootSector));
57        Fat = NULL;
58
59        //Open the handle to the drive
60        VolumeStream = stream;
61
62        //Then read the boot sector for information
63        array<Byte>^ bootSector = gcnew array<Byte>(sizeof(*BootSector));
64        VolumeStream->Seek(0, SeekOrigin::Begin);
65        VolumeStream->Read(bootSector, 0, sizeof(*BootSector));
66        Marshal::Copy(bootSector, 0, static_cast<IntPtr>(BootSector), bootSector->Length);
67
68        //Then load the FAT
69        LoadFat();
70    }
71
72    FatDirectoryBase^ FatApi::LoadDirectory(String^ directory)
73    {
74        //Return the root directory if nothing is specified
75        if (String::IsNullOrEmpty(directory))
76            return LoadDirectory(DirectoryToCluster(directory), String::Empty, nullptr);
77
78        array<wchar_t>^ pathSeparators = { Path::DirectorySeparatorChar, Path::AltDirectorySeparatorChar };
79        int lastIndex = directory->LastIndexOfAny(pathSeparators);
80        return LoadDirectory(DirectoryToCluster(directory), directory->Substring(lastIndex + 1),
81            LoadDirectory(directory->Substring(0, lastIndex)));
82    }
83
84    unsigned long long FatApi::SectorToOffset(unsigned long long sector)
85    {
86        return sector * BootSector->BytesPerSector;
87    }
88
89    unsigned FatApi::SectorSizeToSize(unsigned size)
90    {
91        return size * BootSector->BytesPerSector;
92    }
93
94    unsigned FatApi::ClusterSizeToSize(unsigned size)
95    {
96        return size * (BootSector->BytesPerSector * BootSector->SectorsPerCluster);
97    }
98
99    array<Byte>^ FatApi::GetFileContents(unsigned cluster)
100    {
101        if (!IsClusterAllocated(cluster))
102            throw gcnew ArgumentException(S::_(L"The specified cluster is not used."));
103
104        array<Byte>^ result = gcnew array<Byte>(FileSize(cluster));
105        const int clusterSize = ClusterSizeToSize(1);
106        int nextIndex = 0;
107
108        do
109        {
110            VolumeStream->Seek(ClusterToOffset(cluster), SeekOrigin::Begin);
111            VolumeStream->Read(result, nextIndex, clusterSize);
112            nextIndex += clusterSize;
113        }
114        while ((cluster = GetNextCluster(cluster)) != 0xFFFFFFFF);
115
116        return result;
117    }
118
119    void FatApi::SetFileContents(array<Byte>^ buffer, unsigned cluster)
120    {
121        if (!IsClusterAllocated(cluster))
122            throw gcnew ArgumentException(S::_(L"The specified cluster is not used."));
123        if (static_cast<unsigned>(buffer->Length) != FileSize(cluster))
124            throw gcnew ArgumentException(S::_(L"The provided file contents will not fit in the "
125                L"allocated file."));
126
127        int clusterSize = static_cast<int>(ClusterSizeToSize(1));
128        for (int i = 0; i < buffer->Length; i += clusterSize)
129        {
130            VolumeStream->Seek(ClusterToOffset(cluster), SeekOrigin::Begin);
131            VolumeStream->Write(buffer, i, clusterSize);
132            cluster = GetNextCluster(cluster);
133        }
134    }
135
136    FatDirectoryEntry::FatDirectoryEntry(String^ name, FatDirectoryBase^ parent,
137        FatDirectoryEntryType type, unsigned cluster)
138    {
139        Name = name;
140        Parent = parent;
141        EntryType = type;
142        Cluster = cluster;
143    }
144
145    String^ FatDirectoryEntry::FullName::get()
146    {
147        String^ result = Name;
148        FatDirectoryEntry^ currentEntry = this;
149
150        while (currentEntry->Parent != nullptr)
151        {
152            currentEntry = currentEntry->Parent;
153            result = currentEntry->Name + Path::DirectorySeparatorChar + result;
154        }
155
156        return result;
157    }
158
159    FatDirectoryBase::FatDirectoryBase(String^ name, FatDirectoryBase^ parent, unsigned cluster)
160        : FatDirectoryEntry(name, parent, FatDirectoryEntryType::Directory, cluster)
161    {
162        Entries = gcnew Dictionary<String^, FatDirectoryEntry^>();
163        ReadDirectory();
164    }
165
166    void FatDirectoryBase::ClearDeletedEntries()
167    {
168        std::vector<::FatDirectoryEntry> validEntries;
169
170        //Parse the directory structures
171        for (::FatDirectoryEntry* i = Directory; i != Directory + DirectorySize; ++i)
172        {
173            //Check if we have checked the last valid entry
174            if (i->Short.Name[0] == 0x00)
175                break;
176
177            //Skip deleted entries.
178            if (static_cast<unsigned char>(i->Short.Name[0]) == 0xE5)
179                continue;
180
181            if (i->Short.Attributes == 0x0F)
182            {
183                //This is a long file name.
184                ::FatDirectoryEntry* longFileNameBegin = i;
185                for (unsigned char sequence = 0; i->Short.Attributes == 0x0F; ++i)
186                {
187                    if (static_cast<unsigned char>(i->Short.Name[0]) == 0xE5)
188                        continue;
189                    else if (!(i->LongFileName.Sequence & 0x40)) //Second entry onwards
190                    {
191                        //Check that the checksum of the file name is the same as the previous
192                        //long file name entry, to ensure no corruption has taken place
193                        if ((i - 1)->LongFileName.Checksum != i->LongFileName.Checksum)
194                            continue;
195
196                        //Check that the sequence is one less than the previous one.
197                        if (sequence != i->LongFileName.Sequence + 1)
198                            throw gcnew ArgumentException(S::_(L"Invalid directory entry."));
199                    }
200                   
201                    sequence = i->LongFileName.Sequence & ~0x40;
202                }
203
204                //Checksum the string
205                unsigned char sum = 0;
206                char* shortFileName = i->Short.Name;
207                for (int j = 11; j; --j)
208                    sum = ((sum & 1) << 7) + (sum >> 1) + *shortFileName++;
209
210                if (sum == (i - 1)->LongFileName.Checksum)
211                {
212                    //The previous few entries contained the correct file name. Save these entries
213                    validEntries.insert(validEntries.end(), longFileNameBegin, i);
214                }
215                else
216                {
217                    --i;
218                    continue;
219                }
220            }
221
222            validEntries.push_back(*i);
223        }
224
225        //validEntries now contains the compacted list of directory entries. Zero
226        //the memory used.
227        memset(Directory, 0, DirectorySize * sizeof(::FatDirectoryEntry));
228
229        //Copy the memory back if we have any valid entries. The root directory can
230        //be empty (no . and .. entries)
231        if (!validEntries.empty())
232            memcpy(Directory, &validEntries.front(), validEntries.size() * sizeof(::FatDirectoryEntry));
233
234        //Write the entries to disk
235        WriteDirectory();
236    }
237
238    void FatDirectoryBase::ParseDirectory()
239    {
240        //Clear the list of entries
241        Entries->Clear();
242
243        //Parse the directory structures
244        for (::FatDirectoryEntry* i = Directory; i != Directory + DirectorySize; ++i)
245        {
246            //Check if we have checked the last valid entry
247            if (i->Short.Name[0] == 0x00)
248                break;
249
250            //Skip deleted entries.
251            if (static_cast<unsigned char>(i->Short.Name[0]) == 0xE5)
252                continue;
253
254            if (i->Short.Attributes == 0x0F)
255            {
256                //This is a long file name.
257                std::wstring longFileName;
258                for (unsigned char sequence = 0; i->Short.Attributes == 0x0F; ++i)
259                {
260                    if (static_cast<unsigned char>(i->Short.Name[0]) == 0xE5)
261                        continue;
262                    else if (!(i->LongFileName.Sequence & 0x40)) //Second entry onwards
263                    {
264                        //Check that the checksum of the file name is the same as the previous
265                        //long file name entry, to ensure no corruption has taken place
266                        if ((i - 1)->LongFileName.Checksum != i->LongFileName.Checksum)
267                            continue;
268
269                        //Check that the sequence is one less than the previous one.
270                        if (sequence != i->LongFileName.Sequence + 1)
271                            throw gcnew ArgumentException(S::_(L"Invalid directory entry."));
272                    }
273                    else
274                        longFileName.clear();
275                   
276                    sequence = i->LongFileName.Sequence & ~0x40;
277                    std::wstring namePart(i->LongFileName.Name1, sizeof(i->LongFileName.Name1) / sizeof(i->LongFileName.Name1[0]));
278                    namePart += std::wstring(i->LongFileName.Name2, sizeof(i->LongFileName.Name2) / sizeof(i->LongFileName.Name2[0]));
279                    namePart += std::wstring(i->LongFileName.Name3, sizeof(i->LongFileName.Name3) / sizeof(i->LongFileName.Name3[0]));
280                    namePart += longFileName;
281                    longFileName = namePart;
282                }
283
284                //Checksum the string
285                unsigned char sum = 0;
286                char* shortFileName = i->Short.Name;
287                for (int j = 11; j; --j)
288                    sum = ((sum & 1) << 7) + (sum >> 1) + *shortFileName++;
289
290                if (sum == (i - 1)->LongFileName.Checksum)
291                {
292                    //fileName contains the correct full long file name, strip the file name of the
293                    //invalid characters.
294                    String^ fileName = gcnew String(longFileName.c_str());
295                    Entries->Add(fileName, gcnew FatDirectoryEntry(fileName, this,
296                        (i->Short.Attributes & FILE_ATTRIBUTE_DIRECTORY) ?
297                            FatDirectoryEntryType::Directory : FatDirectoryEntryType::File,
298                        GetStartCluster(*i)));
299                }
300                else
301                {
302                    --i;
303                    continue;
304                }
305            }
306
307            //Skip the dot directories.
308            if (i->Short.Name[0] == '.')
309                continue;
310
311            //Substitute 0x05 with 0xE5
312            if (i->Short.Name[0] == 0x05)
313                i->Short.Name[0] = static_cast<unsigned char>(0xE5);
314           
315            //Then read the 8.3 entry for the file details
316            wchar_t shortFileName[8 + 3 + 2];
317            if (mbstowcs(shortFileName, i->Short.Name, sizeof(i->Short.Name)) != sizeof(i->Short.Name))
318                continue;
319
320            //If the extension is blank, don't care about it
321            if (strncmp(i->Short.Extension, "   ", 3) == 0)
322            {
323                shortFileName[8] = '\0';
324            }
325            else
326            {
327                mbstowcs(shortFileName + 9, i->Short.Extension, sizeof(i->Short.Extension) + 1);
328                shortFileName[8] = L'.';
329                shortFileName[8 + 3 + 1] = '\0';
330            }
331
332            String^ fileName = gcnew String(shortFileName);
333            Entries->Add(fileName, gcnew FatDirectoryEntry(fileName, this,
334                (i->Short.Attributes & FILE_ATTRIBUTE_DIRECTORY) ?
335                    FatDirectoryEntryType::Directory : FatDirectoryEntryType::File,
336                GetStartCluster(*i)));
337        }
338    }
339
340    FatDirectory::FatDirectory(String^ name, FatDirectoryBase^ parent, unsigned cluster, FatApi^ api)
341        : Api(api),
342          FatDirectoryBase(name, parent, cluster)
343    {
344    }
345
346    void FatDirectory::ReadDirectory()
347    {
348        array<Byte>^ dir = Api->GetFileContents(Cluster);
349        DirectorySize = dir->Length / sizeof(::FatDirectoryEntry);
350        Directory = new ::FatDirectoryEntry[DirectorySize];
351        Marshal::Copy(dir, 0, static_cast<IntPtr>(Directory), dir->Length);
352
353        ParseDirectory();
354    }
355
356    void FatDirectory::WriteDirectory()
357    {
358        array<Byte>^ buffer = gcnew array<Byte>(static_cast<int>(DirectorySize *
359            sizeof(::FatDirectoryEntry)));
360        Marshal::Copy(static_cast<IntPtr>(Directory), buffer, 0, buffer->Length);
361        Api->SetFileContents(buffer, Cluster);
362    }
363}
364}
Note: See TracBrowser for help on using the repository browser.