Line data Source code
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 1439 : TABIDFile::TABIDFile()
40 : : m_pszFname(nullptr), m_fp(nullptr), m_eAccessMode(TABRead),
41 1439 : m_poIDBlock(nullptr), m_nBlockSize(0), m_nMaxId(-1)
42 : {
43 1439 : }
44 :
45 : /**********************************************************************
46 : * TABIDFile::~TABIDFile()
47 : *
48 : * Destructor.
49 : **********************************************************************/
50 2878 : TABIDFile::~TABIDFile()
51 : {
52 1439 : Close();
53 1439 : }
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 0 : int TABIDFile::Open(const char *pszFname, const char *pszAccess)
63 : {
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 : "Open() failed: access mode \"%s\" not supported", pszAccess);
72 0 : return -1;
73 : }
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 1439 : int TABIDFile::Open(const char *pszFname, TABAccess eAccess)
87 : {
88 1439 : if (m_fp)
89 : {
90 0 : CPLError(CE_Failure, CPLE_FileIO,
91 : "Open() failed: object already contains an open file");
92 0 : return -1;
93 : }
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 1439 : const char *pszAccess = nullptr;
99 1439 : if (eAccess == TABRead)
100 : {
101 242 : m_eAccessMode = TABRead;
102 242 : pszAccess = "rb";
103 : }
104 1197 : else if (eAccess == TABWrite)
105 : {
106 122 : m_eAccessMode = TABReadWrite;
107 122 : pszAccess = "wb+";
108 : }
109 1075 : else if (eAccess == TABReadWrite)
110 : {
111 1075 : m_eAccessMode = TABReadWrite;
112 1075 : pszAccess = "rb+";
113 : }
114 : else
115 : {
116 0 : CPLError(CE_Failure, CPLE_FileIO,
117 : "Open() failed: access mode \"%d\" not supported", eAccess);
118 0 : return -1;
119 : }
120 :
121 : // Change .MAP extension to .ID if necessary.
122 1439 : m_pszFname = CPLStrdup(pszFname);
123 :
124 1439 : const int nLen = static_cast<int>(strlen(m_pszFname));
125 1439 : if (nLen > 4 && strcmp(m_pszFname + nLen - 4, ".MAP") == 0)
126 3 : strcpy(m_pszFname + nLen - 4, ".ID");
127 1436 : else if (nLen > 4 && strcmp(m_pszFname + nLen - 4, ".map") == 0)
128 1436 : strcpy(m_pszFname + nLen - 4, ".id");
129 :
130 : #ifndef _WIN32
131 : // Change .MAP extension to .ID if necessary.
132 1439 : TABAdjustFilenameExtension(m_pszFname);
133 : #endif
134 :
135 : // Open file.
136 1439 : m_fp = VSIFOpenL(m_pszFname, pszAccess);
137 :
138 1439 : if (m_fp == nullptr)
139 : {
140 0 : CPLError(CE_Failure, CPLE_FileIO, "Open() failed for %s", m_pszFname);
141 0 : CPLFree(m_pszFname);
142 0 : m_pszFname = nullptr;
143 0 : return -1;
144 : }
145 :
146 1439 : if (m_eAccessMode == TABRead || m_eAccessMode == TABReadWrite)
147 : {
148 : // READ access:
149 : // Establish the number of object IDs from the size of the file.
150 : VSIStatBufL sStatBuf;
151 1439 : if (VSIStatL(m_pszFname, &sStatBuf) == -1)
152 : {
153 0 : CPLError(CE_Failure, CPLE_FileIO, "stat() failed for %s",
154 : m_pszFname);
155 0 : Close();
156 0 : return -1;
157 : }
158 :
159 1439 : if (static_cast<vsi_l_offset>(sStatBuf.st_size) >
160 : static_cast<vsi_l_offset>(INT_MAX / 4))
161 0 : m_nMaxId = INT_MAX / 4;
162 : else
163 1439 : m_nMaxId = static_cast<int>(sStatBuf.st_size / 4);
164 1439 : m_nBlockSize = std::min(1024, m_nMaxId * 4);
165 :
166 : // Read the first block from the file.
167 1439 : m_poIDBlock = new TABRawBinBlock(m_eAccessMode, FALSE);
168 :
169 1439 : if (m_nMaxId == 0)
170 : {
171 : // .ID file size = 0 ... just allocate a blank block but
172 : // it won't get really used anyways.
173 133 : m_nBlockSize = 512;
174 133 : m_poIDBlock->InitNewBlock(m_fp, m_nBlockSize, 0);
175 : }
176 1306 : else if (m_poIDBlock->ReadFromFile(m_fp, 0, m_nBlockSize) != 0)
177 : {
178 : // CPLError() has already been called.
179 0 : Close();
180 0 : return -1;
181 1439 : }
182 : }
183 : else
184 : {
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 : }
192 :
193 1439 : return 0;
194 : }
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 2878 : int TABIDFile::Close()
204 : {
205 2878 : if (m_fp == nullptr)
206 1439 : return 0;
207 :
208 : // Write access: commit latest changes to the file.
209 1439 : if (m_eAccessMode != TABRead)
210 1197 : SyncToDisk();
211 :
212 : // Delete all structures
213 1439 : delete m_poIDBlock;
214 1439 : m_poIDBlock = nullptr;
215 :
216 : // Close file
217 1439 : VSIFCloseL(m_fp);
218 1439 : m_fp = nullptr;
219 :
220 1439 : CPLFree(m_pszFname);
221 1439 : m_pszFname = nullptr;
222 :
223 1439 : return 0;
224 : }
225 :
226 : /************************************************************************/
227 : /* SyncToDisk() */
228 : /************************************************************************/
229 :
230 2398 : int TABIDFile::SyncToDisk()
231 : {
232 2398 : if (m_eAccessMode == TABRead)
233 : {
234 0 : CPLError(CE_Failure, CPLE_NotSupported,
235 : "SyncToDisk() can be used only with Write access.");
236 0 : return -1;
237 : }
238 :
239 2398 : if (m_poIDBlock == nullptr)
240 0 : return 0;
241 :
242 2398 : return m_poIDBlock->CommitToFile();
243 : }
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 153055 : GInt32 TABIDFile::GetObjPtr(GInt32 nObjId)
258 : {
259 153055 : if (m_poIDBlock == nullptr)
260 0 : return -1;
261 :
262 153055 : if (nObjId < 1 || nObjId > m_nMaxId)
263 : {
264 0 : CPLError(CE_Failure, CPLE_IllegalArg,
265 : "GetObjPtr(): Invalid object ID %d (valid range is [1..%d])",
266 : nObjId, m_nMaxId);
267 0 : return -1;
268 : }
269 :
270 153055 : if (m_poIDBlock->GotoByteInFile((nObjId - 1) * 4) != 0)
271 0 : return -1;
272 :
273 153055 : return m_poIDBlock->ReadInt32();
274 : }
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 26949 : int TABIDFile::SetObjPtr(GInt32 nObjId, GInt32 nObjPtr)
289 : {
290 26949 : if (m_poIDBlock == nullptr)
291 0 : return -1;
292 :
293 26949 : if (m_eAccessMode == TABRead)
294 : {
295 0 : CPLError(CE_Failure, CPLE_NotSupported,
296 : "SetObjPtr() can be used only with Write access.");
297 0 : return -1;
298 : }
299 :
300 26949 : if (nObjId < 1)
301 : {
302 0 : CPLError(
303 : CE_Failure, CPLE_IllegalArg,
304 : "SetObjPtr(): Invalid object ID %d (must be greater than zero)",
305 : nObjId);
306 0 : return -1;
307 : }
308 :
309 : // GotoByteInFile() will automagically commit current block and init
310 : // a new one if necessary.
311 26949 : const GInt32 nLastIdBlock = ((m_nMaxId - 1) * 4) / m_nBlockSize;
312 26949 : const GInt32 nTargetIdBlock = ((nObjId - 1) * 4) / m_nBlockSize;
313 26949 : if (m_nMaxId > 0 && nTargetIdBlock <= nLastIdBlock)
314 : {
315 : // Pass second arg to GotoByteInFile() to force reading from file
316 : // when going back to blocks already committed.
317 26523 : if (m_poIDBlock->GotoByteInFile((nObjId - 1) * 4, TRUE) != 0)
318 0 : return -1;
319 : }
320 : else
321 : {
322 : // If we reach EOF then a new empty block will have to be allocated.
323 426 : if (m_poIDBlock->GotoByteInFile((nObjId - 1) * 4) != 0)
324 0 : return -1;
325 : }
326 :
327 26949 : m_nMaxId = std::max(m_nMaxId, nObjId);
328 :
329 26949 : return m_poIDBlock->WriteInt32(nObjPtr);
330 : }
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 0 : GInt32 TABIDFile::GetMaxObjId()
342 : {
343 0 : return m_nMaxId;
344 : }
345 :
346 : /**********************************************************************
347 : * TABIDFile::Dump()
348 : *
349 : * Dump block contents... available only in DEBUG mode.
350 : **********************************************************************/
351 : #ifdef DEBUG
352 :
353 0 : void TABIDFile::Dump(FILE *fpOut /*=NULL*/)
354 : {
355 0 : if (fpOut == nullptr)
356 0 : fpOut = stdout;
357 :
358 0 : fprintf(fpOut, "----- TABIDFile::Dump() -----\n");
359 :
360 0 : if (m_fp == nullptr)
361 : {
362 0 : fprintf(fpOut, "File is not opened.\n");
363 : }
364 : else
365 : {
366 0 : fprintf(fpOut, "File is opened: %s\n", m_pszFname);
367 0 : fprintf(fpOut, "Current index block follows ...\n\n");
368 0 : m_poIDBlock->Dump(fpOut);
369 0 : fprintf(fpOut, "... end of index block.\n\n");
370 : }
371 :
372 0 : fflush(fpOut);
373 0 : }
374 :
375 : #endif // DEBUG
|