summaryrefslogtreecommitdiff
path: root/FreeRTOS-Plus/Source/Reliance-Edge/core/driver/volume.c
blob: 976a2f01a37000e9f6cb68f74965f6b4f6587020 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
/*             ----> DO NOT REMOVE THE FOLLOWING NOTICE <----

                   Copyright (c) 2014-2015 Datalight, Inc.
                       All Rights Reserved Worldwide.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; use version 2 of the License.

    This program is distributed in the hope that it will be useful,
    but "AS-IS," WITHOUT ANY WARRANTY; without even the implied warranty
    of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*  Businesses and individuals that for commercial or other reasons cannot
    comply with the terms of the GPLv2 license may obtain a commercial license
    before incorporating Reliance Edge into proprietary software for
    distribution in any form.  Visit http://www.datalight.com/reliance-edge for
    more information.
*/
/** @file
    @brief Implements core volume operations.
*/
#include <redfs.h>
#include <redcore.h>


static bool MetarootIsValid(METAROOT *pMR, bool *pfSectorCRCIsValid);
#ifdef REDCONF_ENDIAN_SWAP
static void MetaRootEndianSwap(METAROOT *pMetaRoot);
#endif


/** @brief Mount a file system volume.

    @return A negated ::REDSTATUS code indicating the operation result.

    @retval 0           Operation was successful.
    @retval -RED_EIO    Volume not formatted, improperly formatted, or corrupt.
*/
REDSTATUS RedVolMount(void)
{
    REDSTATUS ret;

  #if REDCONF_READ_ONLY == 0
    ret = RedOsBDevOpen(gbRedVolNum, BDEV_O_RDWR);
  #else
    ret = RedOsBDevOpen(gbRedVolNum, BDEV_O_RDONLY);
  #endif

    if(ret == 0)
    {
        ret = RedVolMountMaster();

        if(ret == 0)
        {
            ret = RedVolMountMetaroot();
        }

        if(ret != 0)
        {
            /*  If we fail to mount, invalidate the buffers to prevent any
                confusion that could be caused by stale or corrupt metadata.
            */
            (void)RedBufferDiscardRange(0U, gpRedVolume->ulBlockCount);
            (void)RedOsBDevClose(gbRedVolNum);
        }
    }

    return ret;
}


/** @brief Mount the master block.

    @return A negated ::REDSTATUS code indicating the operation result.

    @retval 0           Operation was successful.
    @retval -RED_EIO    Master block missing, corrupt, or inconsistent with the
                        compile-time driver settings.
*/
REDSTATUS RedVolMountMaster(void)
{
    REDSTATUS       ret;
    MASTERBLOCK    *pMB;

    /*  Read the master block, to ensure that the disk was formatted with
        Reliance Edge.
    */
    ret = RedBufferGet(BLOCK_NUM_MASTER, BFLAG_META_MASTER, CAST_VOID_PTR_PTR(&pMB));

    if(ret == 0)
    {
        /*  Verify that the driver was compiled with the same settings that
            the disk was formatted with.  If not, the user has made a
            mistake: either the driver settings are wrong, or the disk needs
            to be reformatted.
        */
        if(    (pMB->ulVersion != RED_DISK_LAYOUT_VERSION)
            || (pMB->ulInodeCount != gpRedVolConf->ulInodeCount)
            || (pMB->ulBlockCount != gpRedVolume->ulBlockCount)
            || (pMB->uMaxNameLen != REDCONF_NAME_MAX)
            || (pMB->uDirectPointers != REDCONF_DIRECT_POINTERS)
            || (pMB->uIndirectPointers != REDCONF_INDIRECT_POINTERS)
            || (pMB->bBlockSizeP2 != BLOCK_SIZE_P2)
            || (((pMB->bFlags & MBFLAG_API_POSIX) != 0U) != (REDCONF_API_POSIX == 1))
            || (((pMB->bFlags & MBFLAG_INODE_TIMESTAMPS) != 0U) != (REDCONF_INODE_TIMESTAMPS == 1))
            || (((pMB->bFlags & MBFLAG_INODE_BLOCKS) != 0U) != (REDCONF_INODE_BLOCKS == 1)))
        {
            ret = -RED_EIO;
        }
      #if REDCONF_API_POSIX == 1
        else if(((pMB->bFlags & MBFLAG_INODE_NLINK) != 0U) != (REDCONF_API_POSIX_LINK == 1))
        {
            ret = -RED_EIO;
        }
      #else
        else if((pMB->bFlags & MBFLAG_INODE_NLINK) != 0U)
        {
            ret = -RED_EIO;
        }
      #endif
        else
        {
            /*  Master block configuration is valid.

                Save the sequence number of the master block in the volume,
                since we need it later (see RedVolMountMetaroot()) and we do
                not want to re-buffer the master block.
            */
            gpRedVolume->ullSequence = pMB->hdr.ullSequence;
        }

        RedBufferPut(pMB);
    }

    return ret;
}


/** @brief Mount the latest metaroot.

    This function also populates the volume contexts.

    @return A negated ::REDSTATUS code indicating the operation result.

    @retval 0           Operation was successful.
    @retval -RED_EIO    Both metaroots are missing or corrupt.
*/
REDSTATUS RedVolMountMetaroot(void)
{
    REDSTATUS ret;

    ret = RedIoRead(gbRedVolNum, BLOCK_NUM_FIRST_METAROOT, 1U, &gpRedCoreVol->aMR[0U]);

    if(ret == 0)
    {
        ret = RedIoRead(gbRedVolNum, BLOCK_NUM_FIRST_METAROOT + 1U, 1U, &gpRedCoreVol->aMR[1U]);
    }

    /*  Determine which metaroot is the most recent copy that was written
        completely.
    */
    if(ret == 0)
    {
        uint8_t bMR = UINT8_MAX;
        bool    fSectorCRCIsValid;

        if(MetarootIsValid(&gpRedCoreVol->aMR[0U], &fSectorCRCIsValid))
        {
            bMR = 0U;

          #ifdef REDCONF_ENDIAN_SWAP
            MetaRootEndianSwap(&gpRedCoreVol->aMR[0U]);
          #endif
        }
        else if(gpRedVolConf->fAtomicSectorWrite && !fSectorCRCIsValid)
        {
            ret = -RED_EIO;
        }
        else
        {
            /*  Metaroot is not valid, so it is ignored and there's nothing
                to do here.
            */
        }

        if(ret == 0)
        {
            if(MetarootIsValid(&gpRedCoreVol->aMR[1U], &fSectorCRCIsValid))
            {
              #ifdef REDCONF_ENDIAN_SWAP
                MetaRootEndianSwap(&gpRedCoreVol->aMR[1U]);
              #endif

                if((bMR != 0U) || (gpRedCoreVol->aMR[1U].hdr.ullSequence > gpRedCoreVol->aMR[0U].hdr.ullSequence))
                {
                    bMR = 1U;
                }
            }
            else if(gpRedVolConf->fAtomicSectorWrite && !fSectorCRCIsValid)
            {
                ret = -RED_EIO;
            }
            else
            {
                /*  Metaroot is not valid, so it is ignored and there's nothing
                    to do here.
                */
            }
        }

        if(ret == 0)
        {
            if(bMR == UINT8_MAX)
            {
                /*  Neither metaroot was valid.
                */
                ret = -RED_EIO;
            }
            else
            {
                gpRedCoreVol->bCurMR = bMR;
                gpRedMR = &gpRedCoreVol->aMR[bMR];
            }
        }
    }

    if(ret == 0)
    {
        /*  Normally the metaroot contains the highest sequence number, but the
            master block is the last block written during format, so on a
            freshly formatted volume the master block sequence number (stored in
            gpRedVolume->ullSequence) will be higher than that in the metaroot.
        */
        if(gpRedMR->hdr.ullSequence > gpRedVolume->ullSequence)
        {
            gpRedVolume->ullSequence = gpRedMR->hdr.ullSequence;
        }

        /*  gpRedVolume->ullSequence stores the *next* sequence number; to avoid
            giving the next node written to disk the same sequence number as the
            metaroot, increment it here.
        */
        ret = RedVolSeqNumIncrement();
    }

    if(ret == 0)
    {
        gpRedVolume->fMounted = true;
      #if REDCONF_READ_ONLY == 0
        gpRedVolume->fReadOnly = false;
      #endif

      #if RESERVED_BLOCKS > 0U
        gpRedCoreVol->fUseReservedBlocks = false;
      #endif
        gpRedCoreVol->ulAlmostFreeBlocks = 0U;

        gpRedCoreVol->aMR[1U - gpRedCoreVol->bCurMR] = *gpRedMR;
        gpRedCoreVol->bCurMR = 1U - gpRedCoreVol->bCurMR;
        gpRedMR = &gpRedCoreVol->aMR[gpRedCoreVol->bCurMR];
    }

    return ret;
}


/** @brief Determine whether the metaroot is valid.

    @param pMR                  The metaroot buffer.
    @param pfSectorCRCIsValid   Populated with whether the first sector of the
                                metaroot buffer is valid.

    @return Whether the metaroot is valid.

    @retval true    The metaroot buffer is valid.
    @retval false   The metaroot buffer is invalid.
*/
static bool MetarootIsValid(
    METAROOT   *pMR,
    bool       *pfSectorCRCIsValid)
{
    bool        fRet = false;

    if(pfSectorCRCIsValid == NULL)
    {
        REDERROR();
    }
    else if(pMR == NULL)
    {
        REDERROR();
        *pfSectorCRCIsValid = false;
    }
  #ifdef REDCONF_ENDIAN_SWAP
    else if(RedRev32(pMR->hdr.ulSignature) != META_SIG_METAROOT)
  #else
    else if(pMR->hdr.ulSignature != META_SIG_METAROOT)
  #endif
    {
        *pfSectorCRCIsValid = false;
    }
    else
    {
        const uint8_t  *pbMR = CAST_VOID_PTR_TO_CONST_UINT8_PTR(pMR);
        uint32_t        ulSectorCRC = pMR->ulSectorCRC;
        uint32_t        ulCRC;

      #ifdef REDCONF_ENDIAN_SWAP
        ulSectorCRC = RedRev32(ulSectorCRC);
      #endif

        /*  The sector CRC was zero when the CRC was computed during the
            transaction, so it must be zero here.
        */
        pMR->ulSectorCRC = 0U;

        ulCRC = RedCrc32Update(0U, &pbMR[8U], gpRedVolConf->ulSectorSize - 8U);

        fRet = ulCRC == ulSectorCRC;
        *pfSectorCRCIsValid = fRet;

        if(fRet)
        {
            if(gpRedVolConf->ulSectorSize < REDCONF_BLOCK_SIZE)
            {
                ulCRC = RedCrc32Update(ulCRC, &pbMR[gpRedVolConf->ulSectorSize], REDCONF_BLOCK_SIZE - gpRedVolConf->ulSectorSize);
            }

          #ifdef REDCONF_ENDIAN_SWAP
            ulCRC = RedRev32(ulCRC);
          #endif

            fRet = ulCRC == pMR->hdr.ulCRC;
        }
    }

    return fRet;
}


#if REDCONF_READ_ONLY == 0
/** @brief Commit a transaction point.

    @return A negated ::REDSTATUS code indicating the operation result.

    @retval 0           Operation was successful.
    @retval -RED_EIO    A disk I/O error occurred.
*/
REDSTATUS RedVolTransact(void)
{
    REDSTATUS ret = 0;

    REDASSERT(!gpRedVolume->fReadOnly); /* Should be checked by caller. */

    if(gpRedCoreVol->fBranched)
    {
        gpRedMR->ulFreeBlocks += gpRedCoreVol->ulAlmostFreeBlocks;
        gpRedCoreVol->ulAlmostFreeBlocks = 0U;

        ret = RedBufferFlush(0U, gpRedVolume->ulBlockCount);

        if(ret == 0)
        {
            gpRedMR->hdr.ulSignature = META_SIG_METAROOT;
            gpRedMR->hdr.ullSequence = gpRedVolume->ullSequence;

            ret = RedVolSeqNumIncrement();
        }

        if(ret == 0)
        {
            const uint8_t  *pbMR = CAST_VOID_PTR_TO_CONST_UINT8_PTR(gpRedMR);
            uint32_t        ulSectorCRC;

          #ifdef REDCONF_ENDIAN_SWAP
            MetaRootEndianSwap(gpRedMR);
          #endif

            gpRedMR->ulSectorCRC = 0U;

            ulSectorCRC = RedCrc32Update(0U, &pbMR[8U], gpRedVolConf->ulSectorSize - 8U);

            if(gpRedVolConf->ulSectorSize < REDCONF_BLOCK_SIZE)
            {
                gpRedMR->hdr.ulCRC = RedCrc32Update(ulSectorCRC, &pbMR[gpRedVolConf->ulSectorSize], REDCONF_BLOCK_SIZE - gpRedVolConf->ulSectorSize);
            }
            else
            {
                gpRedMR->hdr.ulCRC = ulSectorCRC;
            }

            gpRedMR->ulSectorCRC = ulSectorCRC;

          #ifdef REDCONF_ENDIAN_SWAP
            gpRedMR->hdr.ulCRC = RedRev32(gpRedMR->hdr.ulCRC);
            gpRedMR->ulSectorCRC = RedRev32(gpRedMR->ulSectorCRC);
          #endif

            /*  Flush the block device before writing the metaroot, so that all
                previously written blocks are guaranteed to be on the media before
                the metaroot is written.  Otherwise, if the block device reorders
                the writes, the metaroot could reach the media before metadata it
                points at, creating a window for disk corruption if power is lost.
            */
            ret = RedIoFlush(gbRedVolNum);
        }

        if(ret == 0)
        {
            ret = RedIoWrite(gbRedVolNum, BLOCK_NUM_FIRST_METAROOT + gpRedCoreVol->bCurMR, 1U, gpRedMR);

          #ifdef REDCONF_ENDIAN_SWAP
            MetaRootEndianSwap(gpRedMR);
          #endif
        }

        /*  Flush the block device to force the metaroot write to the media.  This
            guarantees the transaction point is really complete before we return.
        */
        if(ret == 0)
        {
            ret = RedIoFlush(gbRedVolNum);
        }

        /*  Toggle to the other metaroot buffer.  The working state and committed
            state metaroot buffers exchange places.
        */
        if(ret == 0)
        {
            uint8_t bNextMR = 1U - gpRedCoreVol->bCurMR;

            gpRedCoreVol->aMR[bNextMR] = *gpRedMR;
            gpRedCoreVol->bCurMR = bNextMR;

            gpRedMR = &gpRedCoreVol->aMR[gpRedCoreVol->bCurMR];

            gpRedCoreVol->fBranched = false;
        }

        CRITICAL_ASSERT(ret == 0);
    }

    return ret;
}
#endif


#ifdef REDCONF_ENDIAN_SWAP
static void MetaRootEndianSwap(
    METAROOT *pMetaRoot)
{
    if(pMetaRoot == NULL)
    {
        REDERROR();
    }
    else
    {
        pMetaRoot->ulSectorCRC = RedRev32(pMetaRoot->ulSectorCRC);
        pMetaRoot->ulFreeBlocks = RedRev32(pMetaRoot->ulFreeBlocks);
      #if REDCONF_API_POSIX == 1
        pMetaRoot->ulFreeInodes = RedRev32(pMetaRoot->ulFreeInodes);
      #endif
        pMetaRoot->ulAllocNextBlock = RedRev32(pMetaRoot->ulAllocNextBlock);
    }
}
#endif


/** @brief Process a critical file system error.

    @param pszFileName  The file in which the error occurred.
    @param ulLineNum    The line number at which the error occurred.
*/
void RedVolCriticalError(
    const char *pszFileName,
    uint32_t    ulLineNum)
{
  #if REDCONF_OUTPUT == 1
  #if REDCONF_READ_ONLY == 0
    if(!gpRedVolume->fReadOnly)
    {
        RedOsOutputString("Critical file system error in Reliance Edge, setting volume to READONLY\n");
    }
    else
  #endif
    {
        RedOsOutputString("Critical file system error in Reliance Edge (volume already READONLY)\n");
    }
  #endif

  #if REDCONF_READ_ONLY == 0
    gpRedVolume->fReadOnly = true;
  #endif

  #if REDCONF_ASSERTS == 1
    RedOsAssertFail(pszFileName, ulLineNum);
  #else
    (void)pszFileName;
    (void)ulLineNum;
  #endif
}


/** @brief Increment the sequence number.

    @return A negated ::REDSTATUS code indicating the operation result.

    @retval 0           Operation was successful.
    @retval -RED_EINVAL Cannot increment sequence number: maximum value reached.
                        This should not ever happen.
*/
REDSTATUS RedVolSeqNumIncrement(void)
{
    REDSTATUS ret;

    if(gpRedVolume->ullSequence == UINT64_MAX)
    {
        /*  In practice this should never, ever happen; to get here, there would
            need to be UINT64_MAX disk writes, which would take eons: longer
            than the lifetime of any product or storage media.  If this assert
            fires and the current year is still written with four digits,
            suspect memory corruption.
        */
        CRITICAL_ERROR();
        ret = -RED_EFUBAR;
    }
    else
    {
        gpRedVolume->ullSequence++;
        ret = 0;
    }

    return ret;
}