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 */
test_safe_name(const char * name)33 static 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
free_localheap(void * heap)53 static apr_status_t free_localheap(void *heap) {
54 LocalFree(heap);
55 return APR_SUCCESS;
56 }
57
58 static apr_gid_t worldid = NULL;
59
free_world(void)60 static 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 */
69 typedef enum prot_scope_e {
70 prot_scope_world = 0,
71 prot_scope_group = 4,
72 prot_scope_user = 8
73 } prot_scope_e;
74
convert_prot(ACCESS_MASK acc,prot_scope_e scope)75 static 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
resolve_prot(apr_finfo_t * finfo,apr_int32_t wanted,PACL dacl)91 static 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
resolve_ident(apr_finfo_t * finfo,const char * fname,apr_int32_t wanted,apr_pool_t * pool)142 static 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
guess_protection_bits(apr_finfo_t * finfo,apr_int32_t wanted)191 static 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
more_finfo(apr_finfo_t * finfo,const void * ufile,apr_int32_t wanted,int whatfile)213 apr_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 */
fillin_fileinfo(apr_finfo_t * finfo,WIN32_FILE_ATTRIBUTE_DATA * wininfo,int byhandle,apr_int32_t wanted)352 int 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
apr_file_info_get(apr_finfo_t * finfo,apr_int32_t wanted,apr_file_t * thefile)418 APR_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
apr_file_perms_set(const char * fname,apr_fileperms_t perms)481 APR_DECLARE(apr_status_t) apr_file_perms_set(const char *fname,
482 apr_fileperms_t perms)
483 {
484 return APR_ENOTIMPL;
485 }
486
apr_stat(apr_finfo_t * finfo,const char * fname,apr_int32_t wanted,apr_pool_t * pool)487 APR_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
704 APR_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
776 APR_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