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

Generated by: LCOV version 1.14