1// Windows/FileIO.cpp
2
3#include "StdAfx.h"
4
5#ifdef SUPPORT_DEVICE_FILE
6#include "../../C/Alloc.h"
7#endif
8
9#include "FileIO.h"
10#include "FileName.h"
11
12#ifndef _UNICODE
13extern bool g_IsNT;
14#endif
15
16using namespace NWindows;
17using namespace NFile;
18using namespace NName;
19
20namespace NWindows {
21namespace NFile {
22
23#ifdef SUPPORT_DEVICE_FILE
24
25namespace NSystem
26{
27bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, UInt64 &freeSize);
28}
29#endif
30
31namespace NIO {
32
33/*
34WinXP-64 CreateFile():
35  ""             -  ERROR_PATH_NOT_FOUND
36  :stream        -  OK
37  .:stream       -  ERROR_PATH_NOT_FOUND
38  .\:stream      -  OK
39
40  folder\:stream -  ERROR_INVALID_NAME
41  folder:stream  -  OK
42
43  c:\:stream     -  OK
44
45  c::stream      -  ERROR_INVALID_NAME, if current dir is NOT ROOT ( c:\dir1 )
46  c::stream      -  OK,                 if current dir is ROOT     ( c:\ )
47*/
48
49bool CFileBase::Create(CFSTR path, DWORD desiredAccess,
50    DWORD shareMode, DWORD creationDisposition, DWORD flagsAndAttributes)
51{
52  if (!Close())
53    return false;
54
55  #ifdef SUPPORT_DEVICE_FILE
56  IsDeviceFile = false;
57  #endif
58
59  #ifndef _UNICODE
60  if (!g_IsNT)
61  {
62    _handle = ::CreateFile(fs2fas(path), desiredAccess, shareMode,
63        (LPSECURITY_ATTRIBUTES)NULL, creationDisposition, flagsAndAttributes, (HANDLE)NULL);
64  }
65  else
66  #endif
67  {
68    IF_USE_MAIN_PATH
69      _handle = ::CreateFileW(fs2us(path), desiredAccess, shareMode,
70        (LPSECURITY_ATTRIBUTES)NULL, creationDisposition, flagsAndAttributes, (HANDLE)NULL);
71    #ifdef WIN_LONG_PATH
72    if (_handle == INVALID_HANDLE_VALUE && USE_SUPER_PATH)
73    {
74      UString superPath;
75      if (GetSuperPath(path, superPath, USE_MAIN_PATH))
76        _handle = ::CreateFileW(superPath, desiredAccess, shareMode,
77            (LPSECURITY_ATTRIBUTES)NULL, creationDisposition, flagsAndAttributes, (HANDLE)NULL);
78    }
79    #endif
80  }
81  return (_handle != INVALID_HANDLE_VALUE);
82}
83
84bool CFileBase::Close() throw()
85{
86  if (_handle == INVALID_HANDLE_VALUE)
87    return true;
88  if (!::CloseHandle(_handle))
89    return false;
90  _handle = INVALID_HANDLE_VALUE;
91  return true;
92}
93
94bool CFileBase::GetPosition(UInt64 &position) const throw()
95{
96  return Seek(0, FILE_CURRENT, position);
97}
98
99bool CFileBase::GetLength(UInt64 &length) const throw()
100{
101  #ifdef SUPPORT_DEVICE_FILE
102  if (IsDeviceFile && SizeDefined)
103  {
104    length = Size;
105    return true;
106  }
107  #endif
108
109  DWORD sizeHigh;
110  DWORD sizeLow = ::GetFileSize(_handle, &sizeHigh);
111  if (sizeLow == 0xFFFFFFFF)
112    if (::GetLastError() != NO_ERROR)
113      return false;
114  length = (((UInt64)sizeHigh) << 32) + sizeLow;
115  return true;
116}
117
118bool CFileBase::Seek(Int64 distanceToMove, DWORD moveMethod, UInt64 &newPosition) const throw()
119{
120  #ifdef SUPPORT_DEVICE_FILE
121  if (IsDeviceFile && SizeDefined && moveMethod == FILE_END)
122  {
123    distanceToMove += Size;
124    moveMethod = FILE_BEGIN;
125  }
126  #endif
127
128  LONG high = (LONG)(distanceToMove >> 32);
129  DWORD low = ::SetFilePointer(_handle, (LONG)(distanceToMove & 0xFFFFFFFF), &high, moveMethod);
130  if (low == 0xFFFFFFFF)
131    if (::GetLastError() != NO_ERROR)
132      return false;
133  newPosition = (((UInt64)high) << 32) + low;
134  return true;
135}
136
137bool CFileBase::Seek(UInt64 position, UInt64 &newPosition) const throw()
138{
139  return Seek(position, FILE_BEGIN, newPosition);
140}
141
142bool CFileBase::SeekToBegin() const throw()
143{
144  UInt64 newPosition;
145  return Seek(0, newPosition);
146}
147
148bool CFileBase::SeekToEnd(UInt64 &newPosition) const throw()
149{
150  return Seek(0, FILE_END, newPosition);
151}
152
153// ---------- CInFile ---------
154
155#ifdef SUPPORT_DEVICE_FILE
156
157void CInFile::CorrectDeviceSize()
158{
159  // maybe we must decrease kClusterSize to 1 << 12, if we want correct size at tail
160  static const UInt32 kClusterSize = 1 << 14;
161  UInt64 pos = Size & ~(UInt64)(kClusterSize - 1);
162  UInt64 realNewPosition;
163  if (!Seek(pos, realNewPosition))
164    return;
165  Byte *buf = (Byte *)MidAlloc(kClusterSize);
166
167  bool needbackward = true;
168
169  for (;;)
170  {
171    UInt32 processed = 0;
172    // up test is slow for "PhysicalDrive".
173    // processed size for latest block for "PhysicalDrive0" is 0.
174    if (!Read1(buf, kClusterSize, processed))
175      break;
176    if (processed == 0)
177      break;
178    needbackward = false;
179    Size = pos + processed;
180    if (processed != kClusterSize)
181      break;
182    pos += kClusterSize;
183  }
184
185  if (needbackward && pos != 0)
186  {
187    pos -= kClusterSize;
188    for (;;)
189    {
190      // break;
191      if (!Seek(pos, realNewPosition))
192        break;
193      if (!buf)
194      {
195        buf = (Byte *)MidAlloc(kClusterSize);
196        if (!buf)
197          break;
198      }
199      UInt32 processed = 0;
200      // that code doesn't work for "PhysicalDrive0"
201      if (!Read1(buf, kClusterSize, processed))
202        break;
203      if (processed != 0)
204      {
205        Size = pos + processed;
206        break;
207      }
208      if (pos == 0)
209        break;
210      pos -= kClusterSize;
211    }
212  }
213  MidFree(buf);
214}
215
216
217void CInFile::CalcDeviceSize(CFSTR s)
218{
219  SizeDefined = false;
220  Size = 0;
221  if (_handle == INVALID_HANDLE_VALUE || !IsDeviceFile)
222    return;
223  #ifdef UNDER_CE
224
225  SizeDefined = true;
226  Size = 128 << 20;
227
228  #else
229
230  PARTITION_INFORMATION partInfo;
231  bool needCorrectSize = true;
232
233  /*
234    WinXP 64-bit:
235
236    HDD \\.\PhysicalDrive0 (MBR):
237      GetPartitionInfo == GeometryEx :  corrrect size? (includes tail)
238      Geometry   :  smaller than GeometryEx (no tail, maybe correct too?)
239      MyGetDiskFreeSpace : FAIL
240      Size correction is slow and block size (kClusterSize) must be small?
241
242    HDD partition \\.\N: (NTFS):
243      MyGetDiskFreeSpace   :  Size of NTFS clusters. Same size can be calculated after correction
244      GetPartitionInfo     :  size of partition data: NTFS clusters + TAIL; TAIL contains extra empty sectors and copy of first sector of NTFS
245      Geometry / CdRomGeometry / GeometryEx :  size of HDD (not that partition)
246
247    CD-ROM drive (ISO):
248      MyGetDiskFreeSpace   :  correct size. Same size can be calculated after correction
249      Geometry == CdRomGeometry  :  smaller than corrrect size
250      GetPartitionInfo == GeometryEx :  larger than corrrect size
251
252    Floppy \\.\a: (FAT):
253      Geometry :  correct size.
254      CdRomGeometry / GeometryEx / GetPartitionInfo / MyGetDiskFreeSpace - FAIL
255      correction works OK for FAT.
256      correction works OK for non-FAT, if kClusterSize = 512.
257  */
258
259  if (GetPartitionInfo(&partInfo))
260  {
261    Size = partInfo.PartitionLength.QuadPart;
262    SizeDefined = true;
263    needCorrectSize = false;
264    if ((s)[0] == '\\' && (s)[1] == '\\' && (s)[2] == '.' && (s)[3] == '\\' && (s)[5] == ':' && (s)[6] == 0)
265    {
266      FChar path[4] = { s[4], ':', '\\', 0 };
267      UInt64 clusterSize, totalSize, freeSize;
268      if (NSystem::MyGetDiskFreeSpace(path, clusterSize, totalSize, freeSize))
269        Size = totalSize;
270      else
271        needCorrectSize = true;
272    }
273  }
274
275  if (!SizeDefined)
276  {
277    my_DISK_GEOMETRY_EX geomEx;
278    SizeDefined = GetGeometryEx(&geomEx);
279    if (SizeDefined)
280      Size = geomEx.DiskSize.QuadPart;
281    else
282    {
283      DISK_GEOMETRY geom;
284      SizeDefined = GetGeometry(&geom);
285      if (!SizeDefined)
286        SizeDefined = GetCdRomGeometry(&geom);
287      if (SizeDefined)
288        Size = geom.Cylinders.QuadPart * geom.TracksPerCylinder * geom.SectorsPerTrack * geom.BytesPerSector;
289    }
290  }
291
292  if (needCorrectSize && SizeDefined && Size != 0)
293  {
294    CorrectDeviceSize();
295    SeekToBegin();
296  }
297
298  // SeekToBegin();
299  #endif
300}
301
302// ((desiredAccess & (FILE_WRITE_DATA | FILE_APPEND_DATA | GENERIC_WRITE)) == 0 &&
303
304#define MY_DEVICE_EXTRA_CODE \
305  IsDeviceFile = IsDevicePath(fileName); \
306  CalcDeviceSize(fileName);
307#else
308#define MY_DEVICE_EXTRA_CODE
309#endif
310
311bool CInFile::Open(CFSTR fileName, DWORD shareMode, DWORD creationDisposition, DWORD flagsAndAttributes)
312{
313  bool res = Create(fileName, GENERIC_READ, shareMode, creationDisposition, flagsAndAttributes);
314  MY_DEVICE_EXTRA_CODE
315  return res;
316}
317
318bool CInFile::OpenShared(CFSTR fileName, bool shareForWrite)
319{ return Open(fileName, FILE_SHARE_READ | (shareForWrite ? FILE_SHARE_WRITE : 0), OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL); }
320
321bool CInFile::Open(CFSTR fileName)
322  { return OpenShared(fileName, false); }
323
324// ReadFile and WriteFile functions in Windows have BUG:
325// If you Read or Write 64MB or more (probably min_failure_size = 64MB - 32KB + 1)
326// from/to Network file, it returns ERROR_NO_SYSTEM_RESOURCES
327// (Insufficient system resources exist to complete the requested service).
328
329// Probably in some version of Windows there are problems with other sizes:
330// for 32 MB (maybe also for 16 MB).
331// And message can be "Network connection was lost"
332
333static UInt32 kChunkSizeMax = (1 << 22);
334
335bool CInFile::Read1(void *data, UInt32 size, UInt32 &processedSize) throw()
336{
337  DWORD processedLoc = 0;
338  bool res = BOOLToBool(::ReadFile(_handle, data, size, &processedLoc, NULL));
339  processedSize = (UInt32)processedLoc;
340  return res;
341}
342
343bool CInFile::ReadPart(void *data, UInt32 size, UInt32 &processedSize) throw()
344{
345  if (size > kChunkSizeMax)
346    size = kChunkSizeMax;
347  return Read1(data, size, processedSize);
348}
349
350bool CInFile::Read(void *data, UInt32 size, UInt32 &processedSize) throw()
351{
352  processedSize = 0;
353  do
354  {
355    UInt32 processedLoc = 0;
356    bool res = ReadPart(data, size, processedLoc);
357    processedSize += processedLoc;
358    if (!res)
359      return false;
360    if (processedLoc == 0)
361      return true;
362    data = (void *)((unsigned char *)data + processedLoc);
363    size -= processedLoc;
364  }
365  while (size > 0);
366  return true;
367}
368
369// ---------- COutFile ---------
370
371static inline DWORD GetCreationDisposition(bool createAlways)
372  { return createAlways? CREATE_ALWAYS: CREATE_NEW; }
373
374bool COutFile::Open(CFSTR fileName, DWORD shareMode, DWORD creationDisposition, DWORD flagsAndAttributes)
375  { return CFileBase::Create(fileName, GENERIC_WRITE, shareMode, creationDisposition, flagsAndAttributes); }
376
377bool COutFile::Open(CFSTR fileName, DWORD creationDisposition)
378  { return Open(fileName, FILE_SHARE_READ, creationDisposition, FILE_ATTRIBUTE_NORMAL); }
379
380bool COutFile::Create(CFSTR fileName, bool createAlways)
381  { return Open(fileName, GetCreationDisposition(createAlways)); }
382
383bool COutFile::CreateAlways(CFSTR fileName, DWORD flagsAndAttributes)
384  { return Open(fileName, FILE_SHARE_READ, GetCreationDisposition(true), flagsAndAttributes); }
385
386bool COutFile::SetTime(const FILETIME *cTime, const FILETIME *aTime, const FILETIME *mTime) throw()
387  { return BOOLToBool(::SetFileTime(_handle, cTime, aTime, mTime)); }
388
389bool COutFile::SetMTime(const FILETIME *mTime) throw() {  return SetTime(NULL, NULL, mTime); }
390
391bool COutFile::WritePart(const void *data, UInt32 size, UInt32 &processedSize) throw()
392{
393  if (size > kChunkSizeMax)
394    size = kChunkSizeMax;
395  DWORD processedLoc = 0;
396  bool res = BOOLToBool(::WriteFile(_handle, data, size, &processedLoc, NULL));
397  processedSize = (UInt32)processedLoc;
398  return res;
399}
400
401bool COutFile::Write(const void *data, UInt32 size, UInt32 &processedSize) throw()
402{
403  processedSize = 0;
404  do
405  {
406    UInt32 processedLoc = 0;
407    bool res = WritePart(data, size, processedLoc);
408    processedSize += processedLoc;
409    if (!res)
410      return false;
411    if (processedLoc == 0)
412      return true;
413    data = (const void *)((const unsigned char *)data + processedLoc);
414    size -= processedLoc;
415  }
416  while (size > 0);
417  return true;
418}
419
420bool COutFile::SetEndOfFile() throw() { return BOOLToBool(::SetEndOfFile(_handle)); }
421
422bool COutFile::SetLength(UInt64 length) throw()
423{
424  UInt64 newPosition;
425  if (!Seek(length, newPosition))
426    return false;
427  if (newPosition != length)
428    return false;
429  return SetEndOfFile();
430}
431
432}}}
433