Coverage Report

Created: 2025-06-09 08:44

/src/gdal/ogr/ogrsf_frmts/mitab/mitab_idfile.cpp
Line
Count
Source (jump to first uncovered line)
1
/**********************************************************************
2
 *
3
 * Name:     mitab_idfile.cpp
4
 * Project:  MapInfo TAB Read/Write library
5
 * Language: C++
6
 * Purpose:  Implementation of the TABIDFile class used to handle
7
 *           reading/writing of the .ID file attached to a .MAP file
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 <algorithm>
21
#include <limits.h>
22
#include <string.h>
23
24
#include "cpl_conv.h"
25
#include "cpl_error.h"
26
#include "cpl_vsi.h"
27
#include "mitab_priv.h"
28
#include "mitab_utils.h"
29
30
/*=====================================================================
31
 *                      class TABIDFile
32
 *====================================================================*/
33
34
/**********************************************************************
35
 *                   TABIDFile::TABIDFile()
36
 *
37
 * Constructor.
38
 **********************************************************************/
39
TABIDFile::TABIDFile()
40
237
    : m_pszFname(nullptr), m_fp(nullptr), m_eAccessMode(TABRead),
41
237
      m_poIDBlock(nullptr), m_nBlockSize(0), m_nMaxId(-1)
42
237
{
43
237
}
44
45
/**********************************************************************
46
 *                   TABIDFile::~TABIDFile()
47
 *
48
 * Destructor.
49
 **********************************************************************/
50
TABIDFile::~TABIDFile()
51
237
{
52
237
    Close();
53
237
}
54
55
/**********************************************************************
56
 *                   TABIDFile::Open()
57
 *
58
 * Compatibility layer with new interface.
59
 * Return 0 on success, -1 in case of failure.
60
 **********************************************************************/
61
62
int TABIDFile::Open(const char *pszFname, const char *pszAccess)
63
0
{
64
    // cppcheck-suppress nullPointer
65
0
    if (STARTS_WITH_CI(pszAccess, "r"))
66
0
        return Open(pszFname, TABRead);
67
0
    if (STARTS_WITH_CI(pszAccess, "w"))
68
0
        return Open(pszFname, TABWrite);
69
70
0
    CPLError(CE_Failure, CPLE_FileIO,
71
0
             "Open() failed: access mode \"%s\" not supported", pszAccess);
72
0
    return -1;
73
0
}
74
75
/**********************************************************************
76
 *                   TABIDFile::Open()
77
 *
78
 * Open a .ID file, and initialize the structures to be ready to read
79
 * objects from it.
80
 *
81
 * If the filename that is passed in contains a .MAP extension then
82
 * the extension will be changed to .ID before trying to open the file.
83
 *
84
 * Returns 0 on success, -1 on error.
85
 **********************************************************************/
86
int TABIDFile::Open(const char *pszFname, TABAccess eAccess)
87
237
{
88
237
    if (m_fp)
89
0
    {
90
0
        CPLError(CE_Failure, CPLE_FileIO,
91
0
                 "Open() failed: object already contains an open file");
92
0
        return -1;
93
0
    }
94
95
    // Validate access mode and make sure we use binary access.
96
    // Note that in Write mode we need TABReadWrite since we do random
97
    // updates in the index as data blocks are split.
98
237
    const char *pszAccess = nullptr;
99
237
    if (eAccess == TABRead)
100
209
    {
101
209
        m_eAccessMode = TABRead;
102
209
        pszAccess = "rb";
103
209
    }
104
28
    else if (eAccess == TABWrite)
105
28
    {
106
28
        m_eAccessMode = TABReadWrite;
107
28
        pszAccess = "wb+";
108
28
    }
109
0
    else if (eAccess == TABReadWrite)
110
0
    {
111
0
        m_eAccessMode = TABReadWrite;
112
0
        pszAccess = "rb+";
113
0
    }
114
0
    else
115
0
    {
116
0
        CPLError(CE_Failure, CPLE_FileIO,
117
0
                 "Open() failed: access mode \"%d\" not supported", eAccess);
118
0
        return -1;
119
0
    }
120
121
    // Change .MAP extension to .ID if necessary.
122
237
    m_pszFname = CPLStrdup(pszFname);
123
124
237
    const int nLen = static_cast<int>(strlen(m_pszFname));
125
237
    if (nLen > 4 && strcmp(m_pszFname + nLen - 4, ".MAP") == 0)
126
0
        strcpy(m_pszFname + nLen - 4, ".ID");
127
237
    else if (nLen > 4 && strcmp(m_pszFname + nLen - 4, ".map") == 0)
128
236
        strcpy(m_pszFname + nLen - 4, ".id");
129
130
237
#ifndef _WIN32
131
    // Change .MAP extension to .ID if necessary.
132
237
    TABAdjustFilenameExtension(m_pszFname);
133
237
#endif
134
135
    // Open file.
136
237
    m_fp = VSIFOpenL(m_pszFname, pszAccess);
137
138
237
    if (m_fp == nullptr)
139
2
    {
140
2
        CPLError(CE_Failure, CPLE_FileIO, "Open() failed for %s", m_pszFname);
141
2
        CPLFree(m_pszFname);
142
2
        m_pszFname = nullptr;
143
2
        return -1;
144
2
    }
145
146
235
    if (m_eAccessMode == TABRead || m_eAccessMode == TABReadWrite)
147
235
    {
148
        // READ access:
149
        // Establish the number of object IDs from the size of the file.
150
235
        VSIStatBufL sStatBuf;
151
235
        if (VSIStatL(m_pszFname, &sStatBuf) == -1)
152
0
        {
153
0
            CPLError(CE_Failure, CPLE_FileIO, "stat() failed for %s",
154
0
                     m_pszFname);
155
0
            Close();
156
0
            return -1;
157
0
        }
158
159
235
        if (static_cast<vsi_l_offset>(sStatBuf.st_size) >
160
235
            static_cast<vsi_l_offset>(INT_MAX / 4))
161
0
            m_nMaxId = INT_MAX / 4;
162
235
        else
163
235
            m_nMaxId = static_cast<int>(sStatBuf.st_size / 4);
164
235
        m_nBlockSize = std::min(1024, m_nMaxId * 4);
165
166
        // Read the first block from the file.
167
235
        m_poIDBlock = new TABRawBinBlock(m_eAccessMode, FALSE);
168
169
235
        if (m_nMaxId == 0)
170
30
        {
171
            // .ID file size = 0 ... just allocate a blank block but
172
            // it won't get really used anyways.
173
30
            m_nBlockSize = 512;
174
30
            m_poIDBlock->InitNewBlock(m_fp, m_nBlockSize, 0);
175
30
        }
176
205
        else if (m_poIDBlock->ReadFromFile(m_fp, 0, m_nBlockSize) != 0)
177
0
        {
178
            // CPLError() has already been called.
179
0
            Close();
180
0
            return -1;
181
0
        }
182
235
    }
183
0
    else
184
0
    {
185
        // WRITE access:
186
        // Get ready to write to the file.
187
0
        m_poIDBlock = new TABRawBinBlock(m_eAccessMode, FALSE);
188
0
        m_nMaxId = 0;
189
0
        m_nBlockSize = 1024;
190
0
        m_poIDBlock->InitNewBlock(m_fp, m_nBlockSize, 0);
191
0
    }
192
193
235
    return 0;
194
235
}
195
196
/**********************************************************************
197
 *                   TABIDFile::Close()
198
 *
199
 * Close current file, and release all memory used.
200
 *
201
 * Returns 0 on success, -1 on error.
202
 **********************************************************************/
203
int TABIDFile::Close()
204
474
{
205
474
    if (m_fp == nullptr)
206
239
        return 0;
207
208
    // Write access: commit latest changes to the file.
209
235
    if (m_eAccessMode != TABRead)
210
28
        SyncToDisk();
211
212
    // Delete all structures
213
235
    delete m_poIDBlock;
214
235
    m_poIDBlock = nullptr;
215
216
    // Close file
217
235
    VSIFCloseL(m_fp);
218
235
    m_fp = nullptr;
219
220
235
    CPLFree(m_pszFname);
221
235
    m_pszFname = nullptr;
222
223
235
    return 0;
224
474
}
225
226
/************************************************************************/
227
/*                            SyncToDisk()                             */
228
/************************************************************************/
229
230
int TABIDFile::SyncToDisk()
231
56
{
232
56
    if (m_eAccessMode == TABRead)
233
0
    {
234
0
        CPLError(CE_Failure, CPLE_NotSupported,
235
0
                 "SyncToDisk() can be used only with Write access.");
236
0
        return -1;
237
0
    }
238
239
56
    if (m_poIDBlock == nullptr)
240
0
        return 0;
241
242
56
    return m_poIDBlock->CommitToFile();
243
56
}
244
245
/**********************************************************************
246
 *                   TABIDFile::GetObjPtr()
247
 *
248
 * Return the offset in the .MAP file where the map object with the
249
 * specified id is located.
250
 *
251
 * Note that object ids are positive and start at 1.
252
 *
253
 * An object Id of '0' means that object has no geometry.
254
 *
255
 * Returns a value >= 0 on success, -1 on error.
256
 **********************************************************************/
257
GInt32 TABIDFile::GetObjPtr(GInt32 nObjId)
258
765
{
259
765
    if (m_poIDBlock == nullptr)
260
0
        return -1;
261
262
765
    if (nObjId < 1 || nObjId > m_nMaxId)
263
0
    {
264
0
        CPLError(CE_Failure, CPLE_IllegalArg,
265
0
                 "GetObjPtr(): Invalid object ID %d (valid range is [1..%d])",
266
0
                 nObjId, m_nMaxId);
267
0
        return -1;
268
0
    }
269
270
765
    if (m_poIDBlock->GotoByteInFile((nObjId - 1) * 4) != 0)
271
0
        return -1;
272
273
765
    return m_poIDBlock->ReadInt32();
274
765
}
275
276
/**********************************************************************
277
 *                   TABIDFile::SetObjPtr()
278
 *
279
 * Set the offset in the .MAP file where the map object with the
280
 * specified id is located.
281
 *
282
 * Note that object ids are positive and start at 1.
283
 *
284
 * An object Id of '0' means that object has no geometry.
285
 *
286
 * Returns a value of 0 on success, -1 on error.
287
 **********************************************************************/
288
int TABIDFile::SetObjPtr(GInt32 nObjId, GInt32 nObjPtr)
289
245k
{
290
245k
    if (m_poIDBlock == nullptr)
291
0
        return -1;
292
293
245k
    if (m_eAccessMode == TABRead)
294
0
    {
295
0
        CPLError(CE_Failure, CPLE_NotSupported,
296
0
                 "SetObjPtr() can be used only with Write access.");
297
0
        return -1;
298
0
    }
299
300
245k
    if (nObjId < 1)
301
0
    {
302
0
        CPLError(
303
0
            CE_Failure, CPLE_IllegalArg,
304
0
            "SetObjPtr(): Invalid object ID %d (must be greater than zero)",
305
0
            nObjId);
306
0
        return -1;
307
0
    }
308
309
    // GotoByteInFile() will automagically commit current block and init
310
    // a new one if necessary.
311
245k
    const GInt32 nLastIdBlock = ((m_nMaxId - 1) * 4) / m_nBlockSize;
312
245k
    const GInt32 nTargetIdBlock = ((nObjId - 1) * 4) / m_nBlockSize;
313
245k
    if (m_nMaxId > 0 && nTargetIdBlock <= nLastIdBlock)
314
243k
    {
315
        // Pass second arg to GotoByteInFile() to force reading from file
316
        // when going back to blocks already committed.
317
243k
        if (m_poIDBlock->GotoByteInFile((nObjId - 1) * 4, TRUE) != 0)
318
0
            return -1;
319
243k
    }
320
1.92k
    else
321
1.92k
    {
322
        // If we reach EOF then a new empty block will have to be allocated.
323
1.92k
        if (m_poIDBlock->GotoByteInFile((nObjId - 1) * 4) != 0)
324
0
            return -1;
325
1.92k
    }
326
327
245k
    m_nMaxId = std::max(m_nMaxId, nObjId);
328
329
245k
    return m_poIDBlock->WriteInt32(nObjPtr);
330
245k
}
331
332
/**********************************************************************
333
 *                   TABIDFile::GetMaxObjId()
334
 *
335
 * Return the value of the biggest valid object id.
336
 *
337
 * Note that object ids are positive and start at 1.
338
 *
339
 * Returns a value >= 0 on success, -1 on error.
340
 **********************************************************************/
341
GInt32 TABIDFile::GetMaxObjId()
342
0
{
343
0
    return m_nMaxId;
344
0
}
345
346
/**********************************************************************
347
 *                   TABIDFile::Dump()
348
 *
349
 * Dump block contents... available only in DEBUG mode.
350
 **********************************************************************/
351
#ifdef DEBUG
352
353
void TABIDFile::Dump(FILE *fpOut /*=NULL*/)
354
{
355
    if (fpOut == nullptr)
356
        fpOut = stdout;
357
358
    fprintf(fpOut, "----- TABIDFile::Dump() -----\n");
359
360
    if (m_fp == nullptr)
361
    {
362
        fprintf(fpOut, "File is not opened.\n");
363
    }
364
    else
365
    {
366
        fprintf(fpOut, "File is opened: %s\n", m_pszFname);
367
        fprintf(fpOut, "Current index block follows ...\n\n");
368
        m_poIDBlock->Dump(fpOut);
369
        fprintf(fpOut, "... end of index block.\n\n");
370
    }
371
372
    fflush(fpOut);
373
}
374
375
#endif  // DEBUG