1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "apr.h"
18#include <aclapi.h>
19#include "apr_private.h"
20#include "apr_arch_file_io.h"
21#include "apr_file_io.h"
22#include "apr_general.h"
23#include "apr_strings.h"
24#include "apr_errno.h"
25#include "apr_time.h"
26#include <sys/stat.h>
27#include "apr_arch_atime.h"
28#include "apr_arch_misc.h"
29
30/* We have to assure that the file name contains no '*'s, or other
31 * wildcards when using FindFirstFile to recover the true file name.
32 */
33static apr_status_t test_safe_name(const char *name)
34{
35    /* Only accept ':' in the second position of the filename,
36     * as the drive letter delimiter:
37     */
38    if (apr_isalpha(*name) && (name[1] == ':')) {
39        name += 2;
40    }
41    while (*name) {
42        if (!IS_FNCHAR(*name) && (*name != '\\') && (*name != '/')) {
43            if (*name == '?' || *name == '*')
44                return APR_EPATHWILD;
45            else
46                return APR_EBADPATH;
47        }
48        ++name;
49    }
50    return APR_SUCCESS;
51}
52
53static apr_status_t free_localheap(void *heap) {
54    LocalFree(heap);
55    return APR_SUCCESS;
56}
57
58static apr_gid_t worldid = NULL;
59
60static void free_world(void)
61{
62    if (worldid) {
63        FreeSid(worldid);
64        worldid = NULL;
65    }
66}
67
68/* Left bit shifts from World scope to given scope */
69typedef enum prot_scope_e {
70    prot_scope_world = 0,
71    prot_scope_group = 4,
72    prot_scope_user =  8
73} prot_scope_e;
74
75static apr_fileperms_t convert_prot(ACCESS_MASK acc, prot_scope_e scope)
76{
77    /* These choices are based on the single filesystem bit that controls
78     * the given behavior.  They are -not- recommended for any set protection
79     * function, such a function should -set- use GENERIC_READ/WRITE/EXECUTE
80     */
81    apr_fileperms_t prot = 0;
82    if (acc & FILE_EXECUTE)
83        prot |= APR_WEXECUTE;
84    if (acc & FILE_WRITE_DATA)
85        prot |= APR_WWRITE;
86    if (acc & FILE_READ_DATA)
87        prot |= APR_WREAD;
88    return (prot << scope);
89}
90
91static void resolve_prot(apr_finfo_t *finfo, apr_int32_t wanted, PACL dacl)
92{
93    TRUSTEE_W ident = {NULL, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID};
94    ACCESS_MASK acc;
95    /*
96     * This function is only invoked for WinNT,
97     * there is no reason for os_level testing here.
98     */
99    if ((wanted & APR_FINFO_WPROT) && !worldid) {
100        SID_IDENTIFIER_AUTHORITY SIDAuth = {SECURITY_WORLD_SID_AUTHORITY};
101        if (AllocateAndInitializeSid(&SIDAuth, 1, SECURITY_WORLD_RID,
102                                     0, 0, 0, 0, 0, 0, 0, &worldid))
103            atexit(free_world);
104        else
105            worldid = NULL;
106    }
107    if ((wanted & APR_FINFO_UPROT) && (finfo->valid & APR_FINFO_USER)) {
108        ident.TrusteeType = TRUSTEE_IS_USER;
109        ident.ptstrName = finfo->user;
110        /* GetEffectiveRightsFromAcl isn't supported under Win9x,
111         * which shouldn't come as a surprize.  Since we are passing
112         * TRUSTEE_IS_SID, always skip the A->W layer.
113         */
114        if (GetEffectiveRightsFromAclW(dacl, &ident, &acc) == ERROR_SUCCESS) {
115            finfo->protection |= convert_prot(acc, prot_scope_user);
116            finfo->valid |= APR_FINFO_UPROT;
117        }
118    }
119    /* Windows NT: did not return group rights.
120     * Windows 2000 returns group rights information.
121     * Since WinNT kernels don't follow the unix model of
122     * group associations, this all all pretty mute.
123     */
124    if ((wanted & APR_FINFO_GPROT) && (finfo->valid & APR_FINFO_GROUP)) {
125        ident.TrusteeType = TRUSTEE_IS_GROUP;
126        ident.ptstrName = finfo->group;
127        if (GetEffectiveRightsFromAclW(dacl, &ident, &acc) == ERROR_SUCCESS) {
128            finfo->protection |= convert_prot(acc, prot_scope_group);
129            finfo->valid |= APR_FINFO_GPROT;
130        }
131    }
132    if ((wanted & APR_FINFO_WPROT) && (worldid)) {
133        ident.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
134        ident.ptstrName = worldid;
135        if (GetEffectiveRightsFromAclW(dacl, &ident, &acc) == ERROR_SUCCESS) {
136            finfo->protection |= convert_prot(acc, prot_scope_world);
137            finfo->valid |= APR_FINFO_WPROT;
138        }
139    }
140}
141
142static apr_status_t resolve_ident(apr_finfo_t *finfo, const char *fname,
143                                  apr_int32_t wanted, apr_pool_t *pool)
144{
145    apr_file_t *thefile = NULL;
146    apr_status_t rv;
147    /*
148     * NT5 (W2K) only supports symlinks in the same manner as mount points.
149     * This code should eventually take that into account, for now treat
150     * every reparse point as a symlink...
151     *
152     * We must open the file with READ_CONTROL if we plan to retrieve the
153     * user, group or permissions.
154     */
155
156    if ((rv = apr_file_open(&thefile, fname, APR_OPENINFO
157                          | ((wanted & APR_FINFO_LINK) ? APR_OPENLINK : 0)
158                          | ((wanted & (APR_FINFO_PROT | APR_FINFO_OWNER))
159                                ? APR_READCONTROL : 0),
160                            APR_OS_DEFAULT, pool)) == APR_SUCCESS) {
161        rv = apr_file_info_get(finfo, wanted, thefile);
162        finfo->filehand = NULL;
163        apr_file_close(thefile);
164    }
165    else if (APR_STATUS_IS_EACCES(rv) && (wanted & (APR_FINFO_PROT
166                                                  | APR_FINFO_OWNER))) {
167        /* We have a backup plan.  Perhaps we couldn't grab READ_CONTROL?
168         * proceed without asking for that permission...
169         */
170        if ((rv = apr_file_open(&thefile, fname, APR_OPENINFO
171                              | ((wanted & APR_FINFO_LINK) ? APR_OPENLINK : 0),
172                                APR_OS_DEFAULT, pool)) == APR_SUCCESS) {
173            rv = apr_file_info_get(finfo, wanted & ~(APR_FINFO_PROT
174                                                 | APR_FINFO_OWNER),
175                                 thefile);
176            finfo->filehand = NULL;
177            apr_file_close(thefile);
178        }
179    }
180
181    if (rv != APR_SUCCESS && rv != APR_INCOMPLETE)
182        return (rv);
183
184    /* We picked up this case above and had opened the link's properties */
185    if (wanted & APR_FINFO_LINK)
186        finfo->valid |= APR_FINFO_LINK;
187
188    return rv;
189}
190
191static apr_status_t guess_protection_bits(apr_finfo_t *finfo,
192                                          apr_int32_t wanted)
193{
194    /* Read, write execute for owner.  In the Win9x environment, any
195     * readable file is executable (well, not entirely 100% true, but
196     * still looking for some cheap logic that would help us here.)
197     * The same holds on NT if a file doesn't have a DACL (e.g., on FAT)
198     */
199    if (finfo->protection & APR_FREADONLY) {
200        finfo->protection |= APR_WREAD | APR_WEXECUTE;
201    }
202    else {
203        finfo->protection |= APR_WREAD | APR_WEXECUTE | APR_WWRITE;
204    }
205    finfo->protection |= (finfo->protection << prot_scope_group)
206                       | (finfo->protection << prot_scope_user);
207
208    finfo->valid |= APR_FINFO_UPROT | APR_FINFO_GPROT | APR_FINFO_WPROT;
209
210    return ((wanted & ~finfo->valid) ? APR_INCOMPLETE : APR_SUCCESS);
211}
212
213apr_status_t more_finfo(apr_finfo_t *finfo, const void *ufile,
214                        apr_int32_t wanted, int whatfile)
215{
216    PSID user = NULL, grp = NULL;
217    PACL dacl = NULL;
218    apr_status_t rv;
219
220    if (apr_os_level < APR_WIN_NT)
221        return guess_protection_bits(finfo, wanted);
222
223    if (wanted & (APR_FINFO_PROT | APR_FINFO_OWNER))
224    {
225        /* On NT this request is incredibly expensive, but accurate.
226         * Since the WinNT-only functions below are protected by the
227         * (apr_os_level < APR_WIN_NT) case above, we need no extra
228         * tests, but remember GetNamedSecurityInfo & GetSecurityInfo
229         * are not supported on 9x.
230         */
231        SECURITY_INFORMATION sinf = 0;
232        PSECURITY_DESCRIPTOR pdesc = NULL;
233        if (wanted & (APR_FINFO_USER | APR_FINFO_UPROT))
234            sinf |= OWNER_SECURITY_INFORMATION;
235        if (wanted & (APR_FINFO_GROUP | APR_FINFO_GPROT))
236            sinf |= GROUP_SECURITY_INFORMATION;
237        if (wanted & APR_FINFO_PROT)
238            sinf |= DACL_SECURITY_INFORMATION;
239        if (whatfile == MORE_OF_WFSPEC) {
240            apr_wchar_t *wfile = (apr_wchar_t*) ufile;
241            int fix = 0;
242            if (wcsncmp(wfile, L"\\\\?\\", 4) == 0) {
243                fix = 4;
244                if (wcsncmp(wfile + fix, L"UNC\\", 4) == 0)
245                    wfile[6] = L'\\', fix = 6;
246            }
247            rv = GetNamedSecurityInfoW(wfile + fix,
248                                 SE_FILE_OBJECT, sinf,
249                                 ((wanted & (APR_FINFO_USER | APR_FINFO_UPROT)) ? &user : NULL),
250                                 ((wanted & (APR_FINFO_GROUP | APR_FINFO_GPROT)) ? &grp : NULL),
251                                 ((wanted & APR_FINFO_PROT) ? &dacl : NULL),
252                                 NULL, &pdesc);
253            if (fix == 6)
254                wfile[6] = L'C';
255        }
256        else if (whatfile == MORE_OF_FSPEC)
257            rv = GetNamedSecurityInfoA((char*)ufile,
258                                 SE_FILE_OBJECT, sinf,
259                                 ((wanted & (APR_FINFO_USER | APR_FINFO_UPROT)) ? &user : NULL),
260                                 ((wanted & (APR_FINFO_GROUP | APR_FINFO_GPROT)) ? &grp : NULL),
261                                 ((wanted & APR_FINFO_PROT) ? &dacl : NULL),
262                                 NULL, &pdesc);
263        else if (whatfile == MORE_OF_HANDLE)
264            rv = GetSecurityInfo((HANDLE)ufile,
265                                 SE_FILE_OBJECT, sinf,
266                                 ((wanted & (APR_FINFO_USER | APR_FINFO_UPROT)) ? &user : NULL),
267                                 ((wanted & (APR_FINFO_GROUP | APR_FINFO_GPROT)) ? &grp : NULL),
268                                 ((wanted & APR_FINFO_PROT) ? &dacl : NULL),
269                                 NULL, &pdesc);
270        else
271            return APR_INCOMPLETE; /* should not occur */
272        if (rv == ERROR_SUCCESS)
273            apr_pool_cleanup_register(finfo->pool, pdesc, free_localheap,
274                                 apr_pool_cleanup_null);
275        else
276            user = grp = dacl = NULL;
277
278        if (user) {
279            finfo->user = user;
280            finfo->valid |= APR_FINFO_USER;
281        }
282
283        if (grp) {
284            finfo->group = grp;
285            finfo->valid |= APR_FINFO_GROUP;
286        }
287
288        if (dacl) {
289            /* Retrieved the discresionary access list */
290            resolve_prot(finfo, wanted, dacl);
291        }
292        else if (wanted & APR_FINFO_PROT)
293            guess_protection_bits(finfo, wanted);
294    }
295
296    if ((apr_os_level >= APR_WIN_2000) && (wanted & APR_FINFO_CSIZE)
297                                       && (finfo->filetype == APR_REG))
298    {
299        DWORD sizelo, sizehi;
300        if (whatfile == MORE_OF_HANDLE) {
301            /* Not available for development and implementation under
302             * a reasonable license; if you review the licensing
303             * terms and conditions of;
304             *   http://go.microsoft.com/fwlink/?linkid=84083
305             * you probably understand why APR chooses not to implement.
306             */
307            IOSB sb;
308            FSI fi;
309            if ((ZwQueryInformationFile((HANDLE)ufile, &sb,
310                                       &fi, sizeof(fi), 5) == 0)
311                    && (sb.Status == 0)) {
312                finfo->csize = fi.AllocationSize;
313                finfo->valid |= APR_FINFO_CSIZE;
314            }
315        }
316        else {
317            SetLastError(NO_ERROR);
318            if (whatfile == MORE_OF_WFSPEC)
319                sizelo = GetCompressedFileSizeW((apr_wchar_t*)ufile, &sizehi);
320            else if (whatfile == MORE_OF_FSPEC)
321                sizelo = GetCompressedFileSizeA((char*)ufile, &sizehi);
322            else
323                return APR_EGENERAL; /* should not occur */
324
325            if (sizelo != INVALID_FILE_SIZE || GetLastError() == NO_ERROR) {
326#if APR_HAS_LARGE_FILES
327                finfo->csize =  (apr_off_t)sizelo
328                             | ((apr_off_t)sizehi << 32);
329#else
330                finfo->csize = (apr_off_t)sizelo;
331                if (finfo->csize < 0 || sizehi)
332                    finfo->csize = 0x7fffffff;
333#endif
334                finfo->valid |= APR_FINFO_CSIZE;
335            }
336        }
337    }
338    return ((wanted & ~finfo->valid) ? APR_INCOMPLETE : APR_SUCCESS);
339}
340
341
342/* This generic fillin depends upon byhandle to be passed as 0 when
343 * a WIN32_FILE_ATTRIBUTE_DATA or either WIN32_FIND_DATA [A or W] is
344 * passed for wininfo.  When the BY_HANDLE_FILE_INFORMATION structure
345 * is passed for wininfo, byhandle is passed as 1 to offset the one
346 * dword discrepancy in offset of the High/Low size structure members.
347 *
348 * The generic fillin returns 1 if the caller should further inquire
349 * if this is a CHR filetype.  If it's reasonably certain it can't be,
350 * then the function returns 0.
351 */
352int fillin_fileinfo(apr_finfo_t *finfo,
353                    WIN32_FILE_ATTRIBUTE_DATA *wininfo,
354                    int byhandle, apr_int32_t wanted)
355{
356    DWORD *sizes = &wininfo->nFileSizeHigh + byhandle;
357    int warn = 0;
358
359    memset(finfo, '\0', sizeof(*finfo));
360
361    FileTimeToAprTime(&finfo->atime, &wininfo->ftLastAccessTime);
362    FileTimeToAprTime(&finfo->ctime, &wininfo->ftCreationTime);
363    FileTimeToAprTime(&finfo->mtime, &wininfo->ftLastWriteTime);
364
365#if APR_HAS_LARGE_FILES
366    finfo->size =  (apr_off_t)sizes[1]
367                | ((apr_off_t)sizes[0] << 32);
368#else
369    finfo->size = (apr_off_t)sizes[1];
370    if (finfo->size < 0 || sizes[0])
371        finfo->size = 0x7fffffff;
372#endif
373
374    if (wanted & APR_FINFO_LINK &&
375        wininfo->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
376        finfo->filetype = APR_LNK;
377    }
378    else if (wininfo->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
379        finfo->filetype = APR_DIR;
380    }
381    else if (wininfo->dwFileAttributes & FILE_ATTRIBUTE_DEVICE) {
382        /* Warning: This test only succeeds on Win9x, on NT these files
383         * (con, aux, nul, lpt#, com# etc) escape early detection!
384         */
385        finfo->filetype = APR_CHR;
386    }
387    else {
388        /* Warning: Short of opening the handle to the file, the 'FileType'
389         * appears to be unknowable (in any trustworthy or consistent sense)
390         * on WinNT/2K as far as PIPE, CHR, etc are concerned.
391         */
392        if (!wininfo->ftLastWriteTime.dwLowDateTime
393                && !wininfo->ftLastWriteTime.dwHighDateTime
394                && !finfo->size)
395            warn = 1;
396        finfo->filetype = APR_REG;
397    }
398
399    /* The following flags are [for this moment] private to Win32.
400     * That's the only excuse for not toggling valid bits to reflect them.
401     */
402    if (wininfo->dwFileAttributes & FILE_ATTRIBUTE_READONLY)
403        finfo->protection = APR_FREADONLY;
404
405    finfo->valid = APR_FINFO_ATIME | APR_FINFO_CTIME | APR_FINFO_MTIME
406                 | APR_FINFO_SIZE  | APR_FINFO_TYPE;   /* == APR_FINFO_MIN */
407
408    /* Only byhandle optionally tests link targets, so tell that caller
409     * what it wants to hear, otherwise the byattributes is never
410     * reporting anything but the link.
411     */
412    if (!byhandle || (wanted & APR_FINFO_LINK))
413        finfo->valid |= APR_FINFO_LINK;
414    return warn;
415}
416
417
418APR_DECLARE(apr_status_t) apr_file_info_get(apr_finfo_t *finfo, apr_int32_t wanted,
419                                            apr_file_t *thefile)
420{
421    BY_HANDLE_FILE_INFORMATION FileInfo;
422
423    if (thefile->buffered) {
424        /* XXX: flush here is not mutex protected */
425        apr_status_t rv = apr_file_flush(thefile);
426        if (rv != APR_SUCCESS)
427            return rv;
428    }
429
430    if (!GetFileInformationByHandle(thefile->filehand, &FileInfo)) {
431        return apr_get_os_error();
432    }
433
434    fillin_fileinfo(finfo, (WIN32_FILE_ATTRIBUTE_DATA *) &FileInfo, 1, wanted);
435
436    if (finfo->filetype == APR_REG)
437    {
438        /* Go the extra mile to be -certain- that we have a real, regular
439         * file, since the attribute bits aren't a certain thing.  Even
440         * though fillin should have hinted if we *must* do this, we
441         * don't need to take chances while the handle is already open.
442         */
443        DWORD FileType;
444        if ((FileType = GetFileType(thefile->filehand))) {
445            if (FileType == FILE_TYPE_CHAR) {
446                finfo->filetype = APR_CHR;
447            }
448            else if (FileType == FILE_TYPE_PIPE) {
449                finfo->filetype = APR_PIPE;
450            }
451            /* Otherwise leave the original conclusion alone
452             */
453        }
454    }
455
456    finfo->pool = thefile->pool;
457
458    /* ### The finfo lifetime may exceed the lifetime of thefile->pool
459     * but finfo's aren't managed in pools, so where on earth would
460     * we pstrdup the fname into???
461     */
462    finfo->fname = thefile->fname;
463
464    /* Extra goodies known only by GetFileInformationByHandle() */
465    finfo->inode  =  (apr_ino_t)FileInfo.nFileIndexLow
466                  | ((apr_ino_t)FileInfo.nFileIndexHigh << 32);
467    finfo->device = FileInfo.dwVolumeSerialNumber;
468    finfo->nlink  = FileInfo.nNumberOfLinks;
469
470    finfo->valid |= APR_FINFO_IDENT | APR_FINFO_NLINK;
471
472    /* If we still want something more (besides the name) go get it!
473     */
474    if ((wanted &= ~finfo->valid) & ~APR_FINFO_NAME) {
475        return more_finfo(finfo, thefile->filehand, wanted, MORE_OF_HANDLE);
476    }
477
478    return APR_SUCCESS;
479}
480
481APR_DECLARE(apr_status_t) apr_file_perms_set(const char *fname,
482                                           apr_fileperms_t perms)
483{
484    return APR_ENOTIMPL;
485}
486
487APR_DECLARE(apr_status_t) apr_stat(apr_finfo_t *finfo, const char *fname,
488                                   apr_int32_t wanted, apr_pool_t *pool)
489{
490    /* XXX: is constant - needs testing - which requires a lighter-weight root test fn */
491    int isroot = 0;
492    apr_status_t ident_rv = 0;
493    apr_status_t rv;
494#if APR_HAS_UNICODE_FS
495    apr_wchar_t wfname[APR_PATH_MAX];
496
497#endif
498    char *filename = NULL;
499    /* These all share a common subset of this structure */
500    union {
501        WIN32_FIND_DATAW w;
502        WIN32_FIND_DATAA n;
503        WIN32_FILE_ATTRIBUTE_DATA i;
504    } FileInfo;
505
506    /* Catch fname length == MAX_PATH since GetFileAttributesEx fails
507     * with PATH_NOT_FOUND.  We would rather indicate length error than
508     * 'not found'
509     */
510    if (strlen(fname) >= APR_PATH_MAX) {
511        return APR_ENAMETOOLONG;
512    }
513
514#if APR_HAS_UNICODE_FS
515    IF_WIN_OS_IS_UNICODE
516    {
517        if ((wanted & (APR_FINFO_IDENT | APR_FINFO_NLINK))
518               || (~wanted & APR_FINFO_LINK)) {
519            /* FindFirstFile and GetFileAttributesEx can't figure the inode,
520             * device or number of links, so we need to resolve with an open
521             * file handle.  If the user has asked for these fields, fall over
522             * to the get file info by handle method.  If we fail, or the user
523             * also asks for the file name, continue by our usual means.
524             *
525             * We also must use this method for a 'true' stat, that resolves
526             * a symlink (NTFS Junction) target.  This is because all fileinfo
527             * on a Junction always returns the junction, opening the target
528             * is the only way to resolve the target's attributes.
529             */
530            if ((ident_rv = resolve_ident(finfo, fname, wanted, pool))
531                    == APR_SUCCESS)
532                return ident_rv;
533            else if (ident_rv == APR_INCOMPLETE)
534                wanted &= ~finfo->valid;
535        }
536
537        if ((rv = utf8_to_unicode_path(wfname, sizeof(wfname)
538                                            / sizeof(apr_wchar_t), fname)))
539            return rv;
540        if (!(wanted & APR_FINFO_NAME)) {
541            if (!GetFileAttributesExW(wfname, GetFileExInfoStandard,
542                                      &FileInfo.i))
543                return apr_get_os_error();
544        }
545        else {
546            /* Guard against bogus wildcards and retrieve by name
547             * since we want the true name, and set aside a long
548             * enough string to handle the longest file name.
549             */
550            char tmpname[APR_FILE_MAX * 3 + 1];
551            HANDLE hFind;
552            if ((rv = test_safe_name(fname)) != APR_SUCCESS) {
553                return rv;
554            }
555            hFind = FindFirstFileW(wfname, &FileInfo.w);
556            if (hFind == INVALID_HANDLE_VALUE)
557                return apr_get_os_error();
558            FindClose(hFind);
559            if (unicode_to_utf8_path(tmpname, sizeof(tmpname),
560                                     FileInfo.w.cFileName)) {
561                return APR_ENAMETOOLONG;
562            }
563            filename = apr_pstrdup(pool, tmpname);
564        }
565    }
566#endif
567#if APR_HAS_ANSI_FS
568    ELSE_WIN_OS_IS_ANSI
569    {
570        char *root = NULL;
571        const char *test = fname;
572        rv = apr_filepath_root(&root, &test, APR_FILEPATH_NATIVE, pool);
573        isroot = (root && *root && !(*test));
574
575        if ((apr_os_level >= APR_WIN_98) && (!(wanted & APR_FINFO_NAME) || isroot))
576        {
577            /* cannot use FindFile on a Win98 root, it returns \*
578             * GetFileAttributesExA is not available on Win95
579             */
580            if (!GetFileAttributesExA(fname, GetFileExInfoStandard,
581                                     &FileInfo.i)) {
582                return apr_get_os_error();
583            }
584        }
585        else if (isroot) {
586            /* This is Win95 and we are trying to stat a root.  Lie.
587             */
588            if (GetDriveType(fname) != DRIVE_UNKNOWN)
589            {
590                finfo->pool = pool;
591                finfo->filetype = 0;
592                finfo->mtime = apr_time_now();
593                finfo->protection |= APR_WREAD | APR_WEXECUTE | APR_WWRITE;
594                finfo->protection |= (finfo->protection << prot_scope_group)
595                                   | (finfo->protection << prot_scope_user);
596                finfo->valid |= APR_FINFO_TYPE | APR_FINFO_PROT
597                              | APR_FINFO_MTIME
598                              | (wanted & APR_FINFO_LINK);
599                return (wanted &= ~finfo->valid) ? APR_INCOMPLETE
600                                                 : APR_SUCCESS;
601            }
602            else
603                return APR_FROM_OS_ERROR(ERROR_PATH_NOT_FOUND);
604        }
605        else  {
606            /* Guard against bogus wildcards and retrieve by name
607             * since we want the true name, or are stuck in Win95,
608             * or are looking for the root of a Win98 drive.
609             */
610            HANDLE hFind;
611            if ((rv = test_safe_name(fname)) != APR_SUCCESS) {
612                return rv;
613            }
614            hFind = FindFirstFileA(fname, &FileInfo.n);
615            if (hFind == INVALID_HANDLE_VALUE) {
616                return apr_get_os_error();
617    	    }
618            FindClose(hFind);
619            filename = apr_pstrdup(pool, FileInfo.n.cFileName);
620        }
621    }
622#endif
623
624    if (ident_rv != APR_INCOMPLETE) {
625        if (fillin_fileinfo(finfo, (WIN32_FILE_ATTRIBUTE_DATA *) &FileInfo,
626                            0, wanted))
627        {
628            /* Go the extra mile to assure we have a file.  WinNT/2000 seems
629             * to reliably translate char devices to the path '\\.\device'
630             * so go ask for the full path.
631             */
632            if (apr_os_level >= APR_WIN_NT)
633            {
634#if APR_HAS_UNICODE_FS
635                apr_wchar_t tmpname[APR_FILE_MAX];
636                apr_wchar_t *tmpoff = NULL;
637                if (GetFullPathNameW(wfname, sizeof(tmpname) / sizeof(apr_wchar_t),
638                                     tmpname, &tmpoff))
639                {
640                    if (!wcsncmp(tmpname, L"\\\\.\\", 4)) {
641#else
642                /* Same initial logic as above, but
643                 * only for WinNT/non-UTF-8 builds of APR:
644                 */
645                char tmpname[APR_FILE_MAX];
646                char *tmpoff;
647                if (GetFullPathName(fname, sizeof(tmpname), tmpname, &tmpoff))
648                {
649                    if (!strncmp(tmpname, "\\\\.\\", 4)) {
650#endif
651                        if (tmpoff == tmpname + 4) {
652                            finfo->filetype = APR_CHR;
653                        }
654                        /* For WHATEVER reason, CHR devices such as \\.\con
655                         * or \\.\lpt1 *may*not* update tmpoff; in fact the
656                         * resulting tmpoff is set to NULL.  Guard against
657                         * either case.
658                         *
659                         * This code is identical for wide and narrow chars...
660                         */
661                        else if (!tmpoff) {
662                            tmpoff = tmpname + 4;
663                            while (*tmpoff) {
664                                if (*tmpoff == '\\' || *tmpoff == '/') {
665                                    break;
666                                }
667                                ++tmpoff;
668                            }
669                            if (!*tmpoff) {
670                                finfo->filetype = APR_CHR;
671                            }
672                        }
673                    }
674                }
675                else {
676                    finfo->valid &= ~APR_FINFO_TYPE;
677                }
678
679            }
680            else {
681                finfo->valid &= ~APR_FINFO_TYPE;
682            }
683        }
684        finfo->pool = pool;
685    }
686
687    if (filename && !isroot) {
688        finfo->name = filename;
689        finfo->valid |= APR_FINFO_NAME;
690    }
691
692    if (wanted &= ~finfo->valid) {
693        /* Caller wants more than APR_FINFO_MIN | APR_FINFO_NAME */
694#if APR_HAS_UNICODE_FS
695        if (apr_os_level >= APR_WIN_NT)
696            return more_finfo(finfo, wfname, wanted, MORE_OF_WFSPEC);
697#endif
698        return more_finfo(finfo, fname, wanted, MORE_OF_FSPEC);
699    }
700
701    return APR_SUCCESS;
702}
703
704APR_DECLARE(apr_status_t) apr_file_attrs_set(const char *fname,
705                                             apr_fileattrs_t attributes,
706                                             apr_fileattrs_t attr_mask,
707                                             apr_pool_t *pool)
708{
709    DWORD flags;
710    apr_status_t rv;
711#if APR_HAS_UNICODE_FS
712    apr_wchar_t wfname[APR_PATH_MAX];
713#endif
714
715    /* Don't do anything if we can't handle the requested attributes */
716    if (!(attr_mask & (APR_FILE_ATTR_READONLY
717                       | APR_FILE_ATTR_HIDDEN)))
718        return APR_SUCCESS;
719
720#if APR_HAS_UNICODE_FS
721    IF_WIN_OS_IS_UNICODE
722    {
723        if ((rv = utf8_to_unicode_path(wfname,
724                                       sizeof(wfname) / sizeof(wfname[0]),
725                                       fname)))
726            return rv;
727        flags = GetFileAttributesW(wfname);
728    }
729#endif
730#if APR_HAS_ANSI_FS
731    ELSE_WIN_OS_IS_ANSI
732    {
733        flags = GetFileAttributesA(fname);
734    }
735#endif
736
737    if (flags == 0xFFFFFFFF)
738        return apr_get_os_error();
739
740    if (attr_mask & APR_FILE_ATTR_READONLY)
741    {
742        if (attributes & APR_FILE_ATTR_READONLY)
743            flags |= FILE_ATTRIBUTE_READONLY;
744        else
745            flags &= ~FILE_ATTRIBUTE_READONLY;
746    }
747
748    if (attr_mask & APR_FILE_ATTR_HIDDEN)
749    {
750        if (attributes & APR_FILE_ATTR_HIDDEN)
751            flags |= FILE_ATTRIBUTE_HIDDEN;
752        else
753            flags &= ~FILE_ATTRIBUTE_HIDDEN;
754    }
755
756#if APR_HAS_UNICODE_FS
757    IF_WIN_OS_IS_UNICODE
758    {
759        rv = SetFileAttributesW(wfname, flags);
760    }
761#endif
762#if APR_HAS_ANSI_FS
763    ELSE_WIN_OS_IS_ANSI
764    {
765        rv = SetFileAttributesA(fname, flags);
766    }
767#endif
768
769    if (rv == 0)
770        return apr_get_os_error();
771
772    return APR_SUCCESS;
773}
774
775
776APR_DECLARE(apr_status_t) apr_file_mtime_set(const char *fname,
777                                             apr_time_t mtime,
778                                             apr_pool_t *pool)
779{
780    apr_file_t *thefile;
781    apr_status_t rv;
782
783    rv = apr_file_open(&thefile, fname,
784                       APR_FOPEN_READ | APR_WRITEATTRS,
785                       APR_OS_DEFAULT, pool);
786    if (!rv)
787    {
788        FILETIME file_ctime;
789        FILETIME file_atime;
790        FILETIME file_mtime;
791
792        if (!GetFileTime(thefile->filehand,
793                         &file_ctime, &file_atime, &file_mtime))
794            rv = apr_get_os_error();
795        else
796        {
797            AprTimeToFileTime(&file_mtime, mtime);
798            if (!SetFileTime(thefile->filehand,
799                             &file_ctime, &file_atime, &file_mtime))
800                rv = apr_get_os_error();
801        }
802
803        apr_file_close(thefile);
804    }
805
806    return rv;
807}
808