Coverage Report

Created: 2024-05-20 06:23

/src/nss/lib/ssl/sslmutex.c
Line
Count
Source (jump to first uncovered line)
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
#include "seccomon.h"
6
/* This ifdef should match the one in sslsnce.c */
7
#if defined(XP_UNIX) || defined(XP_WIN32) || defined(XP_OS2)
8
9
#include "sslmutex.h"
10
#include "prerr.h"
11
12
static SECStatus
13
single_process_sslMutex_Init(sslMutex* pMutex)
14
11
{
15
11
    PR_ASSERT(pMutex != 0 && pMutex->u.sslLock == 0);
16
17
11
    pMutex->u.sslLock = PR_NewLock();
18
11
    if (!pMutex->u.sslLock) {
19
0
        return SECFailure;
20
0
    }
21
11
    return SECSuccess;
22
11
}
23
24
static SECStatus
25
single_process_sslMutex_Destroy(sslMutex* pMutex)
26
11
{
27
11
    PR_ASSERT(pMutex != 0);
28
11
    PR_ASSERT(pMutex->u.sslLock != 0);
29
11
    if (!pMutex->u.sslLock) {
30
0
        PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
31
0
        return SECFailure;
32
0
    }
33
11
    PR_DestroyLock(pMutex->u.sslLock);
34
11
    return SECSuccess;
35
11
}
36
37
static SECStatus
38
single_process_sslMutex_Unlock(sslMutex* pMutex)
39
5.20k
{
40
5.20k
    PR_ASSERT(pMutex != 0);
41
5.20k
    PR_ASSERT(pMutex->u.sslLock != 0);
42
5.20k
    if (!pMutex->u.sslLock) {
43
0
        PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
44
0
        return SECFailure;
45
0
    }
46
5.20k
    PR_Unlock(pMutex->u.sslLock);
47
5.20k
    return SECSuccess;
48
5.20k
}
49
50
static SECStatus
51
single_process_sslMutex_Lock(sslMutex* pMutex)
52
5.20k
{
53
5.20k
    PR_ASSERT(pMutex != 0);
54
5.20k
    PR_ASSERT(pMutex->u.sslLock != 0);
55
5.20k
    if (!pMutex->u.sslLock) {
56
0
        PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
57
0
        return SECFailure;
58
0
    }
59
5.20k
    PR_Lock(pMutex->u.sslLock);
60
5.20k
    return SECSuccess;
61
5.20k
}
62
63
#if defined(LINUX) || defined(AIX) || defined(BSDI) || \
64
    (defined(NETBSD) && __NetBSD_Version__ < 500000000) || defined(OPENBSD) || defined(__GLIBC__)
65
66
#include <unistd.h>
67
#include <fcntl.h>
68
#include <string.h>
69
#include <errno.h>
70
#include "unix_err.h"
71
#include "pratom.h"
72
73
0
#define SSL_MUTEX_MAGIC 0xfeedfd
74
#define NONBLOCKING_POSTS 1 /* maybe this is faster */
75
76
#if NONBLOCKING_POSTS
77
78
#ifndef FNONBLOCK
79
#define FNONBLOCK O_NONBLOCK
80
#endif
81
82
static int
83
setNonBlocking(int fd, int nonBlocking)
84
0
{
85
0
    int flags;
86
0
    int err;
87
88
0
    flags = fcntl(fd, F_GETFL, 0);
89
0
    if (0 > flags)
90
0
        return flags;
91
0
    if (nonBlocking)
92
0
        flags |= FNONBLOCK;
93
0
    else
94
0
        flags &= ~FNONBLOCK;
95
0
    err = fcntl(fd, F_SETFL, flags);
96
0
    return err;
97
0
}
98
#endif
99
100
SECStatus
101
sslMutex_Init(sslMutex* pMutex, int shared)
102
11
{
103
11
    int err;
104
11
    PR_ASSERT(pMutex);
105
11
    pMutex->isMultiProcess = (PRBool)(shared != 0);
106
11
    if (!shared) {
107
11
        return single_process_sslMutex_Init(pMutex);
108
11
    }
109
0
    pMutex->u.pipeStr.mPipes[0] = -1;
110
0
    pMutex->u.pipeStr.mPipes[1] = -1;
111
0
    pMutex->u.pipeStr.mPipes[2] = -1;
112
0
    pMutex->u.pipeStr.nWaiters = 0;
113
114
0
    err = pipe(pMutex->u.pipeStr.mPipes);
115
0
    if (err) {
116
0
        nss_MD_unix_map_default_error(errno);
117
0
        return err;
118
0
    }
119
0
#if NONBLOCKING_POSTS
120
0
    err = setNonBlocking(pMutex->u.pipeStr.mPipes[1], 1);
121
0
    if (err)
122
0
        goto loser;
123
0
#endif
124
125
0
    pMutex->u.pipeStr.mPipes[2] = SSL_MUTEX_MAGIC;
126
127
#if defined(LINUX) && defined(i386)
128
    /* Pipe starts out empty */
129
    return SECSuccess;
130
#else
131
    /* Pipe starts with one byte. */
132
0
    return sslMutex_Unlock(pMutex);
133
0
#endif
134
135
0
loser:
136
0
    nss_MD_unix_map_default_error(errno);
137
0
    close(pMutex->u.pipeStr.mPipes[0]);
138
0
    close(pMutex->u.pipeStr.mPipes[1]);
139
0
    return SECFailure;
140
0
}
141
142
SECStatus
143
sslMutex_Destroy(sslMutex* pMutex, PRBool processLocal)
144
11
{
145
11
    if (PR_FALSE == pMutex->isMultiProcess) {
146
11
        return single_process_sslMutex_Destroy(pMutex);
147
11
    }
148
0
    if (pMutex->u.pipeStr.mPipes[2] != SSL_MUTEX_MAGIC) {
149
0
        PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
150
0
        return SECFailure;
151
0
    }
152
0
    close(pMutex->u.pipeStr.mPipes[0]);
153
0
    close(pMutex->u.pipeStr.mPipes[1]);
154
155
0
    if (processLocal) {
156
0
        return SECSuccess;
157
0
    }
158
159
0
    pMutex->u.pipeStr.mPipes[0] = -1;
160
0
    pMutex->u.pipeStr.mPipes[1] = -1;
161
0
    pMutex->u.pipeStr.mPipes[2] = -1;
162
0
    pMutex->u.pipeStr.nWaiters = 0;
163
164
0
    return SECSuccess;
165
0
}
166
167
#if defined(LINUX) && defined(i386)
168
/* No memory barrier needed for this platform */
169
170
/* nWaiters includes the holder of the lock (if any) and the number
171
** threads waiting for it.  After incrementing nWaiters, if the count
172
** is exactly 1, then you have the lock and may proceed.  If the
173
** count is greater than 1, then you must wait on the pipe.
174
*/
175
176
SECStatus
177
sslMutex_Unlock(sslMutex* pMutex)
178
{
179
    PRInt32 newValue;
180
    if (PR_FALSE == pMutex->isMultiProcess) {
181
        return single_process_sslMutex_Unlock(pMutex);
182
    }
183
184
    if (pMutex->u.pipeStr.mPipes[2] != SSL_MUTEX_MAGIC) {
185
        PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
186
        return SECFailure;
187
    }
188
    /* Do Memory Barrier here. */
189
    newValue = PR_ATOMIC_DECREMENT(&pMutex->u.pipeStr.nWaiters);
190
    if (newValue > 0) {
191
        int cc;
192
        char c = 1;
193
        do {
194
            cc = write(pMutex->u.pipeStr.mPipes[1], &c, 1);
195
        } while (cc < 0 && (errno == EINTR || errno == EAGAIN));
196
        if (cc != 1) {
197
            if (cc < 0)
198
                nss_MD_unix_map_default_error(errno);
199
            else
200
                PORT_SetError(PR_UNKNOWN_ERROR);
201
            return SECFailure;
202
        }
203
    }
204
    return SECSuccess;
205
}
206
207
SECStatus
208
sslMutex_Lock(sslMutex* pMutex)
209
{
210
    PRInt32 newValue;
211
    if (PR_FALSE == pMutex->isMultiProcess) {
212
        return single_process_sslMutex_Lock(pMutex);
213
    }
214
215
    if (pMutex->u.pipeStr.mPipes[2] != SSL_MUTEX_MAGIC) {
216
        PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
217
        return SECFailure;
218
    }
219
    newValue = PR_ATOMIC_INCREMENT(&pMutex->u.pipeStr.nWaiters);
220
    /* Do Memory Barrier here. */
221
    if (newValue > 1) {
222
        int cc;
223
        char c;
224
        do {
225
            cc = read(pMutex->u.pipeStr.mPipes[0], &c, 1);
226
        } while (cc < 0 && errno == EINTR);
227
        if (cc != 1) {
228
            if (cc < 0)
229
                nss_MD_unix_map_default_error(errno);
230
            else
231
                PORT_SetError(PR_UNKNOWN_ERROR);
232
            return SECFailure;
233
        }
234
    }
235
    return SECSuccess;
236
}
237
238
#else
239
240
/* Using Atomic operations requires the use of a memory barrier instruction
241
** on PowerPC, Sparc, and Alpha.  NSPR's PR_Atomic functions do not perform
242
** them, and NSPR does not provide a function that does them (e.g. PR_Barrier).
243
** So, we don't use them on those platforms.
244
*/
245
246
SECStatus
247
sslMutex_Unlock(sslMutex* pMutex)
248
5.20k
{
249
5.20k
    int cc;
250
5.20k
    char c = 1;
251
252
5.20k
    if (PR_FALSE == pMutex->isMultiProcess) {
253
5.20k
        return single_process_sslMutex_Unlock(pMutex);
254
5.20k
    }
255
256
0
    if (pMutex->u.pipeStr.mPipes[2] != SSL_MUTEX_MAGIC) {
257
0
        PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
258
0
        return SECFailure;
259
0
    }
260
0
    do {
261
0
        cc = write(pMutex->u.pipeStr.mPipes[1], &c, 1);
262
0
    } while (cc < 0 && (errno == EINTR || errno == EAGAIN));
263
0
    if (cc != 1) {
264
0
        if (cc < 0)
265
0
            nss_MD_unix_map_default_error(errno);
266
0
        else
267
0
            PORT_SetError(PR_UNKNOWN_ERROR);
268
0
        return SECFailure;
269
0
    }
270
271
0
    return SECSuccess;
272
0
}
273
274
SECStatus
275
sslMutex_Lock(sslMutex* pMutex)
276
5.20k
{
277
5.20k
    int cc;
278
5.20k
    char c;
279
280
5.20k
    if (PR_FALSE == pMutex->isMultiProcess) {
281
5.20k
        return single_process_sslMutex_Lock(pMutex);
282
5.20k
    }
283
284
0
    if (pMutex->u.pipeStr.mPipes[2] != SSL_MUTEX_MAGIC) {
285
0
        PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
286
0
        return SECFailure;
287
0
    }
288
289
0
    do {
290
0
        cc = read(pMutex->u.pipeStr.mPipes[0], &c, 1);
291
0
    } while (cc < 0 && errno == EINTR);
292
0
    if (cc != 1) {
293
0
        if (cc < 0)
294
0
            nss_MD_unix_map_default_error(errno);
295
0
        else
296
0
            PORT_SetError(PR_UNKNOWN_ERROR);
297
0
        return SECFailure;
298
0
    }
299
300
0
    return SECSuccess;
301
0
}
302
303
#endif
304
305
#elif defined(WIN32)
306
307
#include "win32err.h"
308
309
/* on Windows, we need to find the optimal type of locking mechanism to use
310
 for the sslMutex.
311
312
 There are 3 cases :
313
 1) single-process, use a PRLock, as for all other platforms
314
 2) Win95 multi-process, use a Win32 mutex
315
 3) on WINNT multi-process, use a PRLock + a Win32 mutex
316
317
*/
318
319
#ifdef WINNT
320
321
SECStatus
322
sslMutex_2LevelInit(sslMutex *sem)
323
{
324
    /*  the following adds a PRLock to sslMutex . This is done in each
325
        process of a multi-process server and is only needed on WINNT, if
326
        using fibers. We can't tell if native threads or fibers are used, so
327
        we always do it on WINNT
328
    */
329
    PR_ASSERT(sem);
330
    if (sem) {
331
        /* we need to reset the sslLock in the children or the single_process init
332
           function below will assert */
333
        sem->u.sslLock = NULL;
334
    }
335
    return single_process_sslMutex_Init(sem);
336
}
337
338
static SECStatus
339
sslMutex_2LevelDestroy(sslMutex *sem)
340
{
341
    return single_process_sslMutex_Destroy(sem);
342
}
343
344
#endif
345
346
SECStatus
347
sslMutex_Init(sslMutex *pMutex, int shared)
348
{
349
#ifdef WINNT
350
    SECStatus retvalue;
351
#endif
352
    HANDLE hMutex;
353
    SECURITY_ATTRIBUTES attributes = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
354
355
    PR_ASSERT(pMutex != 0 && (pMutex->u.sslMutx == 0 ||
356
                              pMutex->u.sslMutx ==
357
                                  INVALID_HANDLE_VALUE));
358
359
    pMutex->isMultiProcess = (PRBool)(shared != 0);
360
361
    if (PR_FALSE == pMutex->isMultiProcess) {
362
        return single_process_sslMutex_Init(pMutex);
363
    }
364
365
#ifdef WINNT
366
    /*  we need a lock on WINNT for fibers in the parent process */
367
    retvalue = sslMutex_2LevelInit(pMutex);
368
    if (SECSuccess != retvalue)
369
        return SECFailure;
370
#endif
371
372
    if (!pMutex || ((hMutex = pMutex->u.sslMutx) != 0 &&
373
                    hMutex !=
374
                        INVALID_HANDLE_VALUE)) {
375
        PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
376
        return SECFailure;
377
    }
378
    attributes.bInheritHandle = (shared ? TRUE : FALSE);
379
    hMutex = CreateMutex(&attributes, FALSE, NULL);
380
    if (hMutex == NULL) {
381
        hMutex = INVALID_HANDLE_VALUE;
382
        nss_MD_win32_map_default_error(GetLastError());
383
        return SECFailure;
384
    }
385
    pMutex->u.sslMutx = hMutex;
386
    return SECSuccess;
387
}
388
389
SECStatus
390
sslMutex_Destroy(sslMutex *pMutex, PRBool processLocal)
391
{
392
    HANDLE hMutex;
393
    int rv;
394
    int retvalue = SECSuccess;
395
396
    PR_ASSERT(pMutex != 0);
397
    if (!pMutex) {
398
        PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
399
        return SECFailure;
400
    }
401
402
    if (PR_FALSE == pMutex->isMultiProcess) {
403
        return single_process_sslMutex_Destroy(pMutex);
404
    }
405
406
/*  multi-process mode */
407
#ifdef WINNT
408
    /* on NT, get rid of the PRLock used for fibers within a process */
409
    retvalue = sslMutex_2LevelDestroy(pMutex);
410
#endif
411
412
    PR_ASSERT(pMutex->u.sslMutx != 0 &&
413
              pMutex->u.sslMutx != INVALID_HANDLE_VALUE);
414
    if ((hMutex = pMutex->u.sslMutx) == 0 || hMutex == INVALID_HANDLE_VALUE) {
415
        PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
416
        return SECFailure;
417
    }
418
419
    rv = CloseHandle(hMutex); /* ignore error */
420
    if (!processLocal && rv) {
421
        pMutex->u.sslMutx = hMutex = INVALID_HANDLE_VALUE;
422
    }
423
    if (!rv) {
424
        nss_MD_win32_map_default_error(GetLastError());
425
        retvalue = SECFailure;
426
    }
427
    return retvalue;
428
}
429
430
int
431
sslMutex_Unlock(sslMutex *pMutex)
432
{
433
    BOOL success = FALSE;
434
    HANDLE hMutex;
435
436
    PR_ASSERT(pMutex != 0);
437
    if (!pMutex) {
438
        PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
439
        return SECFailure;
440
    }
441
442
    if (PR_FALSE == pMutex->isMultiProcess) {
443
        return single_process_sslMutex_Unlock(pMutex);
444
    }
445
446
    PR_ASSERT(pMutex->u.sslMutx != 0 &&
447
              pMutex->u.sslMutx != INVALID_HANDLE_VALUE);
448
    if ((hMutex = pMutex->u.sslMutx) == 0 || hMutex == INVALID_HANDLE_VALUE) {
449
        PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
450
        return SECFailure;
451
    }
452
    success = ReleaseMutex(hMutex);
453
    if (!success) {
454
        nss_MD_win32_map_default_error(GetLastError());
455
        return SECFailure;
456
    }
457
#ifdef WINNT
458
    return single_process_sslMutex_Unlock(pMutex);
459
/* release PRLock for other fibers in the process */
460
#else
461
    return SECSuccess;
462
#endif
463
}
464
465
int
466
sslMutex_Lock(sslMutex *pMutex)
467
{
468
    HANDLE hMutex;
469
    DWORD event;
470
    DWORD lastError;
471
    SECStatus rv;
472
    SECStatus retvalue = SECSuccess;
473
474
    PR_ASSERT(pMutex != 0);
475
    if (!pMutex) {
476
        PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
477
        return SECFailure;
478
    }
479
480
    if (PR_FALSE == pMutex->isMultiProcess) {
481
        return single_process_sslMutex_Lock(pMutex);
482
    }
483
#ifdef WINNT
484
    /* lock first to preserve from other threads/fibers in the same process */
485
    retvalue = single_process_sslMutex_Lock(pMutex);
486
#endif
487
    PR_ASSERT(pMutex->u.sslMutx != 0 &&
488
              pMutex->u.sslMutx != INVALID_HANDLE_VALUE);
489
    if ((hMutex = pMutex->u.sslMutx) == 0 || hMutex == INVALID_HANDLE_VALUE) {
490
        PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
491
        return SECFailure; /* what else ? */
492
    }
493
    /* acquire the mutex to be the only owner accross all other processes */
494
    event = WaitForSingleObject(hMutex, INFINITE);
495
    switch (event) {
496
        case WAIT_OBJECT_0:
497
        case WAIT_ABANDONED:
498
            rv = SECSuccess;
499
            break;
500
501
        case WAIT_TIMEOUT:
502
#if defined(WAIT_IO_COMPLETION)
503
        case WAIT_IO_COMPLETION:
504
#endif
505
        default: /* should never happen. nothing we can do. */
506
            PR_ASSERT(PR_FALSE && "WaitForSingleObject returned invalid value.");
507
            PORT_SetError(PR_UNKNOWN_ERROR);
508
            rv = SECFailure;
509
            break;
510
511
        case WAIT_FAILED: /* failure returns this */
512
            rv = SECFailure;
513
            lastError = GetLastError(); /* for debugging */
514
            nss_MD_win32_map_default_error(lastError);
515
            break;
516
    }
517
518
    if (!(SECSuccess == retvalue && SECSuccess == rv)) {
519
        return SECFailure;
520
    }
521
522
    return SECSuccess;
523
}
524
525
#elif defined(XP_UNIX) && !defined(DARWIN)
526
527
#include <errno.h>
528
#include "unix_err.h"
529
530
SECStatus
531
sslMutex_Init(sslMutex* pMutex, int shared)
532
{
533
    int rv;
534
    PR_ASSERT(pMutex);
535
    pMutex->isMultiProcess = (PRBool)(shared != 0);
536
    if (!shared) {
537
        return single_process_sslMutex_Init(pMutex);
538
    }
539
    do {
540
        rv = sem_init(&pMutex->u.sem, shared, 1);
541
    } while (rv < 0 && errno == EINTR);
542
    if (rv < 0) {
543
        nss_MD_unix_map_default_error(errno);
544
        return SECFailure;
545
    }
546
    return SECSuccess;
547
}
548
549
SECStatus
550
sslMutex_Destroy(sslMutex* pMutex, PRBool processLocal)
551
{
552
    int rv;
553
    if (PR_FALSE == pMutex->isMultiProcess) {
554
        return single_process_sslMutex_Destroy(pMutex);
555
    }
556
557
    /* semaphores are global resources. See SEM_DESTROY(3) man page */
558
    if (processLocal) {
559
        return SECSuccess;
560
    }
561
    do {
562
        rv = sem_destroy(&pMutex->u.sem);
563
    } while (rv < 0 && errno == EINTR);
564
    if (rv < 0) {
565
        nss_MD_unix_map_default_error(errno);
566
        return SECFailure;
567
    }
568
    return SECSuccess;
569
}
570
571
SECStatus
572
sslMutex_Unlock(sslMutex* pMutex)
573
{
574
    int rv;
575
    if (PR_FALSE == pMutex->isMultiProcess) {
576
        return single_process_sslMutex_Unlock(pMutex);
577
    }
578
    do {
579
        rv = sem_post(&pMutex->u.sem);
580
    } while (rv < 0 && errno == EINTR);
581
    if (rv < 0) {
582
        nss_MD_unix_map_default_error(errno);
583
        return SECFailure;
584
    }
585
    return SECSuccess;
586
}
587
588
SECStatus
589
sslMutex_Lock(sslMutex* pMutex)
590
{
591
    int rv;
592
    if (PR_FALSE == pMutex->isMultiProcess) {
593
        return single_process_sslMutex_Lock(pMutex);
594
    }
595
    do {
596
        rv = sem_wait(&pMutex->u.sem);
597
    } while (rv < 0 && errno == EINTR);
598
    if (rv < 0) {
599
        nss_MD_unix_map_default_error(errno);
600
        return SECFailure;
601
    }
602
    return SECSuccess;
603
}
604
605
#else
606
607
SECStatus
608
sslMutex_Init(sslMutex* pMutex, int shared)
609
{
610
    PR_ASSERT(pMutex);
611
    pMutex->isMultiProcess = (PRBool)(shared != 0);
612
    if (!shared) {
613
        return single_process_sslMutex_Init(pMutex);
614
    }
615
    PORT_Assert(PR_FALSE && "sslMutex_Init not implemented for multi-process applications !");
616
    PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
617
    return SECFailure;
618
}
619
620
SECStatus
621
sslMutex_Destroy(sslMutex* pMutex, PRBool processLocal)
622
{
623
    PR_ASSERT(pMutex);
624
    if (PR_FALSE == pMutex->isMultiProcess) {
625
        return single_process_sslMutex_Destroy(pMutex);
626
    }
627
    PORT_Assert(PR_FALSE && "sslMutex_Destroy not implemented for multi-process applications !");
628
    PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
629
    return SECFailure;
630
}
631
632
SECStatus
633
sslMutex_Unlock(sslMutex* pMutex)
634
{
635
    PR_ASSERT(pMutex);
636
    if (PR_FALSE == pMutex->isMultiProcess) {
637
        return single_process_sslMutex_Unlock(pMutex);
638
    }
639
    PORT_Assert(PR_FALSE && "sslMutex_Unlock not implemented for multi-process applications !");
640
    PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
641
    return SECFailure;
642
}
643
644
SECStatus
645
sslMutex_Lock(sslMutex* pMutex)
646
{
647
    PR_ASSERT(pMutex);
648
    if (PR_FALSE == pMutex->isMultiProcess) {
649
        return single_process_sslMutex_Lock(pMutex);
650
    }
651
    PORT_Assert(PR_FALSE && "sslMutex_Lock not implemented for multi-process applications !");
652
    PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
653
    return SECFailure;
654
}
655
656
#endif
657
658
#endif