| #ifdef TEST_FTRUNCATE64 |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <sys/stat.h> |
| #endif /* TEST_FTRUNCATE64 */ |
| |
| #include <unistd.h> |
| #include <io.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <wchar.h> |
| #include <windows.h> |
| #include <psapi.h> |
| |
| /* Mutually exclusive methods |
| We check disk space as truncating more than the allowed space results |
| in file getting mysteriously deleted |
| */ |
| #define _CHECK_SPACE_BY_VOLUME_METHOD_ 1 /* Needs to walk through all volumes */ |
| #define _CHECK_SPACE_BY_PSAPI_METHOD_ 0 /* Requires psapi.dll */ |
| #define _CHECK_SPACE_BY_VISTA_METHOD_ 0 /* Won't work on XP */ |
| |
| #if (_CHECK_SPACE_BY_PSAPI_METHOD_ == 1) /* Retrive actual volume path */ |
| static LPWSTR getdirpath(const LPWSTR __str){ |
| int len, walk = 0; |
| LPWSTR dirname; |
| while (__str[walk] != L'\0'){ |
| walk++; |
| if (__str[walk] == L'\\') len = walk + 1; |
| } |
| dirname = calloc(len + 1, sizeof(wchar_t)); |
| if (!dirname) return dirname; /* memory error */ |
| return wcsncpy(dirname,__str,len); |
| } |
| |
| static LPWSTR xp_normalize_fn(const LPWSTR fn) { |
| DWORD len, err, walker, isfound; |
| LPWSTR drives = NULL; |
| LPWSTR target = NULL; |
| LPWSTR ret = NULL; |
| wchar_t tmplt[3] = L" :"; /* Template */ |
| |
| /*Get list of drive letters */ |
| len = GetLogicalDriveStringsW(0,NULL); |
| drives = calloc(len,sizeof(wchar_t)); |
| if (!drives) return NULL; |
| len = GetLogicalDriveStringsW(len,drives); |
| |
| /*Allocatate memory */ |
| target = calloc(MAX_PATH + 1,sizeof(wchar_t)); |
| if (!target) { |
| free(drives); |
| return NULL; |
| } |
| |
| walker = 0; |
| while ((walker < len) && !(drives[walker] == L'\0' && drives[walker + 1] == L'\0')){ |
| /* search through alphabets */ |
| if(iswalpha(drives[walker])) { |
| *tmplt = drives[walker]; /* Put drive letter */ |
| err = QueryDosDeviceW(tmplt,target,MAX_PATH); |
| if(!err) { |
| free(drives); |
| free(target); |
| return NULL; |
| } |
| if( _wcsnicmp(target,fn,wcslen(target)) == 0) break; |
| wmemset(target,L'\0',MAX_PATH); |
| walker++; |
| } else walker++; |
| } |
| |
| if (!iswalpha(*tmplt)) { |
| free(drives); |
| free(target); |
| return NULL; /* Finish walking without finding correct drive */ |
| } |
| |
| ret = calloc(MAX_PATH + 1,sizeof(wchar_t)); |
| if (!ret) { |
| free(drives); |
| free(target); |
| return NULL; |
| } |
| _snwprintf(ret,MAX_PATH,L"%ws%ws",tmplt,fn+wcslen(target)); |
| |
| return ret; |
| } |
| |
| /* XP method of retrieving filename from handles, based on: |
| http://msdn.microsoft.com/en-us/library/aa366789%28VS.85%29.aspx |
| */ |
| static LPWSTR xp_getfilepath(const HANDLE f, const LARGE_INTEGER fsize){ |
| HANDLE hFileMap = NULL; |
| void* pMem = NULL; |
| LPWSTR temp, ret; |
| DWORD err; |
| |
| temp = calloc(MAX_PATH + 1, sizeof(wchar_t)); |
| if (!temp) goto errormap; |
| |
| /* CreateFileMappingW limitation: Cannot map 0 byte files, so extend it to 1 byte */ |
| if (!fsize.QuadPart) { |
| SetFilePointer(f, 1, NULL, FILE_BEGIN); |
| err = SetEndOfFile(f); |
| if(!temp) goto errormap; |
| } |
| |
| hFileMap = CreateFileMappingW(f,NULL,PAGE_READONLY,0,1,NULL); |
| if(!hFileMap) goto errormap; |
| pMem = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 1); |
| if(!pMem) goto errormap; |
| err = GetMappedFileNameW(GetCurrentProcess(),pMem,temp,MAX_PATH); |
| if(!err) goto errormap; |
| |
| if (pMem) UnmapViewOfFile(pMem); |
| if (hFileMap) CloseHandle(hFileMap); |
| ret = xp_normalize_fn(temp); |
| free(temp); |
| return ret; |
| |
| errormap: |
| if (temp) free(temp); |
| if (pMem) UnmapViewOfFile(pMem); |
| if (hFileMap) CloseHandle(hFileMap); |
| _set_errno(EBADF); |
| return NULL; |
| } |
| #endif /* _CHECK_SPACE_BY_PSAPI_METHOD_ */ |
| |
| static int checkfreespace(const HANDLE f, const ULONGLONG requiredspace){ |
| LPWSTR dirpath, volumeid, volumepath; |
| ULARGE_INTEGER freespace; |
| LARGE_INTEGER currentsize; |
| DWORD check, volumeserial; |
| BY_HANDLE_FILE_INFORMATION fileinfo; |
| HANDLE vol; |
| |
| /* Get current size */ |
| check = GetFileSizeEx(f,¤tsize); |
| if (!check) { |
| _set_errno(EBADF); |
| return -1; /* Error checking file size */ |
| } |
| |
| /* Short circuit disk space check if shrink operation */ |
| if ((ULONGLONG)currentsize.QuadPart >= requiredspace) |
| return 0; |
| |
| /* We check available space to user before attempting to truncate */ |
| |
| #if (_CHECK_SPACE_BY_VISTA_METHOD_ == 1) |
| /* Get path length */ |
| DWORD err; |
| LPWSTR filepath = NULL; |
| check = GetFinalPathNameByHandleW(f,filepath,0,FILE_NAME_NORMALIZED|VOLUME_NAME_GUID); |
| err = GetLastError(); |
| if (err == ERROR_PATH_NOT_FOUND || err == ERROR_INVALID_PARAMETER) { |
| _set_errno(EINVAL); |
| return -1; /* IO error */ |
| } |
| filepath = calloc(check + 1,sizeof(wchar_t)); |
| if (!filepath) { |
| _set_errno(EBADF); |
| return -1; /* Out of memory */ |
| } |
| check = GetFinalPathNameByHandleW(f,filepath,check,FILE_NAME_NORMALIZED|VOLUME_NAME_GUID); |
| /* FIXME: last error was set to error 87 (0x57) |
| "The parameter is incorrect." for some reason but works out */ |
| if (!check) { |
| _set_errno(EBADF); |
| return -1; /* Error resolving filename */ |
| } |
| #endif /* _CHECK_SPACE_BY_VISTA_METHOD_ */ |
| |
| #if (_CHECK_SPACE_BY_PSAPI_METHOD_ == 1) |
| LPWSTR filepath = NULL; |
| filepath = xp_getfilepath(f,currentsize); |
| |
| /* Get durectory path */ |
| dirpath = getdirpath(filepath); |
| free(filepath); |
| filepath = NULL; |
| if (!dirpath) { |
| _set_errno(EBADF); |
| return -1; /* Out of memory */ |
| } |
| #endif /* _CHECK_SPACE_BY_PSAPI_METHOD_ */ |
| |
| #if _CHECK_SPACE_BY_VOLUME_METHOD_ |
| if(!GetFileInformationByHandle(f,&fileinfo)) { |
| _set_errno(EINVAL); |
| return -1; /* Resolution failure */ |
| } |
| |
| volumeid = calloc(51,sizeof(wchar_t)); |
| volumepath = calloc(MAX_PATH+2,sizeof(wchar_t)); |
| if(!volumeid || !volumepath) { |
| _set_errno(EBADF); |
| return -1; /* Out of memory */ |
| } |
| |
| dirpath = NULL; |
| |
| vol = FindFirstVolumeW(volumeid,50); |
| /* wprintf(L"%d - %ws\n",wcslen(volumeid),volumeid); */ |
| do { |
| check = GetVolumeInformationW(volumeid,volumepath,MAX_PATH+1,&volumeserial,NULL,NULL,NULL,0); |
| /* wprintf(L"GetVolumeInformationW %d id %ws path %ws error %d\n",check,volumeid,volumepath,GetLastError()); */ |
| if(volumeserial == fileinfo.dwVolumeSerialNumber) { |
| dirpath = volumeid; |
| break; |
| } |
| } while (FindNextVolumeW(vol,volumeid,50)); |
| FindVolumeClose(vol); |
| |
| if(!dirpath) free(volumeid); /* we found the volume */ |
| free(volumepath); |
| #endif /* _CHECK_SPACE_BY_VOLUME_METHOD_ */ |
| |
| /* Get available free space */ |
| check = GetDiskFreeSpaceExW(dirpath,&freespace,NULL,NULL); |
| wprintf(L"freespace %I64u\n",freespace); |
| free(dirpath); |
| if(!check) { |
| _set_errno(EFBIG); |
| return -1; /* Error getting free space */ |
| } |
| |
| /* Check space requirements */ |
| if (requiredspace - currentsize.QuadPart > freespace.QuadPart) { |
| _set_errno(EFBIG); /* File too big for disk */ |
| return -1; |
| } /* We have enough space to truncate/expand */ |
| return 0; |
| } |
| |
| int ftruncate64(int __fd, _off64_t __length) { |
| HANDLE f; |
| LARGE_INTEGER quad; |
| DWORD check; |
| int ret = 0; |
| |
| /* Sanity check */ |
| if (__length < 0) { |
| _set_errno(EINVAL); |
| return -1; |
| } |
| |
| /* Get Win32 Handle */ |
| if(__fd == -1) return __fd; |
| f = (HANDLE)_get_osfhandle(__fd); |
| if (f == INVALID_HANDLE_VALUE || (GetFileType(f) != FILE_TYPE_DISK)) { |
| _set_errno(EBADF); |
| return -1; |
| } |
| |
| /* Check available space */ |
| check = checkfreespace(f,__length); |
| if (check != 0) { |
| return -1; /* Error, errno already set */ |
| } |
| |
| quad.QuadPart = __length; |
| check = SetFilePointer(f, (LONG)quad.LowPart, &(quad.HighPart), FILE_BEGIN); |
| if (check == INVALID_SET_FILE_POINTER && quad.LowPart != INVALID_SET_FILE_POINTER) { |
| switch (GetLastError()) { |
| case ERROR_NEGATIVE_SEEK: |
| _set_errno(EFBIG); /* file too big? */ |
| return -1; |
| case INVALID_SET_FILE_POINTER: |
| _set_errno(EINVAL); /* shouldn't happen */ |
| return -1; |
| default: |
| _set_errno(EINVAL); /* shouldn't happen */ |
| return -1; |
| } |
| } |
| |
| check = SetEndOfFile(f); |
| if (!check) { |
| check = GetLastError(); |
| _set_errno(EINVAL); |
| ret = -1; |
| } |
| return ret; |
| } |
| |
| #ifdef TEST_FTRUNCATE64 |
| int main(){ |
| LARGE_INTEGER sz; |
| ULARGE_INTEGER freespace; |
| int f; |
| LPWSTR path, dir; |
| sz.QuadPart = 0LL; |
| f = _open("XXX.tmp", _O_BINARY|_O_CREAT|_O_RDWR, _S_IREAD | _S_IWRITE); |
| wprintf(L"%d\n",ftruncate64(f,12)); |
| wprintf(L"%d\n",ftruncate64(f,20)); |
| wprintf(L"%d\n",ftruncate64(f,15)); |
| /* path = xp_getfilepath((HANDLE)_get_osfhandle(f),sz); |
| dir = getdirpath(path); |
| GetDiskFreeSpaceExW(dir,&freespace,NULL,NULL); |
| wprintf(L"fs - %ws\n",path); |
| wprintf(L"dirfs - %ws\n",dir); |
| wprintf(L"free - %I64u\n",freespace.QuadPart); |
| free(dir); |
| free(path);*/ |
| _close(f); |
| return 0; |
| } |
| #endif /* TEST_FTRUNCATE64 */ |