/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 |