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

Generated by: LCOV version 1.14