1 #include <precomp.h>
2 #include "autofd.h"
3 #include "autodir.h"
4 
5 // Due to common implementation considerations, autofd and autodir's implementation are both in this file
6 
7 // Translate utf-8 to wide characters
a2u(const char * str)8 static auto_array<wchar_t> a2u( const char *str )
9 {
10     int newlen=MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, str, -1, NULL, 0 );
11 
12     if( newlen==0 ) {
13         throw( rscerror( _T("Ansi to Unicode conversion error"), Error2errno(GetLastError())) );
14     }
15 
16     auto_array<wchar_t> buffer(new wchar_t[newlen+1]);
17 
18     if( MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, str, -1, buffer.get(), newlen )==0 ) {
19         throw( rscerror( _T("Ansi to Unicode conversion error"), Error2errno(GetLastError())) );
20     }
21 
22     buffer[newlen]=L'\0';
23 
24     return buffer;
25 }
26 
27 // Unconditionally convert from Wide to ANSI
u2a(const wchar_t * str)28 static auto_array<char> u2a( const wchar_t *str )
29 {
30     int newlen=WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, str, -1, NULL, 0, NULL, NULL );
31 
32     if( newlen==0 ) {
33         throw( rscerror( _T("Unicode to Ansi conversion error"), GetLastError()) );
34     }
35 
36     auto_array<char> buffer(new char[newlen+1]);
37 
38     if( WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, str, -1, buffer.get(), newlen, NULL, NULL )==0 ) {
39         throw( rscerror( _T("Ansi to Unicode conversion error"), GetLastError()) );
40     }
41 
42     buffer[newlen]='\0';
43 
44     return buffer;
45 }
46 
47 
ut2ft(time_t unixtime)48 FILETIME autofd::ut2ft( time_t unixtime )
49 {
50     ULARGE_INTEGER res;
51     static const ULONGLONG epoch_start=116444736000000000;
52 
53     res.QuadPart=unixtime;
54     res.QuadPart*=10*1000*1000;
55     res.QuadPart+=epoch_start;
56 
57     FILETIME ret;
58 
59     ret.dwHighDateTime=res.HighPart;
60     ret.dwLowDateTime=res.LowPart;
61 
62     return ret;
63 }
64 
ft2ut(FILETIME ft)65 time_t autofd::ft2ut( FILETIME ft )
66 {
67     // Converts FILETIME to time_t
68     static const ULONGLONG epoch_start=116444736000000000;
69     ULARGE_INTEGER unified_ft;
70     unified_ft.LowPart=ft.dwLowDateTime;
71     unified_ft.HighPart=ft.dwHighDateTime;
72     if( unified_ft.QuadPart<epoch_start )
73         return -1;
74 
75     unified_ft.QuadPart-=epoch_start;
76     unified_ft.QuadPart/=10*1000*1000;
77     if( unified_ft.HighPart!=0 )
78         return -1;
79 
80     return unified_ft.LowPart;
81 }
82 
autofd(const char * pathname,int flags,mode_t mode,bool utf8)83 autofd::autofd( const char *pathname, int flags, mode_t mode, bool utf8 ) : f_eof(false)
84 {
85     DWORD access=0, disposition=0;
86 
87     WIN32_FILE_ATTRIBUTE_DATA file_attr;
88     if( GetFileAttributesEx( pathname, GetFileExInfoStandard, &file_attr ) ) {
89         if( file_attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) {
90             // Cannot open a directory
91             throw EXCEPT_CLASS("file open failed", EISDIR, pathname );
92         }
93     }
94 
95 #define CHECK_MASK(a) ((flags&(a))==(a))
96     if( CHECK_MASK(O_CREAT|O_EXCL) )
97         disposition=CREATE_NEW;
98     else if( CHECK_MASK(O_CREAT|O_TRUNC) )
99         disposition=CREATE_ALWAYS;
100     else if( CHECK_MASK(O_CREAT) )
101         disposition=OPEN_ALWAYS;
102     else if( CHECK_MASK(O_TRUNC) )
103         disposition=TRUNCATE_EXISTING;
104     else
105         disposition=OPEN_EXISTING;
106 
107     switch( flags&O_ACCMODE ) {
108         case O_RDONLY:
109             access=GENERIC_READ;
110             break;
111         case O_WRONLY:
112             access=GENERIC_WRITE;
113             break;
114         case O_RDWR:
115             access=GENERIC_READ|GENERIC_WRITE;
116             break;
117     }
118 
119     HANDLE newfile;
120     if( !utf8 )
121         newfile=CreateFileA(pathname, access,
122             FILE_SHARE_READ|FILE_SHARE_WRITE, // We are a unix program at heart
123             NULL, disposition, FILE_ATTRIBUTE_NORMAL, NULL );
124     else {
125         // Need to translate the file name to unicode
126         newfile=CreateFileW(a2u(pathname).get(), access,
127             FILE_SHARE_READ|FILE_SHARE_WRITE, // We are a unix program at heart
128             NULL, disposition, FILE_ATTRIBUTE_NORMAL, NULL );
129     }
130     static_cast<autohandle &>(*this)=autohandle(newfile);
131 
132     if( *this==INVALID_HANDLE_VALUE )
133         throw EXCEPT_CLASS("file open failed", Error2errno(GetLastError()), pathname );
134 }
135 
read(file_t fd,void * buf,size_t count)136 ssize_t autofd::read( file_t fd, void *buf, size_t count )
137 {
138     DWORD ures;
139     if( !ReadFile( fd, buf, count, &ures, NULL ) && GetLastError()!=ERROR_BROKEN_PIPE )
140         throw rscerror("read failed", Error2errno(GetLastError()));
141 
142     return ures;
143 }
144 
read(void * buf,size_t count) const145 ssize_t autofd::read( void *buf, size_t count ) const
146 {
147     ssize_t num=read( *static_cast<const autohandle *>(this), buf, count );
148 
149     if( num==0 )
150         f_eof=true;
151 
152     return num;
153 }
154 
write(file_t fd,const void * buf,size_t count)155 ssize_t autofd::write( file_t fd, const void *buf, size_t count )
156 {
157     DWORD written;
158     if( !WriteFile( fd, buf, count, &written, NULL ) )
159         throw rscerror("write failed", Error2errno(GetLastError()));
160 
161     return written;
162 }
163 
stat(const char * file_name,bool utf8)164 struct stat autofd::stat( const char *file_name, bool utf8 )
165 {
166     struct stat ret;
167     WIN32_FILE_ATTRIBUTE_DATA data;
168 
169     if( !utf8 ) {
170         if( !GetFileAttributesExA( file_name, GetFileExInfoStandard, &data ) )
171             throw rscerror("stat failed", Error2errno(GetLastError()), file_name);
172     } else {
173         if( !GetFileAttributesExW( a2u(file_name).get(), GetFileExInfoStandard, &data ) )
174             throw rscerror("stat failed", Error2errno(GetLastError()), file_name);
175     }
176 
177     ZeroMemory( &ret, sizeof(ret) );
178     ret.st_atime=ft2ut(data.ftLastAccessTime);
179     ret.st_ctime=ft2ut(data.ftCreationTime);
180     ret.st_mtime=ft2ut(data.ftLastWriteTime);
181     ret.st_dev=0;
182     ret.st_rdev=0;
183 
184     if( data.dwFileAttributes&FILE_ATTRIBUTE_REPARSE_POINT ) {
185         // The Vista equivalent of a symbolic link, more or less
186         ret.st_mode=S_IFLNK;
187     } else if( data.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY ) {
188         ret.st_mode=S_IFDIR;
189     } else {
190         ret.st_mode=S_IFREG;
191     }
192 
193     ret.st_nlink=1;
194     ret.st_size=data.nFileSizeHigh;
195     ret.st_size<<=32;
196     ret.st_size|=data.nFileSizeLow;
197 
198     return ret;
199 }
200 
fstat() const201 struct stat autofd::fstat() const
202 {
203     struct stat ret;
204     BY_HANDLE_FILE_INFORMATION info;
205 
206     if( !GetFileInformationByHandle(*this, &info ) )
207         throw rscerror("stat failed", Error2errno(GetLastError()));
208 
209     ZeroMemory(&ret, sizeof(ret));
210     ret.st_atime=ft2ut(info.ftLastAccessTime);
211     ret.st_ctime=ft2ut(info.ftCreationTime);
212     ret.st_dev=0; // For a device - handle. Otherwise 0
213     ret.st_mode=S_IFREG; // unix mode
214     ret.st_mtime=ft2ut(info.ftLastWriteTime);
215     ret.st_nlink=static_cast<short>(info.nNumberOfLinks); // nlink
216     ret.st_rdev=0; // same as dev
217     ret.st_size=info.nFileSizeHigh;
218     ret.st_size<<=32;
219     ret.st_size|=info.nFileSizeLow;
220 
221     return ret;
222 }
223 
lseek(file_t file,off_t offset,int whence)224 off_t autofd::lseek( file_t file, off_t offset, int whence )
225 {
226     DWORD dwMoveMethod=0;
227 
228     switch( whence ) {
229 case SEEK_SET:
230     dwMoveMethod=FILE_BEGIN;
231     break;
232 case SEEK_CUR:
233     dwMoveMethod=FILE_CURRENT;
234     break;
235 case SEEK_END:
236     dwMoveMethod=FILE_END;
237     break;
238 default:
239     throw rscerror("Invalid whence given", EINVAL);
240     }
241 
242     LONG offsethigh, offsetlow;
243     offsetlow=static_cast<LONG>(offset);
244     offsethigh=static_cast<LONG>(offset>>32);
245     offsetlow=SetFilePointer( file, offsetlow, &offsethigh, dwMoveMethod );
246 
247     offset=offsethigh;
248     offset<<=32;
249     offset|=offsetlow;
250 
251     return offset;
252 }
253 
utimes(const char * filename,const struct timeval tv[2],bool utf8)254 int autofd::utimes( const char *filename, const struct timeval tv[2], bool utf8)
255 {
256     FILETIME modtime, accesstime;
257 
258     accesstime=ut2ft(tv[0].tv_sec);
259     modtime=ut2ft(tv[1].tv_sec);
260 
261     // The only function in Windows that sets file modification/access times does so for
262     // open files only, so we have no choice but to open the file for write access
263     autofd file(filename, O_WRONLY, utf8);
264     if( SetFileTime(file, NULL, &accesstime, &modtime ) )
265         return 0;
266     else {
267         errno=Error2errno(GetLastError());
268         return -1;
269     }
270 }
271 
dup(int filedes)272 autofd autofd::dup( int filedes )
273 {
274     autofd orig_std(GetStdHandle(filedes));
275     autofd ret(orig_std.Duplicate(false));
276     orig_std.release();
277     return ret;
278 }
279 
rmdir(const char * pathname,bool utf8)280 void autofd::rmdir( const char *pathname, bool utf8 )
281 {
282     if( !(utf8?RemoveDirectoryW(a2u(pathname).get()):RemoveDirectoryA(pathname)) ) {
283         DWORD error=GetLastError();
284 
285         if( error!=ERROR_FILE_NOT_FOUND && error!=ERROR_PATH_NOT_FOUND )
286             throw rscerror("Error removing directory", Error2errno(error), pathname);
287     }
288 }
289 
mv(const char * src,const char * dst,bool utf8)290 void autofd::mv( const char *src, const char *dst, bool utf8 ) {
291     if( !(utf8?
292             MoveFileExW( a2u(src).get(), a2u(dst).get(), MOVEFILE_REPLACE_EXISTING):
293             MoveFileExA( src, dst, MOVEFILE_REPLACE_EXISTING)) )
294     {
295         throw rscerror("rename failed", Error2errno(GetLastError()), dst );
296     }
297 }
298 
unlink(const char * pathname,bool utf8)299 int autofd::unlink(const char *pathname, bool utf8)
300 {
301     DWORD error=ERROR_SUCCESS;
302     if( !(utf8?DeleteFileW( a2u(pathname).get() ):DeleteFileA( pathname ))
303         && (error=GetLastError())!=ERROR_FILE_NOT_FOUND )
304             throw rscerror("Erasing file", Error2errno(GetLastError()), pathname );
305 
306     if( error!=ERROR_SUCCESS ) {
307         errno=Error2errno(error);
308 
309         return -1;
310     }
311 
312     return 0;
313 }
314 
315     // Recursively create directories
316     // mode is the permissions of the end directory
317     // int_mode is the permissions of all intermediately created dirs
mkpath_actual(const std::string & path,mode_t mode,bool utf8)318 static void mkpath_actual(const std::string &path, mode_t mode, bool utf8)
319 {
320     BOOL res;
321     if( utf8 )
322         res=CreateDirectoryW( a2u(path.c_str()).get(), NULL );
323     else
324         res=CreateDirectoryA( path.c_str(), NULL );
325 
326     if( !res && GetLastError()!=ERROR_ALREADY_EXISTS ) {
327         // "Creating" a drive letter may fail for a whole host of reasons while actually succeeding
328         if( path.length()!=2 || path[1]!=':' ||
329             // Got this far in the "if" only if we tried to create something of the form C:
330             // Only a ERROR_INVALID_DRIVE actually means an error
331             GetLastError()==ERROR_INVALID_DRIVE )
332 
333             throw rscerror("mkdir failed", Error2errno(GetLastError()), path.c_str() );
334     }
335 }
336 
mkpath(const char * path,mode_t mode,bool utf8)337 void autofd::mkpath(const char *path, mode_t mode, bool utf8)
338 {
339     if( path[0]!='\0' ) {
340         for( int sublen=0; path[sublen]!='\0'; sublen++ ) {
341             if( sublen>0 && path[sublen]==DIRSEP_C && path[sublen+1]!=DIRSEP_C ) {
342                 std::string subpath(path, sublen);
343                 mkpath_actual(subpath, mode, utf8);
344             }
345         }
346 
347         mkpath_actual(path, mode, utf8);
348     }
349 }
350 
351 // Return the dir part of the name
dirpart(const char * path,bool utf8)352 int autofd::dirpart( const char *path, bool utf8 )
353 {
354     int i, last=0;
355 
356     for( i=0; path[i]!='\0'; ++i ) {
357         if( path[i]==DIRSEP_C )
358             last=i;
359     }
360 
361     return last;
362 }
363 
combine_paths(const char * left,const char * right,bool utf8)364 std::string autofd::combine_paths( const char *left, const char *right, bool utf8 )
365 {
366     std::string ret(left);
367 
368     int i;
369     // Trim trailing slashes
370     for( i=ret.length()-1; i>0 && ret[i]==DIRSEP_C; --i )
371         ;
372 
373     ret.resize(++i);
374     if( i>0 )
375         ret+=DIRSEP_S;
376 
377     // Trim leading slashes
378     for( i=0; right[i]==DIRSEP_C; ++i )
379         ;
380     ret+=right+i;
381 
382     return ret;
383 }
384 
readline() const385 std::string autofd::readline() const
386 {
387     std::string ret;
388     char ch;
389 
390     while( read( &ch, 1 )==1 && ch!='\n' ) {
391         ret+=ch;
392     }
393 
394     if( ch=='\n' && ret.length()>0 && ret[ret.length()-1]=='\r' )
395         ret.resize(ret.length()-1);
396 
397     return ret;
398 }
399 
400 // autodir implementation
autodir(const char * dirname,bool _utf8)401 autodir::autodir( const char *dirname, bool _utf8 ) : eof(false), utf8(_utf8)
402 {
403     WIN32_FILE_ATTRIBUTE_DATA file_attr;
404     if( GetFileAttributesEx( dirname, GetFileExInfoStandard, &file_attr ) ) {
405         if( !(file_attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ) {
406             // Cannot open a directory
407             throw EXCEPT_CLASS("opendir failed", ENOTDIR, dirname );
408         }
409     }
410 
411     if( utf8 ) {
412         WIN32_FIND_DATAW wide_find_data;
413         h_dirscan=FindFirstFileW((std::basic_string<wchar_t>(a2u(dirname).get())+L"\\*").c_str(), &wide_find_data );
414 
415         if( h_dirscan!=INVALID_HANDLE_VALUE )
416             data_w2a(&wide_find_data);
417     } else
418         h_dirscan=FindFirstFile((std::string(dirname)+"\\*").c_str(), &finddata );
419 
420 #if defined(EXCEPT_CLASS)
421     if( h_dirscan==INVALID_HANDLE_VALUE )
422         throw rscerror("opendir failed", Error2errno(GetLastError()), dirname);
423 #endif
424 }
425 
read()426 struct dirent *autodir::read()
427 {
428     if( !eof ) {
429         strcpy_s(posixdir.d_name, finddata.cFileName);
430 
431         BOOL res;
432         if( utf8 ) {
433             WIN32_FIND_DATAW wide_data;
434             res=FindNextFileW(h_dirscan, &wide_data);
435 
436             if( res )
437                 data_w2a(&wide_data);
438         } else {
439             res=FindNextFileA(h_dirscan, &finddata);
440         }
441 
442         if( !res ) {
443             eof=true;
444             DWORD error=GetLastError();
445             if( error!=ERROR_NO_MORE_FILES ) {
446                 throw rscerror("Error getting directory listing", Error2errno(error));
447             }
448         }
449         return &posixdir;
450     } else {
451         return NULL;
452     }
453 }
454 
data_w2a(const WIN32_FIND_DATAW * data)455 void autodir::data_w2a( const WIN32_FIND_DATAW *data )
456 {
457     // Copy all identical file elements
458     finddata.dwFileAttributes=data->dwFileAttributes;
459     finddata.ftCreationTime=data->ftCreationTime;
460     finddata.ftLastAccessTime=data->ftLastAccessTime;
461     finddata.ftLastWriteTime=data->ftLastWriteTime;
462     finddata.nFileSizeHigh=data->nFileSizeHigh;
463     finddata.nFileSizeLow=data->nFileSizeLow;
464     finddata.dwReserved0=data->dwReserved0;
465     finddata.dwReserved1=data->dwReserved1;
466 
467     // XXX What happens if the name is too long?
468     strncpy( finddata.cFileName, u2a(data->cFileName).get(), MAX_PATH );
469 
470     for( int i=0; i<sizeof(finddata.cAlternateFileName); ++i ) {
471         // The short path is only ASCII characters
472         finddata.cAlternateFileName[i]=static_cast<CHAR>(data->cAlternateFileName[i]);
473     }
474 }
475