Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(853)

Side by Side Diff: nspr/pr/src/md/windows/w95io.c

Issue 2078763002: Delete bundled copy of NSS and replace with README. (Closed) Base URL: https://chromium.googlesource.com/chromium/deps/nss@master
Patch Set: Delete bundled copy of NSS and replace with README. Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « nspr/pr/src/md/windows/w95dllmain.c ('k') | nspr/pr/src/md/windows/w95sock.c » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 /* Windows 95 IO module
7 *
8 * Assumes synchronous I/O.
9 *
10 */
11
12 #include "primpl.h"
13 #include <direct.h>
14 #include <mbstring.h>
15 #ifdef MOZ_UNICODE
16 #include <wchar.h>
17 #endif /* MOZ_UNICODE */
18
19 struct _MDLock _pr_ioq_lock;
20
21 /*
22 * NSPR-to-NT access right mapping table for files.
23 */
24 static DWORD fileAccessTable[] = {
25 FILE_GENERIC_READ,
26 FILE_GENERIC_WRITE,
27 FILE_GENERIC_EXECUTE
28 };
29
30 /*
31 * NSPR-to-NT access right mapping table for directories.
32 */
33 static DWORD dirAccessTable[] = {
34 FILE_GENERIC_READ,
35 FILE_GENERIC_WRITE|FILE_DELETE_CHILD,
36 FILE_GENERIC_EXECUTE
37 };
38
39 static PRBool IsPrevCharSlash(const char *str, const char *current);
40
41 void
42 _PR_MD_INIT_IO()
43 {
44 WORD WSAVersion = 0x0101;
45 WSADATA WSAData;
46 int err;
47
48 err = WSAStartup( WSAVersion, &WSAData );
49 PR_ASSERT(0 == err);
50
51 #ifdef DEBUG
52 /* Doublecheck _pr_filetime_offset's hard-coded value is correct. */
53 {
54 SYSTEMTIME systime;
55 union {
56 PRTime prt;
57 FILETIME ft;
58 } filetime;
59 BOOL rv;
60
61 systime.wYear = 1970;
62 systime.wMonth = 1;
63 /* wDayOfWeek is ignored */
64 systime.wDay = 1;
65 systime.wHour = 0;
66 systime.wMinute = 0;
67 systime.wSecond = 0;
68 systime.wMilliseconds = 0;
69
70 rv = SystemTimeToFileTime(&systime, &filetime.ft);
71 PR_ASSERT(0 != rv);
72 PR_ASSERT(filetime.prt == _pr_filetime_offset);
73 }
74 #endif /* DEBUG */
75
76 _PR_NT_InitSids();
77
78 _PR_MD_InitSockets();
79 }
80
81 PRStatus
82 _PR_MD_WAIT(PRThread *thread, PRIntervalTime ticks)
83 {
84 DWORD rv;
85
86 PRUint32 msecs = (ticks == PR_INTERVAL_NO_TIMEOUT) ?
87 INFINITE : PR_IntervalToMilliseconds(ticks);
88 rv = WaitForSingleObject(thread->md.blocked_sema, msecs);
89 switch(rv)
90 {
91 case WAIT_OBJECT_0:
92 return PR_SUCCESS;
93 case WAIT_TIMEOUT:
94 _PR_THREAD_LOCK(thread);
95 if (thread->state == _PR_IO_WAIT) {
96 ;
97 } else {
98 if (thread->wait.cvar != NULL) {
99 thread->wait.cvar = NULL;
100 _PR_THREAD_UNLOCK(thread);
101 } else {
102 /* The CVAR was notified just as the timeout
103 * occurred. This led to us being notified twice.
104 * call WaitForSingleObject() to clear the semaphore.
105 */
106 _PR_THREAD_UNLOCK(thread);
107 rv = WaitForSingleObject(thread->md.blocked_sema, 0);
108 PR_ASSERT(rv == WAIT_OBJECT_0);
109 }
110 }
111 return PR_SUCCESS;
112 default:
113 return PR_FAILURE;
114 }
115 }
116 PRStatus
117 _PR_MD_WAKEUP_WAITER(PRThread *thread)
118 {
119 if ( _PR_IS_NATIVE_THREAD(thread) )
120 {
121 if (ReleaseSemaphore(thread->md.blocked_sema, 1, NULL) == FALSE)
122 return PR_FAILURE;
123 else
124 return PR_SUCCESS;
125 }
126 }
127
128
129 /* --- FILE IO ----------------------------------------------------------- */
130 /*
131 * _PR_MD_OPEN() -- Open a file
132 *
133 * returns: a fileHandle
134 *
135 * The NSPR open flags (osflags) are translated into flags for Win95
136 *
137 * Mode seems to be passed in as a unix style file permissions argument
138 * as in 0666, in the case of opening the logFile.
139 *
140 */
141 PROsfd
142 _PR_MD_OPEN(const char *name, PRIntn osflags, int mode)
143 {
144 HANDLE file;
145 PRInt32 access = 0;
146 PRInt32 flags = 0;
147 PRInt32 flag6 = 0;
148
149 if (osflags & PR_SYNC) flag6 = FILE_FLAG_WRITE_THROUGH;
150
151 if (osflags & PR_RDONLY || osflags & PR_RDWR)
152 access |= GENERIC_READ;
153 if (osflags & PR_WRONLY || osflags & PR_RDWR)
154 access |= GENERIC_WRITE;
155
156 if ( osflags & PR_CREATE_FILE && osflags & PR_EXCL )
157 flags = CREATE_NEW;
158 else if (osflags & PR_CREATE_FILE) {
159 if (osflags & PR_TRUNCATE)
160 flags = CREATE_ALWAYS;
161 else
162 flags = OPEN_ALWAYS;
163 } else {
164 if (osflags & PR_TRUNCATE)
165 flags = TRUNCATE_EXISTING;
166 else
167 flags = OPEN_EXISTING;
168 }
169
170 file = CreateFileA(name,
171 access,
172 FILE_SHARE_READ|FILE_SHARE_WRITE,
173 NULL,
174 flags,
175 flag6,
176 NULL);
177 if (file == INVALID_HANDLE_VALUE) {
178 _PR_MD_MAP_OPEN_ERROR(GetLastError());
179 return -1;
180 }
181
182 return (PROsfd)file;
183 }
184
185 PROsfd
186 _PR_MD_OPEN_FILE(const char *name, PRIntn osflags, int mode)
187 {
188 HANDLE file;
189 PRInt32 access = 0;
190 PRInt32 flags = 0;
191 PRInt32 flag6 = 0;
192 SECURITY_ATTRIBUTES sa;
193 LPSECURITY_ATTRIBUTES lpSA = NULL;
194 PSECURITY_DESCRIPTOR pSD = NULL;
195 PACL pACL = NULL;
196
197 if (osflags & PR_CREATE_FILE) {
198 if (_PR_NT_MakeSecurityDescriptorACL(mode, fileAccessTable,
199 &pSD, &pACL) == PR_SUCCESS) {
200 sa.nLength = sizeof(sa);
201 sa.lpSecurityDescriptor = pSD;
202 sa.bInheritHandle = FALSE;
203 lpSA = &sa;
204 }
205 }
206
207 if (osflags & PR_SYNC) flag6 = FILE_FLAG_WRITE_THROUGH;
208
209 if (osflags & PR_RDONLY || osflags & PR_RDWR)
210 access |= GENERIC_READ;
211 if (osflags & PR_WRONLY || osflags & PR_RDWR)
212 access |= GENERIC_WRITE;
213
214 if ( osflags & PR_CREATE_FILE && osflags & PR_EXCL )
215 flags = CREATE_NEW;
216 else if (osflags & PR_CREATE_FILE) {
217 if (osflags & PR_TRUNCATE)
218 flags = CREATE_ALWAYS;
219 else
220 flags = OPEN_ALWAYS;
221 } else {
222 if (osflags & PR_TRUNCATE)
223 flags = TRUNCATE_EXISTING;
224 else
225 flags = OPEN_EXISTING;
226 }
227
228 file = CreateFileA(name,
229 access,
230 FILE_SHARE_READ|FILE_SHARE_WRITE,
231 lpSA,
232 flags,
233 flag6,
234 NULL);
235 if (lpSA != NULL) {
236 _PR_NT_FreeSecurityDescriptorACL(pSD, pACL);
237 }
238 if (file == INVALID_HANDLE_VALUE) {
239 _PR_MD_MAP_OPEN_ERROR(GetLastError());
240 return -1;
241 }
242
243 return (PROsfd)file;
244 }
245
246 PRInt32
247 _PR_MD_READ(PRFileDesc *fd, void *buf, PRInt32 len)
248 {
249 PRUint32 bytes;
250 int rv, err;
251
252 rv = ReadFile((HANDLE)fd->secret->md.osfd,
253 (LPVOID)buf,
254 len,
255 &bytes,
256 NULL);
257
258 if (rv == 0)
259 {
260 err = GetLastError();
261 /* ERROR_HANDLE_EOF can only be returned by async io */
262 PR_ASSERT(err != ERROR_HANDLE_EOF);
263 if (err == ERROR_BROKEN_PIPE)
264 return 0;
265 else {
266 _PR_MD_MAP_READ_ERROR(err);
267 return -1;
268 }
269 }
270 return bytes;
271 }
272
273 PRInt32
274 _PR_MD_WRITE(PRFileDesc *fd, const void *buf, PRInt32 len)
275 {
276 PROsfd f = fd->secret->md.osfd;
277 PRInt32 bytes;
278 int rv;
279
280 rv = WriteFile((HANDLE)f,
281 buf,
282 len,
283 &bytes,
284 NULL );
285
286 if (rv == 0)
287 {
288 _PR_MD_MAP_WRITE_ERROR(GetLastError());
289 return -1;
290 }
291 return bytes;
292 } /* --- end _PR_MD_WRITE() --- */
293
294 PROffset32
295 _PR_MD_LSEEK(PRFileDesc *fd, PROffset32 offset, PRSeekWhence whence)
296 {
297 DWORD moveMethod;
298 PROffset32 rv;
299
300 switch (whence) {
301 case PR_SEEK_SET:
302 moveMethod = FILE_BEGIN;
303 break;
304 case PR_SEEK_CUR:
305 moveMethod = FILE_CURRENT;
306 break;
307 case PR_SEEK_END:
308 moveMethod = FILE_END;
309 break;
310 default:
311 PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
312 return -1;
313 }
314
315 rv = SetFilePointer((HANDLE)fd->secret->md.osfd, offset, NULL, moveMethod);
316
317 /*
318 * If the lpDistanceToMoveHigh argument (third argument) is
319 * NULL, SetFilePointer returns 0xffffffff on failure.
320 */
321 if (-1 == rv) {
322 _PR_MD_MAP_LSEEK_ERROR(GetLastError());
323 }
324 return rv;
325 }
326
327 PROffset64
328 _PR_MD_LSEEK64(PRFileDesc *fd, PROffset64 offset, PRSeekWhence whence)
329 {
330 DWORD moveMethod;
331 LARGE_INTEGER li;
332 DWORD err;
333
334 switch (whence) {
335 case PR_SEEK_SET:
336 moveMethod = FILE_BEGIN;
337 break;
338 case PR_SEEK_CUR:
339 moveMethod = FILE_CURRENT;
340 break;
341 case PR_SEEK_END:
342 moveMethod = FILE_END;
343 break;
344 default:
345 PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
346 return -1;
347 }
348
349 li.QuadPart = offset;
350 li.LowPart = SetFilePointer((HANDLE)fd->secret->md.osfd,
351 li.LowPart, &li.HighPart, moveMethod);
352
353 if (0xffffffff == li.LowPart && (err = GetLastError()) != NO_ERROR) {
354 _PR_MD_MAP_LSEEK_ERROR(err);
355 li.QuadPart = -1;
356 }
357 return li.QuadPart;
358 }
359
360 /*
361 * This is documented to succeed on read-only files, but Win32's
362 * FlushFileBuffers functions fails with "access denied" in such a
363 * case. So we only signal an error if the error is *not* "access
364 * denied".
365 */
366 PRInt32
367 _PR_MD_FSYNC(PRFileDesc *fd)
368 {
369 /*
370 * From the documentation:
371 *
372 * On Windows NT, the function FlushFileBuffers fails if hFile
373 * is a handle to console output. That is because console
374 * output is not buffered. The function returns FALSE, and
375 * GetLastError returns ERROR_INVALID_HANDLE.
376 *
377 * On the other hand, on Win95, it returns without error. I cannot
378 * assume that 0, 1, and 2 are console, because if someone closes
379 * System.out and then opens a file, they might get file descriptor
380 * 1. An error on *that* version of 1 should be reported, whereas
381 * an error on System.out (which was the original 1) should be
382 * ignored. So I use isatty() to ensure that such an error was due
383 * to this bogosity, and if it was, I ignore the error.
384 */
385
386 BOOL ok = FlushFileBuffers((HANDLE)fd->secret->md.osfd);
387
388 if (!ok) {
389 DWORD err = GetLastError();
390 if (err != ERROR_ACCESS_DENIED) { // from winerror.h
391 _PR_MD_MAP_FSYNC_ERROR(err);
392 return -1;
393 }
394 }
395 return 0;
396 }
397
398 PRInt32
399 _MD_CloseFile(PROsfd osfd)
400 {
401 PRInt32 rv;
402
403 rv = (CloseHandle((HANDLE)osfd))?0:-1;
404 if (rv == -1)
405 _PR_MD_MAP_CLOSE_ERROR(GetLastError());
406 return rv;
407 }
408
409
410 /* --- DIR IO ------------------------------------------------------------ */
411 #define GetFileFromDIR(d) (d)->d_entry.cFileName
412 #define FileIsHidden(d) ((d)->d_entry.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
413
414 static void FlipSlashes(char *cp, size_t len)
415 {
416 while (len-- > 0) {
417 if (cp[0] == '/') {
418 cp[0] = PR_DIRECTORY_SEPARATOR;
419 }
420 cp = _mbsinc(cp);
421 }
422 } /* end FlipSlashes() */
423
424
425 /*
426 **
427 ** Local implementations of standard Unix RTL functions which are not provided
428 ** by the VC RTL.
429 **
430 */
431
432 PRInt32
433 _PR_MD_CLOSE_DIR(_MDDir *d)
434 {
435 if ( d ) {
436 if (FindClose(d->d_hdl)) {
437 d->magic = (PRUint32)-1;
438 return 0;
439 } else {
440 _PR_MD_MAP_CLOSEDIR_ERROR(GetLastError());
441 return -1;
442 }
443 }
444 PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
445 return -1;
446 }
447
448
449 PRStatus
450 _PR_MD_OPEN_DIR(_MDDir *d, const char *name)
451 {
452 char filename[ MAX_PATH ];
453 size_t len;
454
455 len = strlen(name);
456 /* Need 5 bytes for \*.* and the trailing null byte. */
457 if (len + 5 > MAX_PATH) {
458 PR_SetError(PR_NAME_TOO_LONG_ERROR, 0);
459 return PR_FAILURE;
460 }
461 strcpy(filename, name);
462
463 /*
464 * If 'name' ends in a slash or backslash, do not append
465 * another backslash.
466 */
467 if (IsPrevCharSlash(filename, filename + len)) {
468 len--;
469 }
470 strcpy(&filename[len], "\\*.*");
471 FlipSlashes( filename, strlen(filename) );
472
473 d->d_hdl = FindFirstFileA( filename, &(d->d_entry) );
474 if ( d->d_hdl == INVALID_HANDLE_VALUE ) {
475 _PR_MD_MAP_OPENDIR_ERROR(GetLastError());
476 return PR_FAILURE;
477 }
478 d->firstEntry = PR_TRUE;
479 d->magic = _MD_MAGIC_DIR;
480 return PR_SUCCESS;
481 }
482
483 char *
484 _PR_MD_READ_DIR(_MDDir *d, PRIntn flags)
485 {
486 PRInt32 err;
487 BOOL rv;
488 char *fileName;
489
490 if ( d ) {
491 while (1) {
492 if (d->firstEntry) {
493 d->firstEntry = PR_FALSE;
494 rv = 1;
495 } else {
496 rv = FindNextFileA(d->d_hdl, &(d->d_entry));
497 }
498 if (rv == 0) {
499 break;
500 }
501 fileName = GetFileFromDIR(d);
502 if ( (flags & PR_SKIP_DOT) &&
503 (fileName[0] == '.') && (fileName[1] == '\0'))
504 continue;
505 if ( (flags & PR_SKIP_DOT_DOT) &&
506 (fileName[0] == '.') && (fileName[1] == '.') &&
507 (fileName[2] == '\0'))
508 continue;
509 if ( (flags & PR_SKIP_HIDDEN) && FileIsHidden(d))
510 continue;
511 return fileName;
512 }
513 err = GetLastError();
514 PR_ASSERT(NO_ERROR != err);
515 _PR_MD_MAP_READDIR_ERROR(err);
516 return NULL;
517 }
518 PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
519 return NULL;
520 }
521
522 PRInt32
523 _PR_MD_DELETE(const char *name)
524 {
525 if (DeleteFileA(name)) {
526 return 0;
527 } else {
528 _PR_MD_MAP_DELETE_ERROR(GetLastError());
529 return -1;
530 }
531 }
532
533 void
534 _PR_FileTimeToPRTime(const FILETIME *filetime, PRTime *prtm)
535 {
536 PR_ASSERT(sizeof(FILETIME) == sizeof(PRTime));
537 CopyMemory(prtm, filetime, sizeof(PRTime));
538 #if defined(__MINGW32__)
539 *prtm = (*prtm - _pr_filetime_offset) / 10LL;
540 #else
541 *prtm = (*prtm - _pr_filetime_offset) / 10i64;
542 #endif
543
544 #ifdef DEBUG
545 /* Doublecheck our calculation. */
546 {
547 SYSTEMTIME systime;
548 PRExplodedTime etm;
549 PRTime cmp; /* for comparison */
550 BOOL rv;
551
552 rv = FileTimeToSystemTime(filetime, &systime);
553 PR_ASSERT(0 != rv);
554
555 /*
556 * PR_ImplodeTime ignores wday and yday.
557 */
558 etm.tm_usec = systime.wMilliseconds * PR_USEC_PER_MSEC;
559 etm.tm_sec = systime.wSecond;
560 etm.tm_min = systime.wMinute;
561 etm.tm_hour = systime.wHour;
562 etm.tm_mday = systime.wDay;
563 etm.tm_month = systime.wMonth - 1;
564 etm.tm_year = systime.wYear;
565 /*
566 * It is not well-documented what time zone the FILETIME's
567 * are in. WIN32_FIND_DATA is documented to be in UTC (GMT).
568 * But BY_HANDLE_FILE_INFORMATION is unclear about this.
569 * By our best judgement, we assume that FILETIME is in UTC.
570 */
571 etm.tm_params.tp_gmt_offset = 0;
572 etm.tm_params.tp_dst_offset = 0;
573 cmp = PR_ImplodeTime(&etm);
574
575 /*
576 * SYSTEMTIME is in milliseconds precision, so we convert PRTime's
577 * microseconds to milliseconds before doing the comparison.
578 */
579 PR_ASSERT((cmp / PR_USEC_PER_MSEC) == (*prtm / PR_USEC_PER_MSEC));
580 }
581 #endif /* DEBUG */
582 }
583
584 PRInt32
585 _PR_MD_STAT(const char *fn, struct stat *info)
586 {
587 PRInt32 rv;
588
589 rv = _stat(fn, (struct _stat *)info);
590 if (-1 == rv) {
591 /*
592 * Check for MSVC runtime library _stat() bug.
593 * (It's really a bug in FindFirstFile().)
594 * If a pathname ends in a backslash or slash,
595 * e.g., c:\temp\ or c:/temp/, _stat() will fail.
596 * Note: a pathname ending in a slash (e.g., c:/temp/)
597 * can be handled by _stat() on NT but not on Win95.
598 *
599 * We remove the backslash or slash at the end and
600 * try again.
601 */
602
603 size_t len = strlen(fn);
604 if (len > 0 && len <= _MAX_PATH
605 && IsPrevCharSlash(fn, fn + len)) {
606 char newfn[_MAX_PATH + 1];
607
608 strcpy(newfn, fn);
609 newfn[len - 1] = '\0';
610 rv = _stat(newfn, (struct _stat *)info);
611 }
612 }
613
614 if (-1 == rv) {
615 _PR_MD_MAP_STAT_ERROR(errno);
616 }
617 return rv;
618 }
619
620 #define _PR_IS_SLASH(ch) ((ch) == '/' || (ch) == '\\')
621
622 static PRBool
623 IsPrevCharSlash(const char *str, const char *current)
624 {
625 const char *prev;
626
627 if (str >= current)
628 return PR_FALSE;
629 prev = _mbsdec(str, current);
630 return (prev == current - 1) && _PR_IS_SLASH(*prev);
631 }
632
633 /*
634 * IsRootDirectory --
635 *
636 * Return PR_TRUE if the pathname 'fn' is a valid root directory,
637 * else return PR_FALSE. The char buffer pointed to by 'fn' must
638 * be writable. During the execution of this function, the contents
639 * of the buffer pointed to by 'fn' may be modified, but on return
640 * the original contents will be restored. 'buflen' is the size of
641 * the buffer pointed to by 'fn'.
642 *
643 * Root directories come in three formats:
644 * 1. / or \, meaning the root directory of the current drive.
645 * 2. C:/ or C:\, where C is a drive letter.
646 * 3. \\<server name>\<share point name>\ or
647 * \\<server name>\<share point name>, meaning the root directory
648 * of a UNC (Universal Naming Convention) name.
649 */
650
651 static PRBool
652 IsRootDirectory(char *fn, size_t buflen)
653 {
654 char *p;
655 PRBool slashAdded = PR_FALSE;
656 PRBool rv = PR_FALSE;
657
658 if (_PR_IS_SLASH(fn[0]) && fn[1] == '\0') {
659 return PR_TRUE;
660 }
661
662 if (isalpha(fn[0]) && fn[1] == ':' && _PR_IS_SLASH(fn[2])
663 && fn[3] == '\0') {
664 rv = GetDriveType(fn) > 1 ? PR_TRUE : PR_FALSE;
665 return rv;
666 }
667
668 /* The UNC root directory */
669
670 if (_PR_IS_SLASH(fn[0]) && _PR_IS_SLASH(fn[1])) {
671 /* The 'server' part should have at least one character. */
672 p = &fn[2];
673 if (*p == '\0' || _PR_IS_SLASH(*p)) {
674 return PR_FALSE;
675 }
676
677 /* look for the next slash */
678 do {
679 p = _mbsinc(p);
680 } while (*p != '\0' && !_PR_IS_SLASH(*p));
681 if (*p == '\0') {
682 return PR_FALSE;
683 }
684
685 /* The 'share' part should have at least one character. */
686 p++;
687 if (*p == '\0' || _PR_IS_SLASH(*p)) {
688 return PR_FALSE;
689 }
690
691 /* look for the final slash */
692 do {
693 p = _mbsinc(p);
694 } while (*p != '\0' && !_PR_IS_SLASH(*p));
695 if (_PR_IS_SLASH(*p) && p[1] != '\0') {
696 return PR_FALSE;
697 }
698 if (*p == '\0') {
699 /*
700 * GetDriveType() doesn't work correctly if the
701 * path is of the form \\server\share, so we add
702 * a final slash temporarily.
703 */
704 if ((p + 1) < (fn + buflen)) {
705 *p++ = '\\';
706 *p = '\0';
707 slashAdded = PR_TRUE;
708 } else {
709 return PR_FALSE; /* name too long */
710 }
711 }
712 rv = GetDriveType(fn) > 1 ? PR_TRUE : PR_FALSE;
713 /* restore the 'fn' buffer */
714 if (slashAdded) {
715 *--p = '\0';
716 }
717 }
718 return rv;
719 }
720
721 PRInt32
722 _PR_MD_GETFILEINFO64(const char *fn, PRFileInfo64 *info)
723 {
724 WIN32_FILE_ATTRIBUTE_DATA findFileData;
725 BOOL rv;
726
727 if (NULL == fn || '\0' == *fn) {
728 PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
729 return -1;
730 }
731
732 rv = GetFileAttributesEx(fn, GetFileExInfoStandard, &findFileData);
733 if (!rv) {
734 _PR_MD_MAP_OPENDIR_ERROR(GetLastError());
735 return -1;
736 }
737
738 if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
739 info->type = PR_FILE_DIRECTORY;
740 } else {
741 info->type = PR_FILE_FILE;
742 }
743
744 info->size = findFileData.nFileSizeHigh;
745 info->size = (info->size << 32) + findFileData.nFileSizeLow;
746
747 _PR_FileTimeToPRTime(&findFileData.ftLastWriteTime, &info->modifyTime);
748
749 if (0 == findFileData.ftCreationTime.dwLowDateTime &&
750 0 == findFileData.ftCreationTime.dwHighDateTime) {
751 info->creationTime = info->modifyTime;
752 } else {
753 _PR_FileTimeToPRTime(&findFileData.ftCreationTime,
754 &info->creationTime);
755 }
756
757 return 0;
758 }
759
760 PRInt32
761 _PR_MD_GETFILEINFO(const char *fn, PRFileInfo *info)
762 {
763 PRFileInfo64 info64;
764 PRInt32 rv = _PR_MD_GETFILEINFO64(fn, &info64);
765 if (0 == rv)
766 {
767 info->type = info64.type;
768 info->size = (PRUint32) info64.size;
769 info->modifyTime = info64.modifyTime;
770 info->creationTime = info64.creationTime;
771 }
772 return rv;
773 }
774
775 PRInt32
776 _PR_MD_GETOPENFILEINFO64(const PRFileDesc *fd, PRFileInfo64 *info)
777 {
778 int rv;
779
780 BY_HANDLE_FILE_INFORMATION hinfo;
781
782 rv = GetFileInformationByHandle((HANDLE)fd->secret->md.osfd, &hinfo);
783 if (rv == FALSE) {
784 _PR_MD_MAP_FSTAT_ERROR(GetLastError());
785 return -1;
786 }
787
788 if (hinfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
789 info->type = PR_FILE_DIRECTORY;
790 else
791 info->type = PR_FILE_FILE;
792
793 info->size = hinfo.nFileSizeHigh;
794 info->size = (info->size << 32) + hinfo.nFileSizeLow;
795
796 _PR_FileTimeToPRTime(&hinfo.ftLastWriteTime, &(info->modifyTime) );
797 _PR_FileTimeToPRTime(&hinfo.ftCreationTime, &(info->creationTime) );
798
799 return 0;
800 }
801
802 PRInt32
803 _PR_MD_GETOPENFILEINFO(const PRFileDesc *fd, PRFileInfo *info)
804 {
805 PRFileInfo64 info64;
806 int rv = _PR_MD_GETOPENFILEINFO64(fd, &info64);
807 if (0 == rv)
808 {
809 info->type = info64.type;
810 info->modifyTime = info64.modifyTime;
811 info->creationTime = info64.creationTime;
812 LL_L2I(info->size, info64.size);
813 }
814 return rv;
815 }
816
817 PRStatus
818 _PR_MD_SET_FD_INHERITABLE(PRFileDesc *fd, PRBool inheritable)
819 {
820 BOOL rv;
821
822 /*
823 * The SetHandleInformation function fails with the
824 * ERROR_CALL_NOT_IMPLEMENTED error on Win95.
825 */
826 rv = SetHandleInformation(
827 (HANDLE)fd->secret->md.osfd,
828 HANDLE_FLAG_INHERIT,
829 inheritable ? HANDLE_FLAG_INHERIT : 0);
830 if (0 == rv) {
831 _PR_MD_MAP_DEFAULT_ERROR(GetLastError());
832 return PR_FAILURE;
833 }
834 return PR_SUCCESS;
835 }
836
837 void
838 _PR_MD_INIT_FD_INHERITABLE(PRFileDesc *fd, PRBool imported)
839 {
840 if (imported) {
841 fd->secret->inheritable = _PR_TRI_UNKNOWN;
842 } else {
843 fd->secret->inheritable = _PR_TRI_FALSE;
844 }
845 }
846
847 void
848 _PR_MD_QUERY_FD_INHERITABLE(PRFileDesc *fd)
849 {
850 DWORD flags;
851
852 PR_ASSERT(_PR_TRI_UNKNOWN == fd->secret->inheritable);
853 if (GetHandleInformation((HANDLE)fd->secret->md.osfd, &flags)) {
854 if (flags & HANDLE_FLAG_INHERIT) {
855 fd->secret->inheritable = _PR_TRI_TRUE;
856 } else {
857 fd->secret->inheritable = _PR_TRI_FALSE;
858 }
859 }
860 }
861
862 PRInt32
863 _PR_MD_RENAME(const char *from, const char *to)
864 {
865 /* Does this work with dot-relative pathnames? */
866 if (MoveFileA(from, to)) {
867 return 0;
868 } else {
869 _PR_MD_MAP_RENAME_ERROR(GetLastError());
870 return -1;
871 }
872 }
873
874 PRInt32
875 _PR_MD_ACCESS(const char *name, PRAccessHow how)
876 {
877 PRInt32 rv;
878 switch (how) {
879 case PR_ACCESS_WRITE_OK:
880 rv = _access(name, 02);
881 break;
882 case PR_ACCESS_READ_OK:
883 rv = _access(name, 04);
884 break;
885 case PR_ACCESS_EXISTS:
886 return _access(name, 00);
887 break;
888 default:
889 PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
890 return -1;
891 }
892 if (rv < 0)
893 _PR_MD_MAP_ACCESS_ERROR(errno);
894 return rv;
895 }
896
897 PRInt32
898 _PR_MD_MKDIR(const char *name, PRIntn mode)
899 {
900 /* XXXMB - how to translate the "mode"??? */
901 if (CreateDirectoryA(name, NULL)) {
902 return 0;
903 } else {
904 _PR_MD_MAP_MKDIR_ERROR(GetLastError());
905 return -1;
906 }
907 }
908
909 PRInt32
910 _PR_MD_MAKE_DIR(const char *name, PRIntn mode)
911 {
912 BOOL rv;
913 SECURITY_ATTRIBUTES sa;
914 LPSECURITY_ATTRIBUTES lpSA = NULL;
915 PSECURITY_DESCRIPTOR pSD = NULL;
916 PACL pACL = NULL;
917
918 if (_PR_NT_MakeSecurityDescriptorACL(mode, dirAccessTable,
919 &pSD, &pACL) == PR_SUCCESS) {
920 sa.nLength = sizeof(sa);
921 sa.lpSecurityDescriptor = pSD;
922 sa.bInheritHandle = FALSE;
923 lpSA = &sa;
924 }
925 rv = CreateDirectoryA(name, lpSA);
926 if (lpSA != NULL) {
927 _PR_NT_FreeSecurityDescriptorACL(pSD, pACL);
928 }
929 if (rv) {
930 return 0;
931 } else {
932 _PR_MD_MAP_MKDIR_ERROR(GetLastError());
933 return -1;
934 }
935 }
936
937 PRInt32
938 _PR_MD_RMDIR(const char *name)
939 {
940 if (RemoveDirectoryA(name)) {
941 return 0;
942 } else {
943 _PR_MD_MAP_RMDIR_ERROR(GetLastError());
944 return -1;
945 }
946 }
947
948 PRStatus
949 _PR_MD_LOCKFILE(PROsfd f)
950 {
951 PRStatus rc = PR_SUCCESS;
952 DWORD rv;
953
954 rv = LockFile( (HANDLE)f,
955 0l, 0l,
956 0x0l, 0xffffffffl );
957 if ( rv == 0 ) {
958 DWORD err = GetLastError();
959 _PR_MD_MAP_DEFAULT_ERROR(err);
960 PR_LOG( _pr_io_lm, PR_LOG_ERROR,
961 ("_PR_MD_LOCKFILE() failed. Error: %d", err ));
962 rc = PR_FAILURE;
963 }
964
965 return rc;
966 } /* end _PR_MD_LOCKFILE() */
967
968 PRStatus
969 _PR_MD_TLOCKFILE(PROsfd f)
970 {
971 PR_SetError( PR_NOT_IMPLEMENTED_ERROR, 0 );
972 return PR_FAILURE;
973 } /* end _PR_MD_TLOCKFILE() */
974
975
976 PRStatus
977 _PR_MD_UNLOCKFILE(PROsfd f)
978 {
979 PRInt32 rv;
980
981 rv = UnlockFile( (HANDLE) f,
982 0l, 0l,
983 0x0l, 0xffffffffl );
984
985 if ( rv )
986 {
987 return PR_SUCCESS;
988 }
989 else
990 {
991 _PR_MD_MAP_DEFAULT_ERROR(GetLastError());
992 return PR_FAILURE;
993 }
994 } /* end _PR_MD_UNLOCKFILE() */
995
996 PRInt32
997 _PR_MD_PIPEAVAILABLE(PRFileDesc *fd)
998 {
999 if (NULL == fd)
1000 PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0);
1001 else
1002 PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
1003 return -1;
1004 }
1005
1006 #ifdef MOZ_UNICODE
1007
1008 typedef HANDLE (WINAPI *CreateFileWFn) (LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIB UTES, DWORD, DWORD, HANDLE);
1009 static CreateFileWFn createFileW = CreateFileW;
1010 typedef HANDLE (WINAPI *FindFirstFileWFn) (LPCWSTR, LPWIN32_FIND_DATAW);
1011 static FindFirstFileWFn findFirstFileW = FindFirstFileW;
1012 typedef BOOL (WINAPI *FindNextFileWFn) (HANDLE, LPWIN32_FIND_DATAW);
1013 static FindNextFileWFn findNextFileW = FindNextFileW;
1014 typedef DWORD (WINAPI *GetFullPathNameWFn) (LPCWSTR, DWORD, LPWSTR, LPWSTR *);
1015 static GetFullPathNameWFn getFullPathNameW = GetFullPathNameW;
1016 typedef UINT (WINAPI *GetDriveTypeWFn) (LPCWSTR);
1017 static GetDriveTypeWFn getDriveTypeW = GetDriveTypeW;
1018
1019 #endif /* MOZ_UNICODE */
1020
1021 #ifdef MOZ_UNICODE
1022
1023 /* ================ UTF16 Interfaces ================================ */
1024 static void FlipSlashesW(PRUnichar *cp, size_t len)
1025 {
1026 while (len-- > 0) {
1027 if (cp[0] == L'/') {
1028 cp[0] = L'\\';
1029 }
1030 cp++;
1031 }
1032 } /* end FlipSlashesW() */
1033
1034 PROsfd
1035 _PR_MD_OPEN_FILE_UTF16(const PRUnichar *name, PRIntn osflags, int mode)
1036 {
1037 HANDLE file;
1038 PRInt32 access = 0;
1039 PRInt32 flags = 0;
1040 PRInt32 flag6 = 0;
1041 SECURITY_ATTRIBUTES sa;
1042 LPSECURITY_ATTRIBUTES lpSA = NULL;
1043 PSECURITY_DESCRIPTOR pSD = NULL;
1044 PACL pACL = NULL;
1045
1046 if (osflags & PR_CREATE_FILE) {
1047 if (_PR_NT_MakeSecurityDescriptorACL(mode, fileAccessTable,
1048 &pSD, &pACL) == PR_SUCCESS) {
1049 sa.nLength = sizeof(sa);
1050 sa.lpSecurityDescriptor = pSD;
1051 sa.bInheritHandle = FALSE;
1052 lpSA = &sa;
1053 }
1054 }
1055
1056 if (osflags & PR_SYNC) flag6 = FILE_FLAG_WRITE_THROUGH;
1057
1058 if (osflags & PR_RDONLY || osflags & PR_RDWR)
1059 access |= GENERIC_READ;
1060 if (osflags & PR_WRONLY || osflags & PR_RDWR)
1061 access |= GENERIC_WRITE;
1062
1063 if ( osflags & PR_CREATE_FILE && osflags & PR_EXCL )
1064 flags = CREATE_NEW;
1065 else if (osflags & PR_CREATE_FILE) {
1066 if (osflags & PR_TRUNCATE)
1067 flags = CREATE_ALWAYS;
1068 else
1069 flags = OPEN_ALWAYS;
1070 } else {
1071 if (osflags & PR_TRUNCATE)
1072 flags = TRUNCATE_EXISTING;
1073 else
1074 flags = OPEN_EXISTING;
1075 }
1076
1077 file = createFileW(name,
1078 access,
1079 FILE_SHARE_READ|FILE_SHARE_WRITE,
1080 lpSA,
1081 flags,
1082 flag6,
1083 NULL);
1084 if (lpSA != NULL) {
1085 _PR_NT_FreeSecurityDescriptorACL(pSD, pACL);
1086 }
1087 if (file == INVALID_HANDLE_VALUE) {
1088 _PR_MD_MAP_OPEN_ERROR(GetLastError());
1089 return -1;
1090 }
1091
1092 return (PROsfd)file;
1093 }
1094
1095 PRStatus
1096 _PR_MD_OPEN_DIR_UTF16(_MDDirUTF16 *d, const PRUnichar *name)
1097 {
1098 PRUnichar filename[ MAX_PATH ];
1099 int len;
1100
1101 len = wcslen(name);
1102 /* Need 5 bytes for \*.* and the trailing null byte. */
1103 if (len + 5 > MAX_PATH) {
1104 PR_SetError(PR_NAME_TOO_LONG_ERROR, 0);
1105 return PR_FAILURE;
1106 }
1107 wcscpy(filename, name);
1108
1109 /*
1110 * If 'name' ends in a slash or backslash, do not append
1111 * another backslash.
1112 */
1113 if (filename[len - 1] == L'/' || filename[len - 1] == L'\\') {
1114 len--;
1115 }
1116 wcscpy(&filename[len], L"\\*.*");
1117 FlipSlashesW( filename, wcslen(filename) );
1118
1119 d->d_hdl = findFirstFileW( filename, &(d->d_entry) );
1120 if ( d->d_hdl == INVALID_HANDLE_VALUE ) {
1121 _PR_MD_MAP_OPENDIR_ERROR(GetLastError());
1122 return PR_FAILURE;
1123 }
1124 d->firstEntry = PR_TRUE;
1125 d->magic = _MD_MAGIC_DIR;
1126 return PR_SUCCESS;
1127 }
1128
1129 PRUnichar *
1130 _PR_MD_READ_DIR_UTF16(_MDDirUTF16 *d, PRIntn flags)
1131 {
1132 PRInt32 err;
1133 BOOL rv;
1134 PRUnichar *fileName;
1135
1136 if ( d ) {
1137 while (1) {
1138 if (d->firstEntry) {
1139 d->firstEntry = PR_FALSE;
1140 rv = 1;
1141 } else {
1142 rv = findNextFileW(d->d_hdl, &(d->d_entry));
1143 }
1144 if (rv == 0) {
1145 break;
1146 }
1147 fileName = GetFileFromDIR(d);
1148 if ( (flags & PR_SKIP_DOT) &&
1149 (fileName[0] == L'.') && (fileName[1] == L'\0'))
1150 continue;
1151 if ( (flags & PR_SKIP_DOT_DOT) &&
1152 (fileName[0] == L'.') && (fileName[1] == L'.') &&
1153 (fileName[2] == L'\0'))
1154 continue;
1155 if ( (flags & PR_SKIP_HIDDEN) && FileIsHidden(d))
1156 continue;
1157 return fileName;
1158 }
1159 err = GetLastError();
1160 PR_ASSERT(NO_ERROR != err);
1161 _PR_MD_MAP_READDIR_ERROR(err);
1162 return NULL;
1163 }
1164 PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
1165 return NULL;
1166 }
1167
1168 PRInt32
1169 _PR_MD_CLOSE_DIR_UTF16(_MDDirUTF16 *d)
1170 {
1171 if ( d ) {
1172 if (FindClose(d->d_hdl)) {
1173 d->magic = (PRUint32)-1;
1174 return 0;
1175 } else {
1176 _PR_MD_MAP_CLOSEDIR_ERROR(GetLastError());
1177 return -1;
1178 }
1179 }
1180 PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
1181 return -1;
1182 }
1183
1184 #define _PR_IS_W_SLASH(ch) ((ch) == L'/' || (ch) == L'\\')
1185
1186 /*
1187 * IsRootDirectoryW --
1188 *
1189 * Return PR_TRUE if the pathname 'fn' is a valid root directory,
1190 * else return PR_FALSE. The PRUnichar buffer pointed to by 'fn' must
1191 * be writable. During the execution of this function, the contents
1192 * of the buffer pointed to by 'fn' may be modified, but on return
1193 * the original contents will be restored. 'buflen' is the size of
1194 * the buffer pointed to by 'fn', in PRUnichars.
1195 *
1196 * Root directories come in three formats:
1197 * 1. / or \, meaning the root directory of the current drive.
1198 * 2. C:/ or C:\, where C is a drive letter.
1199 * 3. \\<server name>\<share point name>\ or
1200 * \\<server name>\<share point name>, meaning the root directory
1201 * of a UNC (Universal Naming Convention) name.
1202 */
1203
1204 static PRBool
1205 IsRootDirectoryW(PRUnichar *fn, size_t buflen)
1206 {
1207 PRUnichar *p;
1208 PRBool slashAdded = PR_FALSE;
1209 PRBool rv = PR_FALSE;
1210
1211 if (_PR_IS_W_SLASH(fn[0]) && fn[1] == L'\0') {
1212 return PR_TRUE;
1213 }
1214
1215 if (iswalpha(fn[0]) && fn[1] == L':' && _PR_IS_W_SLASH(fn[2])
1216 && fn[3] == L'\0') {
1217 rv = getDriveTypeW(fn) > 1 ? PR_TRUE : PR_FALSE;
1218 return rv;
1219 }
1220
1221 /* The UNC root directory */
1222
1223 if (_PR_IS_W_SLASH(fn[0]) && _PR_IS_W_SLASH(fn[1])) {
1224 /* The 'server' part should have at least one character. */
1225 p = &fn[2];
1226 if (*p == L'\0' || _PR_IS_W_SLASH(*p)) {
1227 return PR_FALSE;
1228 }
1229
1230 /* look for the next slash */
1231 do {
1232 p++;
1233 } while (*p != L'\0' && !_PR_IS_W_SLASH(*p));
1234 if (*p == L'\0') {
1235 return PR_FALSE;
1236 }
1237
1238 /* The 'share' part should have at least one character. */
1239 p++;
1240 if (*p == L'\0' || _PR_IS_W_SLASH(*p)) {
1241 return PR_FALSE;
1242 }
1243
1244 /* look for the final slash */
1245 do {
1246 p++;
1247 } while (*p != L'\0' && !_PR_IS_W_SLASH(*p));
1248 if (_PR_IS_W_SLASH(*p) && p[1] != L'\0') {
1249 return PR_FALSE;
1250 }
1251 if (*p == L'\0') {
1252 /*
1253 * GetDriveType() doesn't work correctly if the
1254 * path is of the form \\server\share, so we add
1255 * a final slash temporarily.
1256 */
1257 if ((p + 1) < (fn + buflen)) {
1258 *p++ = L'\\';
1259 *p = L'\0';
1260 slashAdded = PR_TRUE;
1261 } else {
1262 return PR_FALSE; /* name too long */
1263 }
1264 }
1265 rv = getDriveTypeW(fn) > 1 ? PR_TRUE : PR_FALSE;
1266 /* restore the 'fn' buffer */
1267 if (slashAdded) {
1268 *--p = L'\0';
1269 }
1270 }
1271 return rv;
1272 }
1273
1274 PRInt32
1275 _PR_MD_GETFILEINFO64_UTF16(const PRUnichar *fn, PRFileInfo64 *info)
1276 {
1277 HANDLE hFindFile;
1278 WIN32_FIND_DATAW findFileData;
1279 PRUnichar pathbuf[MAX_PATH + 1];
1280
1281 if (NULL == fn || L'\0' == *fn) {
1282 PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
1283 return -1;
1284 }
1285
1286 /*
1287 * FindFirstFile() expands wildcard characters. So
1288 * we make sure the pathname contains no wildcard.
1289 */
1290 if (NULL != wcspbrk(fn, L"?*")) {
1291 PR_SetError(PR_FILE_NOT_FOUND_ERROR, 0);
1292 return -1;
1293 }
1294
1295 hFindFile = findFirstFileW(fn, &findFileData);
1296 if (INVALID_HANDLE_VALUE == hFindFile) {
1297 DWORD len;
1298 PRUnichar *filePart;
1299
1300 /*
1301 * FindFirstFile() does not work correctly on root directories.
1302 * It also doesn't work correctly on a pathname that ends in a
1303 * slash. So we first check to see if the pathname specifies a
1304 * root directory. If not, and if the pathname ends in a slash,
1305 * we remove the final slash and try again.
1306 */
1307
1308 /*
1309 * If the pathname does not contain ., \, and /, it cannot be
1310 * a root directory or a pathname that ends in a slash.
1311 */
1312 if (NULL == wcspbrk(fn, L".\\/")) {
1313 _PR_MD_MAP_OPENDIR_ERROR(GetLastError());
1314 return -1;
1315 }
1316 len = getFullPathNameW(fn, sizeof(pathbuf)/sizeof(pathbuf[0]), pathbuf,
1317 &filePart);
1318 if (0 == len) {
1319 _PR_MD_MAP_OPENDIR_ERROR(GetLastError());
1320 return -1;
1321 }
1322 if (len > sizeof(pathbuf)/sizeof(pathbuf[0])) {
1323 PR_SetError(PR_NAME_TOO_LONG_ERROR, 0);
1324 return -1;
1325 }
1326 if (IsRootDirectoryW(pathbuf, sizeof(pathbuf)/sizeof(pathbuf[0]))) {
1327 info->type = PR_FILE_DIRECTORY;
1328 info->size = 0;
1329 /*
1330 * These timestamps don't make sense for root directories.
1331 */
1332 info->modifyTime = 0;
1333 info->creationTime = 0;
1334 return 0;
1335 }
1336 if (!_PR_IS_W_SLASH(pathbuf[len - 1])) {
1337 _PR_MD_MAP_OPENDIR_ERROR(GetLastError());
1338 return -1;
1339 } else {
1340 pathbuf[len - 1] = L'\0';
1341 hFindFile = findFirstFileW(pathbuf, &findFileData);
1342 if (INVALID_HANDLE_VALUE == hFindFile) {
1343 _PR_MD_MAP_OPENDIR_ERROR(GetLastError());
1344 return -1;
1345 }
1346 }
1347 }
1348
1349 FindClose(hFindFile);
1350
1351 if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1352 info->type = PR_FILE_DIRECTORY;
1353 } else {
1354 info->type = PR_FILE_FILE;
1355 }
1356
1357 info->size = findFileData.nFileSizeHigh;
1358 info->size = (info->size << 32) + findFileData.nFileSizeLow;
1359
1360 _PR_FileTimeToPRTime(&findFileData.ftLastWriteTime, &info->modifyTime);
1361
1362 if (0 == findFileData.ftCreationTime.dwLowDateTime &&
1363 0 == findFileData.ftCreationTime.dwHighDateTime) {
1364 info->creationTime = info->modifyTime;
1365 } else {
1366 _PR_FileTimeToPRTime(&findFileData.ftCreationTime,
1367 &info->creationTime);
1368 }
1369
1370 return 0;
1371 }
1372 /* ================ end of UTF16 Interfaces ================================ */
1373 #endif /* MOZ_UNICODE */
OLDNEW
« no previous file with comments | « nspr/pr/src/md/windows/w95dllmain.c ('k') | nspr/pr/src/md/windows/w95sock.c » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698