Coverage Report

Created: 2026-08-11 08:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/mitab/mitab_maptoolblock.cpp
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * Name:     mitab_maptoollock.cpp
4
 * Project:  MapInfo TAB Read/Write library
5
 * Language: C++
6
 * Purpose:  Implementation of the TABMAPToolBlock class used to handle
7
 *           reading/writing of the .MAP files' drawing tool blocks
8
 * Author:   Daniel Morissette, dmorissette@dmsolutions.ca
9
 *
10
 **********************************************************************
11
 * Copyright (c) 1999, 2000, Daniel Morissette
12
 * Copyright (c) 2014, Even Rouault <even.rouault at spatialys.com>
13
 *
14
 * SPDX-License-Identifier: MIT
15
 **********************************************************************/
16
17
#include "cpl_port.h"
18
#include "mitab.h"
19
20
#include <cstddef>
21
22
#include "cpl_conv.h"
23
#include "cpl_error.h"
24
#include "cpl_vsi.h"
25
#include "mitab_priv.h"
26
27
/*=====================================================================
28
 *                      class TABMAPToolBlock
29
 *====================================================================*/
30
31
constexpr int MAP_TOOL_HEADER_SIZE = 8;
32
33
/**********************************************************************
34
 *                   TABMAPToolBlock::TABMAPToolBlock()
35
 *
36
 * Constructor.
37
 **********************************************************************/
38
TABMAPToolBlock::TABMAPToolBlock(TABAccess eAccessMode /*= TABRead*/)
39
288
    : TABRawBinBlock(eAccessMode, TRUE), m_numDataBytes(0), m_nNextToolBlock(0),
40
288
      m_numBlocksInChain(1),  // Current block counts as 1
41
288
      m_poBlockManagerRef(nullptr)
42
288
{
43
288
}
44
45
/**********************************************************************
46
 *                   TABMAPToolBlock::~TABMAPToolBlock()
47
 *
48
 * Destructor.
49
 **********************************************************************/
50
TABMAPToolBlock::~TABMAPToolBlock()
51
288
{
52
288
}
53
54
/**********************************************************************
55
 *                   TABMAPToolBlock::EndOfChain()
56
 *
57
 * Return TRUE if we reached the end of the last block in the chain
58
 * TABMAPToolBlocks, or FALSE if there is still data to be read from
59
 * this chain.
60
 **********************************************************************/
61
GBool TABMAPToolBlock::EndOfChain()
62
137
{
63
137
    if (m_pabyBuf && (m_nCurPos < (m_numDataBytes + MAP_TOOL_HEADER_SIZE) ||
64
21
                      m_nNextToolBlock > 0))
65
116
    {
66
116
        return FALSE;  // There is still data to be read.
67
116
    }
68
69
21
    return TRUE;
70
137
}
71
72
/**********************************************************************
73
 *                   TABMAPToolBlock::InitBlockFromData()
74
 *
75
 * Perform some initialization on the block after its binary data has
76
 * been set or changed (or loaded from a file).
77
 *
78
 * Returns 0 if successful or -1 if an error happened, in which case
79
 * CPLError() will have been called.
80
 **********************************************************************/
81
int TABMAPToolBlock::InitBlockFromData(GByte *pabyBuf, int nBlockSize,
82
                                       int nSizeUsed,
83
                                       GBool bMakeCopy /* = TRUE */,
84
                                       VSILFILE *fpSrc /* = NULL */,
85
                                       int nOffset /* = 0 */)
86
42
{
87
    /*-----------------------------------------------------------------
88
     * First of all, we must call the base class' InitBlockFromData()
89
     *----------------------------------------------------------------*/
90
42
    const int nStatus = TABRawBinBlock::InitBlockFromData(
91
42
        pabyBuf, nBlockSize, nSizeUsed, bMakeCopy, fpSrc, nOffset);
92
42
    if (nStatus != 0)
93
0
        return nStatus;
94
95
    /*-----------------------------------------------------------------
96
     * Validate block type
97
     *----------------------------------------------------------------*/
98
42
    if (m_nBlockType != TABMAP_TOOL_BLOCK)
99
12
    {
100
12
        CPLError(CE_Failure, CPLE_FileIO,
101
12
                 "InitBlockFromData(): Invalid Block Type: got %d expected %d",
102
12
                 m_nBlockType, TABMAP_TOOL_BLOCK);
103
12
        CPLFree(m_pabyBuf);
104
12
        m_pabyBuf = nullptr;
105
12
        return -1;
106
12
    }
107
108
    /*-----------------------------------------------------------------
109
     * Init member variables
110
     *----------------------------------------------------------------*/
111
30
    GotoByteInBlock(0x002);
112
30
    m_numDataBytes = ReadInt16(); /* Excluding 8 bytes header */
113
30
    if (m_numDataBytes < 0 ||
114
29
        m_numDataBytes + MAP_TOOL_HEADER_SIZE > nBlockSize)
115
2
    {
116
2
        CPLError(CE_Failure, CPLE_FileIO,
117
2
                 "TABMAPToolBlock::InitBlockFromData(): m_numDataBytes=%d "
118
2
                 "incompatible with block size %d",
119
2
                 m_numDataBytes, nBlockSize);
120
2
        CPLFree(m_pabyBuf);
121
2
        m_pabyBuf = nullptr;
122
2
        return -1;
123
2
    }
124
125
28
    m_nNextToolBlock = ReadInt32();
126
28
    if (m_nNextToolBlock != 0 &&
127
0
        (m_nNextToolBlock / m_nBlockSize) * m_nBlockSize == nOffset)
128
0
    {
129
0
        CPLError(CE_Failure, CPLE_FileIO,
130
0
                 "InitBlockFromData(): self referencing block");
131
0
        CPLFree(m_pabyBuf);
132
0
        m_pabyBuf = nullptr;
133
0
        return -1;
134
0
    }
135
136
    /*-----------------------------------------------------------------
137
     * The read ptr is now located at the beginning of the data part.
138
     *----------------------------------------------------------------*/
139
28
    GotoByteInBlock(MAP_TOOL_HEADER_SIZE);
140
141
28
    return 0;
142
28
}
143
144
/**********************************************************************
145
 *                   TABMAPToolBlock::CommitToFile()
146
 *
147
 * Commit the current state of the binary block to the file to which
148
 * it has been previously attached.
149
 *
150
 * This method makes sure all values are properly set in the map object
151
 * block header and then calls TABRawBinBlock::CommitToFile() to do
152
 * the actual writing to disk.
153
 *
154
 * Returns 0 if successful or -1 if an error happened, in which case
155
 * CPLError() will have been called.
156
 **********************************************************************/
157
int TABMAPToolBlock::CommitToFile()
158
189
{
159
189
    if (m_pabyBuf == nullptr)
160
0
    {
161
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
162
0
                 "CommitToFile(): Block has not been initialized yet!");
163
0
        return -1;
164
0
    }
165
166
    /*-----------------------------------------------------------------
167
     * Nothing to do here if block has not been modified
168
     *----------------------------------------------------------------*/
169
189
    if (!m_bModified)
170
0
        return 0;
171
172
    /*-----------------------------------------------------------------
173
     * Make sure 8 bytes block header is up to date.
174
     *----------------------------------------------------------------*/
175
189
    GotoByteInBlock(0x000);
176
177
189
    WriteInt16(TABMAP_TOOL_BLOCK);  // Block type code
178
189
    CPLAssert(m_nSizeUsed >= MAP_TOOL_HEADER_SIZE &&
179
189
              m_nSizeUsed < MAP_TOOL_HEADER_SIZE + 32768);
180
189
    WriteInt16(static_cast<GInt16>(m_nSizeUsed -
181
189
                                   MAP_TOOL_HEADER_SIZE));  // num. bytes used
182
189
    WriteInt32(m_nNextToolBlock);
183
184
189
    int nStatus = CPLGetLastErrorType() == CE_Failure ? -1 : 0;
185
186
    /*-----------------------------------------------------------------
187
     * OK, call the base class to write the block to disk.
188
     *----------------------------------------------------------------*/
189
189
    if (nStatus == 0)
190
189
    {
191
#ifdef DEBUG_VERBOSE
192
        CPLDebug("MITAB", "Committing TOOL block to offset %d", m_nFileOffset);
193
#endif
194
189
        nStatus = TABRawBinBlock::CommitToFile();
195
189
    }
196
197
189
    return nStatus;
198
189
}
199
200
/**********************************************************************
201
 *                   TABMAPToolBlock::InitNewBlock()
202
 *
203
 * Initialize a newly created block so that it knows to which file it
204
 * is attached, its block size, etc . and then perform any specific
205
 * initialization for this block type, including writing a default
206
 * block header, etc. and leave the block ready to receive data.
207
 *
208
 * This is an alternative to calling ReadFromFile() or InitBlockFromData()
209
 * that puts the block in a stable state without loading any initial
210
 * data in it.
211
 *
212
 * Returns 0 if successful or -1 if an error happened, in which case
213
 * CPLError() will have been called.
214
 **********************************************************************/
215
int TABMAPToolBlock::InitNewBlock(VSILFILE *fpSrc, int nBlockSize,
216
                                  int nFileOffset /* = 0*/)
217
288
{
218
#ifdef DEBUG_VERBOSE
219
    CPLDebug("MITAB", "Instantiating new TOOL block at offset %d", nFileOffset);
220
#endif
221
222
    /*-----------------------------------------------------------------
223
     * Start with the default initialization
224
     *----------------------------------------------------------------*/
225
288
    if (TABRawBinBlock::InitNewBlock(fpSrc, nBlockSize, nFileOffset) != 0)
226
0
        return -1;
227
228
    /*-----------------------------------------------------------------
229
     * And then set default values for the block header.
230
     *----------------------------------------------------------------*/
231
288
    m_nNextToolBlock = 0;
232
233
288
    m_numDataBytes = 0;
234
235
288
    GotoByteInBlock(0x000);
236
237
288
    if (m_eAccess != TABRead)
238
189
    {
239
189
        WriteInt16(TABMAP_TOOL_BLOCK);  // Block type code
240
189
        WriteInt16(0);                  // num. bytes used, excluding header
241
189
        WriteInt32(0);                  // Pointer to next tool block
242
189
    }
243
244
288
    if (CPLGetLastErrorType() == CE_Failure)
245
0
        return -1;
246
247
288
    return 0;
248
288
}
249
250
/**********************************************************************
251
 *                   TABMAPToolBlock::SetNextToolBlock()
252
 *
253
 * Set the address (offset from beginning of file) of the drawing tool block
254
 * that follows the current one.
255
 **********************************************************************/
256
void TABMAPToolBlock::SetNextToolBlock(GInt32 nNextToolBlockAddress)
257
0
{
258
0
    m_nNextToolBlock = nNextToolBlockAddress;
259
0
}
260
261
/**********************************************************************
262
 *                   TABMAPToolBlock::SetMAPBlockManagerRef()
263
 *
264
 * Pass a reference to the block manager object for the file this
265
 * block belongs to.  The block manager will be used by this object
266
 * when it needs to automatically allocate a new block.
267
 **********************************************************************/
268
void TABMAPToolBlock::SetMAPBlockManagerRef(TABBinBlockManager *poBlockMgr)
269
189
{
270
189
    m_poBlockManagerRef = poBlockMgr;
271
189
}
272
273
/**********************************************************************
274
 *                   TABMAPToolBlock::ReadBytes()
275
 *
276
 * Cover function for TABRawBinBlock::ReadBytes() that will automagically
277
 * load the next coordinate block in the chain before reading the
278
 * requested bytes if we are at the end of the current block and if
279
 * m_nNextToolBlock is a valid block.
280
 *
281
 * Then the control is passed to TABRawBinBlock::ReadBytes() to finish the
282
 * work:
283
 * Copy the number of bytes from the data block's internal buffer to
284
 * the user's buffer pointed by pabyDstBuf.
285
 *
286
 * Passing pabyDstBuf = NULL will only move the read pointer by the
287
 * specified number of bytes as if the copy had happened... but it
288
 * won't crash.
289
 *
290
 * Returns 0 if successful or -1 if an error happened, in which case
291
 * CPLError() will have been called.
292
 **********************************************************************/
293
int TABMAPToolBlock::ReadBytes(int numBytes, GByte *pabyDstBuf)
294
911
{
295
911
    if (m_pabyBuf && m_nCurPos >= (m_numDataBytes + MAP_TOOL_HEADER_SIZE) &&
296
10
        m_nNextToolBlock > 0)
297
0
    {
298
0
        int nStatus = GotoByteInFile(m_nNextToolBlock);
299
0
        if (nStatus != 0)
300
0
        {
301
            // Failed.... an error has already been reported.
302
0
            return nStatus;
303
0
        }
304
305
0
        GotoByteInBlock(MAP_TOOL_HEADER_SIZE);  // Move pointer past header
306
307
        // FIXME ? what about a corrupted tab file that would have more than
308
        // 255 blocks
309
0
        m_numBlocksInChain++;
310
0
    }
311
312
911
    return TABRawBinBlock::ReadBytes(numBytes, pabyDstBuf);
313
911
}
314
315
/**********************************************************************
316
 *                   TABMAPToolBlock::WriteBytes()
317
 *
318
 * Cover function for TABRawBinBlock::WriteBytes() that will automagically
319
 * CommitToFile() the current block and create a new one if we are at
320
 * the end of the current block.
321
 *
322
 * Then the control is passed to TABRawBinBlock::WriteBytes() to finish the
323
 * work.
324
 *
325
 * Passing pabySrcBuf = NULL will only move the write pointer by the
326
 * specified number of bytes as if the copy had happened... but it
327
 * won't crash.
328
 *
329
 * Returns 0 if successful or -1 if an error happened, in which case
330
 * CPLError() will have been called.
331
 **********************************************************************/
332
int TABMAPToolBlock::WriteBytes(int nBytesToWrite, const GByte *pabySrcBuf)
333
5.48k
{
334
5.48k
    if (m_eAccess == TABWrite && m_poBlockManagerRef &&
335
4.91k
        (m_nBlockSize - m_nCurPos) < nBytesToWrite)
336
0
    {
337
0
        if (m_numBlocksInChain >= 255)
338
0
        {
339
0
            CPLError(CE_Failure, CPLE_NotSupported,
340
0
                     "Maximum number of 255 tool blocks reached");
341
0
            return -1;
342
0
        }
343
344
0
        int nNewBlockOffset = m_poBlockManagerRef->AllocNewBlock("TOOL");
345
0
        SetNextToolBlock(nNewBlockOffset);
346
347
0
        if (CommitToFile() != 0 ||
348
0
            InitNewBlock(m_fp, m_nBlockSize, nNewBlockOffset) != 0)
349
0
        {
350
            // An error message should have already been reported.
351
0
            return -1;
352
0
        }
353
354
0
        m_numBlocksInChain++;
355
0
    }
356
357
5.48k
    return TABRawBinBlock::WriteBytes(nBytesToWrite, pabySrcBuf);
358
5.48k
}
359
360
/**********************************************************************
361
 *                   TABMAPToolBlock::CheckAvailableSpace()
362
 *
363
 * Check if an object of the specified type can fit in
364
 * current block.  If it can't fit then force committing current block
365
 * and allocating a new one.
366
 *
367
 * Returns 0 if successful or -1 if an error happened, in which case
368
 * CPLError() will have been called.
369
 **********************************************************************/
370
int TABMAPToolBlock::CheckAvailableSpace(int nToolType)
371
505
{
372
505
    int nBytesToWrite = 0;
373
374
505
    switch (nToolType)
375
505
    {
376
174
        case TABMAP_TOOL_PEN:
377
174
            nBytesToWrite = 11;
378
174
            break;
379
154
        case TABMAP_TOOL_BRUSH:
380
154
            nBytesToWrite = 13;
381
154
            break;
382
0
        case TABMAP_TOOL_FONT:
383
0
            nBytesToWrite = 37;
384
0
            break;
385
177
        case TABMAP_TOOL_SYMBOL:
386
177
            nBytesToWrite = 13;
387
177
            break;
388
0
        default:
389
0
            CPLAssert(false);
390
505
    }
391
392
505
    if (GetNumUnusedBytes() < nBytesToWrite)
393
0
    {
394
0
        if (m_numBlocksInChain >= 255)
395
0
        {
396
0
            CPLError(CE_Failure, CPLE_NotSupported,
397
0
                     "Maximum number of 255 tool blocks reached");
398
0
            return -1;
399
0
        }
400
0
        int nNewBlockOffset = m_poBlockManagerRef->AllocNewBlock("TOOL");
401
0
        SetNextToolBlock(nNewBlockOffset);
402
403
0
        if (CommitToFile() != 0 ||
404
0
            InitNewBlock(m_fp, m_nBlockSize, nNewBlockOffset) != 0)
405
0
        {
406
            // An error message should have already been reported.
407
0
            return -1;
408
0
        }
409
410
0
        m_numBlocksInChain++;
411
0
    }
412
413
505
    return 0;
414
505
}
415
416
/**********************************************************************
417
 *                   TABMAPToolBlock::Dump()
418
 *
419
 * Dump block contents... available only in DEBUG mode.
420
 **********************************************************************/
421
#ifdef DEBUG
422
423
void TABMAPToolBlock::Dump(FILE *fpOut /*=NULL*/)
424
{
425
    if (fpOut == nullptr)
426
        fpOut = stdout;
427
428
    fprintf(fpOut, "----- TABMAPToolBlock::Dump() -----\n");
429
    if (m_pabyBuf == nullptr)
430
    {
431
        fprintf(fpOut, "Block has not been initialized yet.");
432
    }
433
    else
434
    {
435
        fprintf(fpOut, "Tool Block (type %d) at offset %d.\n", m_nBlockType,
436
                m_nFileOffset);
437
        fprintf(fpOut, "  m_numDataBytes        = %d\n", m_numDataBytes);
438
        fprintf(fpOut, "  m_nNextToolBlock     = %d\n", m_nNextToolBlock);
439
    }
440
441
    fflush(fpOut);
442
}
443
444
#endif  // DEBUG