LCOV - code coverage report
Current view: top level - apps - gdalalg_vsi_copy.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 131 151 86.8 %
Date: 2026-09-11 05:09:32 Functions: 6 6 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  gdal "vsi copy" subcommand
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "gdalalg_vsi_copy.h"
      14             : 
      15             : #include "cpl_conv.h"
      16             : #include "cpl_string.h"
      17             : #include "cpl_vsi.h"
      18             : #include "cpl_vsi_error.h"
      19             : 
      20             : #include <algorithm>
      21             : 
      22             : //! @cond Doxygen_Suppress
      23             : 
      24             : #ifndef _
      25             : #define _(x) (x)
      26             : #endif
      27             : 
      28             : /************************************************************************/
      29             : /*             GDALVSICopyAlgorithm::GDALVSICopyAlgorithm()             */
      30             : /************************************************************************/
      31             : 
      32          81 : GDALVSICopyAlgorithm::GDALVSICopyAlgorithm()
      33          81 :     : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
      34             : {
      35             :     {
      36             :         auto &arg =
      37         162 :             AddArg("source", 0, _("Source file or directory name"), &m_source)
      38          81 :                 .SetPositional()
      39          81 :                 .SetMinCharCount(1)
      40          81 :                 .SetRequired();
      41          81 :         SetAutoCompleteFunctionForFilename(arg, 0);
      42             :     }
      43             :     {
      44             :         auto &arg =
      45             :             AddArg("destination", 0, _("Destination file or directory name"),
      46         162 :                    &m_destination)
      47          81 :                 .SetPositional()
      48          81 :                 .SetMinCharCount(1)
      49          81 :                 .SetRequired()
      50             :                 .AddAction(
      51          38 :                     [this]()
      52             :                     {
      53             :                         // If outputting to stdout, automatically turn off progress bar
      54          38 :                         if (m_destination == "/vsistdout/")
      55             :                         {
      56           0 :                             auto quietArg = GetArg(GDAL_ARG_NAME_QUIET);
      57           0 :                             if (quietArg && quietArg->GetType() == GAAT_BOOLEAN)
      58           0 :                                 quietArg->Set(true);
      59             :                         }
      60         119 :                     });
      61          81 :         SetAutoCompleteFunctionForFilename(arg, 0);
      62             :     }
      63             : 
      64             :     AddArg("recursive", 'r', _("Copy subdirectories recursively"),
      65          81 :            &m_recursive);
      66             : 
      67          81 :     AddArg("skip-errors", 0, _("Skip errors"), &m_skip);
      68          81 :     AddProgressArg();
      69          81 : }
      70             : 
      71             : /************************************************************************/
      72             : /*                   GDALVSICopyAlgorithm::RunImpl()                    */
      73             : /************************************************************************/
      74             : 
      75          38 : bool GDALVSICopyAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
      76             :                                    void *pProgressData)
      77             : {
      78             :     const auto ReportSourceNotAccessible =
      79           8 :         [this](int nOldErrorNum, int nNewErrorNum, const std::string &filename)
      80             :     {
      81           4 :         if (nOldErrorNum != nNewErrorNum)
      82             :         {
      83           1 :             ReportError(CE_Failure, CPLE_FileIO,
      84             :                         "'%s' cannot be accessed. %s: %s", filename.c_str(),
      85             :                         VSIErrorNumToString(nNewErrorNum),
      86             :                         VSIGetLastErrorMsg());
      87             :         }
      88             :         else
      89             :         {
      90           3 :             ReportError(CE_Failure, CPLE_FileIO, "'%s' cannot be accessed.",
      91             :                         filename.c_str());
      92             :         }
      93          42 :     };
      94             : 
      95          51 :     if (m_recursive || cpl::ends_with(m_source, "/*") ||
      96          51 :         cpl::ends_with(m_source, "\\*"))
      97             :     {
      98             :         // Make sure that copy -r [srcdir/]lastsubdir targetdir' creates
      99             :         // targetdir/lastsubdir if targetdir already exists (like cp -r does).
     100          25 :         if (m_source.back() == '/')
     101           1 :             m_source.pop_back();
     102             : 
     103          25 :         if (!cpl::ends_with(m_source, "/*") && !cpl::ends_with(m_source, "\\*"))
     104             :         {
     105          22 :             const auto nOldErrorNum = VSIGetLastErrorNo();
     106          22 :             VSIErrorReset();
     107             : 
     108             :             VSIStatBufL statBufSrc;
     109          22 :             bool srcExists = VSIStatL(m_source.c_str(), &statBufSrc) == 0;
     110          22 :             if (!srcExists)
     111             :             {
     112           1 :                 srcExists = VSIStatL(std::string(m_source).append("/").c_str(),
     113             :                                      &statBufSrc) == 0;
     114             :             }
     115          22 :             const auto nNewErrorNum = VSIGetLastErrorNo();
     116             :             VSIStatBufL statBufDst;
     117             :             const bool dstExists =
     118          22 :                 VSIStatExL(m_destination.c_str(), &statBufDst,
     119          22 :                            VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0;
     120          22 :             if (srcExists && VSI_ISDIR(statBufSrc.st_mode) && dstExists &&
     121           7 :                 VSI_ISDIR(statBufDst.st_mode))
     122             :             {
     123           7 :                 if (m_destination.back() == '/')
     124           0 :                     m_destination.pop_back();
     125           7 :                 const auto srcLastSlashPos = m_source.rfind('/');
     126           7 :                 if (srcLastSlashPos != std::string::npos)
     127           7 :                     m_destination += m_source.substr(srcLastSlashPos);
     128             :                 else
     129           0 :                     m_destination = CPLFormFilenameSafe(
     130           7 :                         m_destination.c_str(), m_source.c_str(), nullptr);
     131             :             }
     132          15 :             else if (!srcExists)
     133             :             {
     134           1 :                 ReportSourceNotAccessible(nOldErrorNum, nNewErrorNum, m_source);
     135           1 :                 return false;
     136             :             }
     137             :         }
     138             :         else
     139             :         {
     140           3 :             const auto nOldErrorNum = VSIGetLastErrorNo();
     141           3 :             VSIErrorReset();
     142             : 
     143           3 :             m_source.resize(m_source.size() - 2);
     144             :             VSIStatBufL statBufSrc;
     145           3 :             bool srcExists = VSIStatL(m_source.c_str(), &statBufSrc) == 0;
     146           3 :             if (!srcExists)
     147             :             {
     148           1 :                 const auto nNewErrorNum = VSIGetLastErrorNo();
     149           1 :                 ReportSourceNotAccessible(nOldErrorNum, nNewErrorNum, m_source);
     150           1 :                 return false;
     151             :             }
     152             :         }
     153             : 
     154          23 :         uint64_t curAmount = 0;
     155          23 :         return CopyRecursive(m_source, m_destination, 0, m_recursive ? -1 : 0,
     156          23 :                              curAmount, 0, pfnProgress, pProgressData);
     157             :     }
     158             :     else
     159             :     {
     160          13 :         const auto nOldErrorNum = VSIGetLastErrorNo();
     161          13 :         VSIErrorReset();
     162             : 
     163             :         VSIStatBufL statBufSrc;
     164          13 :         bool srcExists = VSIStatL(m_source.c_str(), &statBufSrc) == 0;
     165          13 :         if (!srcExists)
     166             :         {
     167           2 :             const auto nNewErrorNum = VSIGetLastErrorNo();
     168           2 :             ReportSourceNotAccessible(nOldErrorNum, nNewErrorNum, m_source);
     169           2 :             return false;
     170             :         }
     171          11 :         if (VSI_ISDIR(statBufSrc.st_mode))
     172             :         {
     173           1 :             ReportError(CE_Failure, CPLE_FileIO,
     174             :                         "%s is a directory. Use -r/--recursive option",
     175             :                         m_source.c_str());
     176           1 :             return false;
     177             :         }
     178             : 
     179          10 :         return CopySingle(m_source, m_destination, ~(static_cast<uint64_t>(0)),
     180          10 :                           pfnProgress, pProgressData);
     181             :     }
     182             : }
     183             : 
     184             : /************************************************************************/
     185             : /*                  GDALVSICopyAlgorithm::CopySingle()                  */
     186             : /************************************************************************/
     187             : 
     188          92 : bool GDALVSICopyAlgorithm::CopySingle(const std::string &src,
     189             :                                       const std::string &dstIn, uint64_t size,
     190             :                                       GDALProgressFunc pfnProgress,
     191             :                                       void *pProgressData) const
     192             : {
     193          92 :     CPLDebug("gdal_vsi_copy", "Copying file %s...", src.c_str());
     194             :     VSIStatBufL sStat;
     195          92 :     std::string dst = dstIn;
     196             :     const bool bExists =
     197         183 :         VSIStatExL(dst.back() == '/' ? dst.c_str()
     198         182 :                                      : std::string(dst).append("/").c_str(),
     199          92 :                    &sStat, VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0;
     200         178 :     if ((!bExists && dst.back() == '/') ||
     201          86 :         (bExists && VSI_ISDIR(sStat.st_mode)))
     202             :     {
     203          86 :         const std::string filename = CPLGetFilename(src.c_str());
     204          86 :         dst = CPLFormFilenameSafe(dst.c_str(), filename.c_str(), nullptr);
     205             :     }
     206          92 :     return VSICopyFile(src.c_str(), dst.c_str(), nullptr, size, nullptr,
     207          92 :                        pfnProgress, pProgressData) == 0 ||
     208         184 :            m_skip;
     209             : }
     210             : 
     211             : /************************************************************************/
     212             : /*                GDALVSICopyAlgorithm::CopyRecursive()                 */
     213             : /************************************************************************/
     214             : 
     215          70 : bool GDALVSICopyAlgorithm::CopyRecursive(const std::string &srcIn,
     216             :                                          const std::string &dst, int depth,
     217             :                                          int maxdepth, uint64_t &curAmount,
     218             :                                          uint64_t totalAmount,
     219             :                                          GDALProgressFunc pfnProgress,
     220             :                                          void *pProgressData) const
     221             : {
     222         140 :     std::string src(srcIn);
     223          70 :     if (src.back() == '/')
     224           0 :         src.pop_back();
     225             : 
     226          70 :     if (pfnProgress && depth == 0)
     227             :     {
     228           1 :         CPLDebug("gdal_vsi_copy", "Listing source files...");
     229             :         std::unique_ptr<VSIDIR, decltype(&VSICloseDir)> dir(
     230           1 :             VSIOpenDir(src.c_str(), maxdepth, nullptr), VSICloseDir);
     231           1 :         if (dir)
     232             :         {
     233           4 :             while (const auto entry = VSIGetNextDirEntry(dir.get()))
     234             :             {
     235           3 :                 if (!(entry->pszName[0] == '.' &&
     236           0 :                       (entry->pszName[1] == '.' || entry->pszName[1] == 0)))
     237             :                 {
     238           3 :                     totalAmount += entry->nSize + 1;
     239           3 :                     if (!pfnProgress(0.0, "", pProgressData))
     240           0 :                         return false;
     241             :                 }
     242           3 :             }
     243             :         }
     244             :     }
     245          70 :     totalAmount = std::max<uint64_t>(1, totalAmount);
     246             : 
     247          70 :     CPLDebug("gdal_vsi_copy", "Copying directory %s...", src.c_str());
     248             :     std::unique_ptr<VSIDIR, decltype(&VSICloseDir)> dir(
     249         140 :         VSIOpenDir(src.c_str(), 0, nullptr), VSICloseDir);
     250          70 :     if (dir)
     251             :     {
     252             :         VSIStatBufL sStat;
     253          70 :         if (VSIStatL(dst.c_str(), &sStat) != 0)
     254             :         {
     255          70 :             if (VSIMkdir(dst.c_str(), 0755) != 0)
     256             :             {
     257           2 :                 ReportError(m_skip ? CE_Warning : CE_Failure, CPLE_FileIO,
     258             :                             "Cannot create directory %s", dst.c_str());
     259           2 :                 return m_skip;
     260             :             }
     261             :         }
     262             : 
     263         198 :         while (const auto entry = VSIGetNextDirEntry(dir.get()))
     264             :         {
     265         130 :             if (!(entry->pszName[0] == '.' &&
     266           7 :                   (entry->pszName[1] == '.' || entry->pszName[1] == 0)))
     267             :             {
     268             :                 const std::string subsrc =
     269         130 :                     CPLFormFilenameSafe(src.c_str(), entry->pszName, nullptr);
     270         130 :                 if (VSI_ISDIR(entry->nMode))
     271             :                 {
     272             :                     const std::string subdest = CPLFormFilenameSafe(
     273          48 :                         dst.c_str(), entry->pszName, nullptr);
     274          48 :                     if (maxdepth < 0 || depth < maxdepth)
     275             :                     {
     276          47 :                         if (!CopyRecursive(subsrc, subdest, depth + 1, maxdepth,
     277             :                                            curAmount, totalAmount, pfnProgress,
     278          47 :                                            pProgressData) &&
     279           0 :                             !m_skip)
     280             :                         {
     281           0 :                             return false;
     282             :                         }
     283             :                     }
     284             :                     else
     285             :                     {
     286           1 :                         if (VSIStatL(subdest.c_str(), &sStat) != 0)
     287             :                         {
     288           1 :                             if (VSIMkdir(subdest.c_str(), 0755) != 0)
     289             :                             {
     290           0 :                                 ReportError(m_skip ? CE_Warning : CE_Failure,
     291             :                                             CPLE_FileIO,
     292             :                                             "Cannot create directory %s",
     293             :                                             subdest.c_str());
     294           0 :                                 if (!m_skip)
     295           0 :                                     return false;
     296             :                             }
     297             :                         }
     298             :                     }
     299          48 :                     curAmount += 1;
     300             : 
     301          49 :                     if (pfnProgress &&
     302           1 :                         !pfnProgress(
     303          48 :                             std::min(1.0, static_cast<double>(curAmount) /
     304           1 :                                               static_cast<double>(totalAmount)),
     305             :                             "", pProgressData))
     306             :                     {
     307           0 :                         return false;
     308             :                     }
     309             :                 }
     310             :                 else
     311             :                 {
     312         164 :                     void *pScaledProgressData = GDALCreateScaledProgress(
     313          82 :                         static_cast<double>(curAmount) /
     314          82 :                             static_cast<double>(totalAmount),
     315           0 :                         std::min(1.0, static_cast<double>(curAmount +
     316           0 :                                                           entry->nSize + 1) /
     317          82 :                                           static_cast<double>(totalAmount)),
     318             :                         pfnProgress, pProgressData);
     319          82 :                     const bool bRet = CopySingle(
     320          82 :                         subsrc, dst, entry->nSize,
     321             :                         pScaledProgressData ? GDALScaledProgress : nullptr,
     322             :                         pScaledProgressData);
     323          82 :                     GDALDestroyScaledProgress(pScaledProgressData);
     324             : 
     325          82 :                     curAmount += entry->nSize + 1;
     326             : 
     327          82 :                     if (!bRet)
     328           0 :                         return false;
     329             :                 }
     330             :             }
     331         130 :         }
     332             :     }
     333             :     else
     334             :     {
     335           0 :         ReportError(m_skip ? CE_Warning : CE_Failure, CPLE_AppDefined,
     336             :                     "%s is not a directory or cannot be opened", src.c_str());
     337           0 :         if (!m_skip)
     338           0 :             return false;
     339             :     }
     340          68 :     return true;
     341             : }
     342             : 
     343             : //! @endcond

Generated by: LCOV version 1.14