Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: Zarr driver
5 : * Author: Even Rouault <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2021, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "zarr.h"
14 :
15 : #include <algorithm>
16 : #include <cassert>
17 : #include <limits>
18 : #include <map>
19 : #include <set>
20 :
21 : /************************************************************************/
22 : /* ~ZarrGroupBase() */
23 : /************************************************************************/
24 :
25 2395 : ZarrGroupBase::~ZarrGroupBase()
26 : {
27 2395 : CPL_IGNORE_RET_VAL(ZarrGroupBase::Close());
28 2395 : }
29 :
30 : /************************************************************************/
31 : /* Close() */
32 : /************************************************************************/
33 :
34 7943 : bool ZarrGroupBase::Close()
35 : {
36 7943 : bool ret = true;
37 :
38 9236 : for (auto &kv : m_oMapGroups)
39 : {
40 1293 : ret = kv.second->Close() && ret;
41 : }
42 :
43 15880 : for (auto &kv : m_oMapMDArrays)
44 : {
45 7937 : ret = kv.second->Flush() && ret;
46 : }
47 7943 : return ret;
48 : }
49 :
50 : /************************************************************************/
51 : /* Flush() */
52 : /************************************************************************/
53 :
54 42 : bool ZarrGroupBase::Flush()
55 : {
56 42 : bool ret = true;
57 :
58 169 : for (auto &kv : m_oMapMDArrays)
59 : {
60 127 : ret = kv.second->Flush() && ret;
61 : }
62 42 : return ret;
63 : }
64 :
65 : /************************************************************************/
66 : /* GetMDArrayNames() */
67 : /************************************************************************/
68 :
69 1427 : std::vector<std::string> ZarrGroupBase::GetMDArrayNames(CSLConstList) const
70 : {
71 1427 : if (!CheckValidAndErrorOutIfNot())
72 0 : return {};
73 :
74 1427 : if (!m_bDirectoryExplored)
75 354 : ExploreDirectory();
76 :
77 1427 : return m_aosArrays;
78 : }
79 :
80 : /************************************************************************/
81 : /* RegisterArray() */
82 : /************************************************************************/
83 :
84 2387 : void ZarrGroupBase::RegisterArray(const std::shared_ptr<ZarrArray> &array) const
85 : {
86 2387 : m_oMapMDArrays[array->GetName()] = array;
87 2387 : if (!cpl::contains(m_oSetArrayNames, array->GetName()))
88 : {
89 2297 : m_oSetArrayNames.insert(array->GetName());
90 2297 : m_aosArrays.emplace_back(array->GetName());
91 : }
92 4774 : array->RegisterGroup(
93 4774 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()));
94 2387 : }
95 :
96 : /************************************************************************/
97 : /* GetGroupNames() */
98 : /************************************************************************/
99 :
100 419 : std::vector<std::string> ZarrGroupBase::GetGroupNames(CSLConstList) const
101 : {
102 419 : if (!CheckValidAndErrorOutIfNot())
103 0 : return {};
104 :
105 419 : if (!m_bDirectoryExplored)
106 38 : ExploreDirectory();
107 :
108 419 : return m_aosGroups;
109 : }
110 :
111 : /************************************************************************/
112 : /* DeleteGroup() */
113 : /************************************************************************/
114 :
115 20 : bool ZarrGroupBase::DeleteGroup(const std::string &osName,
116 : CSLConstList /*papszOptions*/)
117 : {
118 20 : if (!CheckValidAndErrorOutIfNot())
119 0 : return false;
120 :
121 20 : if (!m_bUpdatable)
122 : {
123 6 : CPLError(CE_Failure, CPLE_NotSupported,
124 : "Dataset not open in update mode");
125 6 : return false;
126 : }
127 14 : if (CPLHasPathTraversal(osName.c_str()))
128 : {
129 0 : CPLError(CE_Failure, CPLE_AppDefined, "Path traversal detected in %s",
130 : osName.c_str());
131 0 : return false;
132 : }
133 14 : GetGroupNames();
134 :
135 14 : auto oIterNames = std::find(m_aosGroups.begin(), m_aosGroups.end(), osName);
136 14 : if (oIterNames == m_aosGroups.end())
137 : {
138 6 : CPLError(CE_Failure, CPLE_AppDefined,
139 : "Group %s is not a sub-group of this group", osName.c_str());
140 6 : return false;
141 : }
142 :
143 : const std::string osSubDirName =
144 16 : CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
145 8 : if (VSIRmdirRecursive(osSubDirName.c_str()) != 0)
146 : {
147 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot delete %s",
148 : osSubDirName.c_str());
149 0 : return false;
150 : }
151 :
152 8 : m_poSharedResource->DeleteZMetadataItemRecursive(osSubDirName);
153 :
154 8 : m_oSetGroupNames.erase(osName);
155 8 : m_aosGroups.erase(oIterNames);
156 :
157 8 : auto oIter = m_oMapGroups.find(osName);
158 8 : if (oIter != m_oMapGroups.end())
159 : {
160 6 : oIter->second->Deleted();
161 6 : m_oMapGroups.erase(oIter);
162 : }
163 :
164 8 : return true;
165 : }
166 :
167 : /************************************************************************/
168 : /* NotifyChildrenOfDeletion() */
169 : /************************************************************************/
170 :
171 8 : void ZarrGroupBase::NotifyChildrenOfDeletion()
172 : {
173 10 : for (const auto &oIter : m_oMapGroups)
174 2 : oIter.second->ParentDeleted();
175 :
176 14 : for (const auto &oIter : m_oMapMDArrays)
177 6 : oIter.second->ParentDeleted();
178 :
179 8 : m_oAttrGroup.ParentDeleted();
180 :
181 16 : for (const auto &oIter : m_oMapDimensions)
182 8 : oIter.second->ParentDeleted();
183 8 : }
184 :
185 : /************************************************************************/
186 : /* ZarrGroupBase::CreateAttribute() */
187 : /************************************************************************/
188 :
189 95 : std::shared_ptr<GDALAttribute> ZarrGroupBase::CreateAttribute(
190 : const std::string &osName, const std::vector<GUInt64> &anDimensions,
191 : const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
192 : {
193 95 : if (!CheckValidAndErrorOutIfNot())
194 0 : return nullptr;
195 :
196 95 : if (!m_bUpdatable)
197 : {
198 6 : CPLError(CE_Failure, CPLE_NotSupported,
199 : "Dataset not open in update mode");
200 6 : return nullptr;
201 : }
202 89 : if (anDimensions.size() >= 2)
203 : {
204 4 : CPLError(CE_Failure, CPLE_NotSupported,
205 : "Cannot create attributes of dimension >= 2");
206 4 : return nullptr;
207 : }
208 85 : LoadAttributes();
209 : return m_oAttrGroup.CreateAttribute(osName, anDimensions, oDataType,
210 85 : papszOptions);
211 : }
212 :
213 : /************************************************************************/
214 : /* ZarrGroupBase::DeleteAttribute() */
215 : /************************************************************************/
216 :
217 18 : bool ZarrGroupBase::DeleteAttribute(const std::string &osName, CSLConstList)
218 : {
219 18 : if (!CheckValidAndErrorOutIfNot())
220 0 : return false;
221 :
222 18 : if (!m_bUpdatable)
223 : {
224 6 : CPLError(CE_Failure, CPLE_NotSupported,
225 : "Dataset not open in update mode");
226 6 : return false;
227 : }
228 :
229 12 : LoadAttributes();
230 12 : return m_oAttrGroup.DeleteAttribute(osName);
231 : }
232 :
233 : /************************************************************************/
234 : /* GetDimensions() */
235 : /************************************************************************/
236 :
237 : std::vector<std::shared_ptr<GDALDimension>>
238 740 : ZarrGroupBase::GetDimensions(CSLConstList) const
239 : {
240 740 : if (!CheckValidAndErrorOutIfNot())
241 0 : return {};
242 :
243 740 : if (!m_bReadFromConsolidatedMetadata && !m_bDimensionsInstantiated)
244 : {
245 339 : m_bDimensionsInstantiated = true;
246 : // We need to instantiate arrays to discover dimensions
247 678 : const auto aosArrays = GetMDArrayNames();
248 373 : for (const auto &osArray : aosArrays)
249 : {
250 34 : OpenMDArray(osArray);
251 : }
252 : }
253 :
254 1480 : std::vector<std::shared_ptr<GDALDimension>> oRes;
255 1303 : for (const auto &oIter : m_oMapDimensions)
256 : {
257 563 : oRes.push_back(oIter.second);
258 : }
259 740 : return oRes;
260 : }
261 :
262 : /************************************************************************/
263 : /* DeleteMDArray() */
264 : /************************************************************************/
265 :
266 18 : bool ZarrGroupBase::DeleteMDArray(const std::string &osName,
267 : CSLConstList /*papszOptions*/)
268 : {
269 18 : if (!CheckValidAndErrorOutIfNot())
270 0 : return false;
271 :
272 18 : if (!m_bUpdatable)
273 : {
274 6 : CPLError(CE_Failure, CPLE_NotSupported,
275 : "Dataset not open in update mode");
276 6 : return false;
277 : }
278 12 : if (CPLHasPathTraversal(osName.c_str()))
279 : {
280 0 : CPLError(CE_Failure, CPLE_AppDefined, "Path traversal detected in %s",
281 : osName.c_str());
282 0 : return false;
283 : }
284 12 : GetMDArrayNames();
285 :
286 12 : auto oIterNames = std::find(m_aosArrays.begin(), m_aosArrays.end(), osName);
287 12 : if (oIterNames == m_aosArrays.end())
288 : {
289 6 : CPLError(CE_Failure, CPLE_AppDefined,
290 : "Array %s is not an array of this group", osName.c_str());
291 6 : return false;
292 : }
293 :
294 : const std::string osSubDirName =
295 12 : CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
296 6 : if (VSIRmdirRecursive(osSubDirName.c_str()) != 0)
297 : {
298 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot delete %s",
299 : osSubDirName.c_str());
300 0 : return false;
301 : }
302 :
303 6 : m_poSharedResource->DeleteZMetadataItemRecursive(osSubDirName);
304 :
305 6 : m_oSetArrayNames.erase(osName);
306 6 : m_aosArrays.erase(oIterNames);
307 :
308 6 : auto oIter = m_oMapMDArrays.find(osName);
309 6 : if (oIter != m_oMapMDArrays.end())
310 : {
311 4 : oIter->second->Deleted();
312 4 : m_oMapMDArrays.erase(oIter);
313 : }
314 :
315 6 : return true;
316 : }
317 :
318 : /************************************************************************/
319 : /* CreateDimension() */
320 : /************************************************************************/
321 :
322 662 : std::shared_ptr<GDALDimension> ZarrGroupBase::CreateDimension(
323 : const std::string &osName, const std::string &osType,
324 : const std::string &osDirection, GUInt64 nSize, CSLConstList)
325 : {
326 662 : if (!CheckValidAndErrorOutIfNot())
327 0 : return nullptr;
328 :
329 662 : if (osName.empty())
330 : {
331 0 : CPLError(CE_Failure, CPLE_NotSupported,
332 : "Empty dimension name not supported");
333 0 : return nullptr;
334 : }
335 662 : GetDimensions(nullptr);
336 :
337 662 : if (m_oMapDimensions.find(osName) != m_oMapDimensions.end())
338 : {
339 0 : CPLError(CE_Failure, CPLE_AppDefined,
340 : "A dimension with same name already exists");
341 0 : return nullptr;
342 : }
343 : auto newDim(std::make_shared<ZarrDimension>(
344 662 : m_poSharedResource,
345 1324 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()), GetFullName(),
346 1324 : osName, osType, osDirection, nSize));
347 662 : newDim->SetXArrayDimension();
348 662 : m_oMapDimensions[osName] = newDim;
349 662 : return newDim;
350 : }
351 :
352 : /************************************************************************/
353 : /* RenameDimension() */
354 : /************************************************************************/
355 :
356 12 : bool ZarrGroupBase::RenameDimension(const std::string &osOldName,
357 : const std::string &osNewName)
358 : {
359 12 : if (m_oMapDimensions.find(osNewName) != m_oMapDimensions.end())
360 : {
361 6 : CPLError(CE_Failure, CPLE_AppDefined,
362 : "A dimension with same name already exists");
363 6 : return false;
364 : }
365 6 : auto oIter = m_oMapDimensions.find(osOldName);
366 6 : if (oIter == m_oMapDimensions.end())
367 : {
368 0 : CPLAssert(false);
369 : return false;
370 : }
371 6 : auto poDim = std::move(oIter->second);
372 6 : m_oMapDimensions.erase(oIter);
373 6 : m_oMapDimensions[osNewName] = std::move(poDim);
374 6 : return true;
375 : }
376 :
377 : /************************************************************************/
378 : /* ZarrGroupBase::UpdateDimensionSize() */
379 : /************************************************************************/
380 :
381 7 : void ZarrGroupBase::UpdateDimensionSize(
382 : const std::shared_ptr<GDALDimension> &poUpdatedDim)
383 : {
384 14 : const auto aosGroupNames = GetGroupNames();
385 7 : for (const auto &osGroupName : aosGroupNames)
386 : {
387 0 : auto poSubGroup = OpenZarrGroup(osGroupName);
388 0 : if (poSubGroup)
389 : {
390 0 : poSubGroup->UpdateDimensionSize(poUpdatedDim);
391 : }
392 : }
393 14 : const auto aosArrayNames = GetMDArrayNames();
394 22 : for (const auto &osArrayName : aosArrayNames)
395 : {
396 : // Disable checks that size of variables referenced by _ARRAY_DIMENSIONS
397 : // are consistent with array shapes, as we are in the middle of updating
398 : // things
399 15 : m_bDimSizeInUpdate = true;
400 30 : auto poArray = OpenZarrArray(osArrayName);
401 15 : m_bDimSizeInUpdate = false;
402 15 : if (poArray)
403 : {
404 39 : for (auto &poDim : poArray->GetDimensions())
405 : {
406 24 : if (poDim->GetFullName() == poUpdatedDim->GetFullName())
407 : {
408 : auto poModifiableDim =
409 30 : std::dynamic_pointer_cast<ZarrDimension>(poDim);
410 15 : CPLAssert(poModifiableDim);
411 15 : poModifiableDim->SetSize(poUpdatedDim->GetSize());
412 15 : poArray->SetDefinitionModified(true);
413 : }
414 : }
415 : }
416 : }
417 7 : }
418 :
419 : /************************************************************************/
420 : /* ZarrGroupBase::NotifyArrayRenamed() */
421 : /************************************************************************/
422 :
423 6 : void ZarrGroupBase::NotifyArrayRenamed(const std::string &osOldName,
424 : const std::string &osNewName)
425 : {
426 6 : for (auto &osName : m_aosArrays)
427 : {
428 6 : if (osName == osOldName)
429 : {
430 6 : osName = osNewName;
431 6 : break;
432 : }
433 : }
434 :
435 6 : auto oIter = m_oMapMDArrays.find(osOldName);
436 6 : if (oIter != m_oMapMDArrays.end())
437 : {
438 6 : auto poArray = std::move(oIter->second);
439 6 : m_oMapMDArrays.erase(oIter);
440 6 : m_oMapMDArrays[osNewName] = std::move(poArray);
441 : }
442 6 : }
443 :
444 : /************************************************************************/
445 : /* IsValidObjectName() */
446 : /************************************************************************/
447 :
448 : /* static */
449 763 : bool ZarrGroupBase::IsValidObjectName(const std::string &osName)
450 : {
451 2203 : return !(osName.empty() || osName == "." || osName == ".." ||
452 1440 : osName.find('/') != std::string::npos ||
453 1424 : osName.find('\\') != std::string::npos ||
454 708 : osName.find(':') != std::string::npos ||
455 1463 : STARTS_WITH(osName.c_str(), ".z"));
456 : }
457 :
458 : /************************************************************************/
459 : /* CheckArrayOrGroupWithSameNameDoesNotExist() */
460 : /************************************************************************/
461 :
462 27 : bool ZarrGroupBase::CheckArrayOrGroupWithSameNameDoesNotExist(
463 : const std::string &osName) const
464 : {
465 54 : const auto groupNames = GetGroupNames();
466 27 : if (std::find(groupNames.begin(), groupNames.end(), osName) !=
467 54 : groupNames.end())
468 : {
469 9 : CPLError(CE_Failure, CPLE_AppDefined,
470 : "A group with same name already exists");
471 9 : return false;
472 : }
473 :
474 36 : const auto arrayNames = GetMDArrayNames();
475 18 : if (std::find(arrayNames.begin(), arrayNames.end(), osName) !=
476 36 : arrayNames.end())
477 : {
478 6 : CPLError(CE_Failure, CPLE_AppDefined,
479 : "An array with same name already exists");
480 6 : return false;
481 : }
482 :
483 12 : return true;
484 : }
485 :
486 : /************************************************************************/
487 : /* Rename() */
488 : /************************************************************************/
489 :
490 36 : bool ZarrGroupBase::Rename(const std::string &osNewName)
491 : {
492 36 : if (!CheckValidAndErrorOutIfNot())
493 3 : return false;
494 :
495 33 : if (!m_bUpdatable)
496 : {
497 6 : CPLError(CE_Failure, CPLE_NotSupported,
498 : "Dataset not open in update mode");
499 6 : return false;
500 : }
501 27 : if (!IsValidObjectName(osNewName))
502 : {
503 6 : CPLError(CE_Failure, CPLE_NotSupported, "Invalid group name");
504 6 : return false;
505 : }
506 21 : if (m_osName == "/")
507 : {
508 6 : CPLError(CE_Failure, CPLE_NotSupported, "Cannot rename root group");
509 6 : return false;
510 : }
511 :
512 30 : auto pParent = std::dynamic_pointer_cast<ZarrGroupBase>(m_poParent.lock());
513 15 : if (pParent)
514 : {
515 15 : if (!pParent->CheckArrayOrGroupWithSameNameDoesNotExist(osNewName))
516 9 : return false;
517 : }
518 :
519 12 : std::string osNewDirectoryName(m_osDirectoryName);
520 6 : osNewDirectoryName.resize(osNewDirectoryName.size() - m_osName.size());
521 6 : osNewDirectoryName += osNewName;
522 :
523 6 : if (VSIRename(m_osDirectoryName.c_str(), osNewDirectoryName.c_str()) != 0)
524 : {
525 0 : CPLError(CE_Failure, CPLE_AppDefined, "Renaming of %s to %s failed",
526 : m_osDirectoryName.c_str(), osNewDirectoryName.c_str());
527 0 : return false;
528 : }
529 :
530 6 : if (pParent)
531 : {
532 6 : auto oIter = pParent->m_oMapGroups.find(m_osName);
533 6 : if (oIter != pParent->m_oMapGroups.end())
534 : {
535 6 : pParent->m_oMapGroups.erase(oIter);
536 6 : CPLAssert(m_pSelf.lock());
537 12 : pParent->m_oMapGroups[osNewName] =
538 18 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
539 : }
540 :
541 6 : for (auto &osName : pParent->m_aosGroups)
542 : {
543 6 : if (osName == m_osName)
544 : {
545 6 : osName = osNewName;
546 6 : break;
547 : }
548 : }
549 : }
550 :
551 6 : m_poSharedResource->RenameZMetadataRecursive(m_osDirectoryName,
552 : osNewDirectoryName);
553 :
554 6 : m_osDirectoryName = std::move(osNewDirectoryName);
555 :
556 6 : BaseRename(osNewName);
557 :
558 6 : return true;
559 : }
560 :
561 : /************************************************************************/
562 : /* ParentRenamed() */
563 : /************************************************************************/
564 :
565 4 : void ZarrGroupBase::ParentRenamed(const std::string &osNewParentFullName)
566 : {
567 8 : auto pParent = std::dynamic_pointer_cast<ZarrGroupBase>(m_poParent.lock());
568 : // The parent necessarily exist, since it notified us
569 4 : CPLAssert(pParent);
570 :
571 8 : m_osDirectoryName = CPLFormFilenameSafe(pParent->m_osDirectoryName.c_str(),
572 4 : m_osName.c_str(), nullptr);
573 :
574 4 : GDALGroup::ParentRenamed(osNewParentFullName);
575 4 : }
576 :
577 : /************************************************************************/
578 : /* NotifyChildrenOfRenaming() */
579 : /************************************************************************/
580 :
581 10 : void ZarrGroupBase::NotifyChildrenOfRenaming()
582 : {
583 14 : for (const auto &oIter : m_oMapGroups)
584 4 : oIter.second->ParentRenamed(m_osFullName);
585 :
586 19 : for (const auto &oIter : m_oMapMDArrays)
587 9 : oIter.second->ParentRenamed(m_osFullName);
588 :
589 10 : m_oAttrGroup.ParentRenamed(m_osFullName);
590 :
591 16 : for (const auto &oIter : m_oMapDimensions)
592 6 : oIter.second->ParentRenamed(m_osFullName);
593 10 : }
594 :
595 : /************************************************************************/
596 : /* ZarrGroupBase::GetParentGroup() */
597 : /************************************************************************/
598 :
599 43 : std::shared_ptr<ZarrGroupBase> ZarrGroupBase::GetParentGroup() const
600 : {
601 43 : std::shared_ptr<ZarrGroupBase> poGroup = m_poParent.lock();
602 43 : if (!poGroup)
603 : {
604 2 : if (auto poRootGroup = m_poSharedResource->GetRootGroup())
605 : {
606 1 : const auto nPos = m_osFullName.rfind('/');
607 1 : if (nPos != std::string::npos)
608 : {
609 2 : poGroup = std::dynamic_pointer_cast<ZarrGroupBase>(
610 3 : poRootGroup->OpenGroupFromFullname(m_osFullName.substr(
611 3 : 0, std::max(static_cast<size_t>(1), nPos))));
612 : }
613 : }
614 : }
615 43 : return poGroup;
616 : }
|