LCOV - code coverage report
Current view: top level - tests - catch_rm_r.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 90.4 % 136 123
Test Date: 2025-08-31 07:54:46 Functions: 100.0 % 2 2
Legend: Lines: hit not hit

            Line data    Source code
       1              : // Copyright (c) 2018-2025  Made to Order Software Corp.  All Rights Reserved
       2              : //
       3              : // https://snapwebsites.org/project/snapdev
       4              : // contact@m2osw.com
       5              : //
       6              : // This program is free software: you can redistribute it and/or modify
       7              : // it under the terms of the GNU General Public License as published by
       8              : // the Free Software Foundation, either version 3 of the License, or
       9              : // (at your option) any later version.
      10              : //
      11              : // This program is distributed in the hope that it will be useful,
      12              : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      13              : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14              : // GNU General Public License for more details.
      15              : //
      16              : // You should have received a copy of the GNU General Public License
      17              : // along with this program.  If not, see <https://www.gnu.org/licenses/>.
      18              : 
      19              : /** \file
      20              :  * \brief Verify that the rm_r() function works.
      21              :  *
      22              :  * This file implements tests for the rm_r() function.
      23              :  */
      24              : 
      25              : // self
      26              : //
      27              : #include    <snapdev/rm_r.h>
      28              : 
      29              : #include    <snapdev/as_root.h>
      30              : #include    <snapdev/raii_generic_deleter.h>
      31              : #include    <snapdev/mkdir_p.h>
      32              : #include    <snapdev/pathinfo.h>
      33              : #include    <snapdev/version.h>
      34              : 
      35              : #include    "catch_main.h"
      36              : 
      37              : 
      38              : // C
      39              : //
      40              : #include    <sys/socket.h>
      41              : #include    <sys/sysmacros.h>
      42              : #include    <sys/un.h>
      43              : #include    <unistd.h>
      44              : 
      45              : 
      46              : // last include
      47              : //
      48              : #include    <snapdev/poison.h>
      49              : 
      50              : 
      51              : namespace
      52              : {
      53              : 
      54              : 
      55              : // gcc on 22.04 generates this warning thinking that an strncpy() will fail
      56              : // but according to the bug report it is not an issue
      57              : // https://github.com/eworm-de/mkinitcpio-ykfde/issues/25
      58              : // the warning was removed in future versions of gcc, unfortunately, we'll
      59              : // be on 22.04 for a while...
      60              : #if SNAPDEV_CHECK_GCC_VERSION(11, 3, 0)
      61              : #pragma GCC diagnostic push
      62              : #pragma GCC diagnostic ignored "-Wstringop-truncation"
      63              : #endif
      64            4 : void create_files(std::string const & folder, int depth, int max_depth, bool special_files, std::vector<int> & sockets)
      65              : {
      66            4 :     int r(0);
      67              : 
      68              :     // first create this directory (unless it exists, then leave it alone)
      69              :     //
      70            4 :     struct stat st;
      71            4 :     if(stat(folder.c_str(), &st) == 0)
      72              :     {
      73              :         // if it already exists and it is a directory, go on
      74              :         //
      75            0 :         CATCH_REQUIRE(S_ISDIR(st.st_mode));
      76              :     }
      77              :     else
      78              :     {
      79            4 :         r = mkdir(folder.c_str(), 0700);
      80            4 :         CATCH_REQUIRE(r == 0);
      81              :     }
      82              : 
      83              :     // second create a few text files in this folder
      84              :     //
      85            4 :     int const max(rand() % 5 + 1);
      86           16 :     for(int i(1); i <= max; ++i)
      87              :     {
      88           12 :         std::string filename(folder);
      89           12 :         filename += '/';
      90           12 :         filename += "file";
      91           12 :         filename += std::to_string(i);
      92           12 :         filename += ".txt";
      93              : 
      94           12 :         std::ofstream out(filename);
      95           12 :         out << SNAP_CATCH2_NAMESPACE::random_string(10, 100);
      96           12 :         CATCH_REQUIRE(!!out);
      97           12 :     }
      98              : 
      99            4 :     if(special_files)
     100              :     {
     101              :         // also create special files
     102              :         //
     103            3 :         std::string fifo(folder);
     104            3 :         fifo += "/file.fifo";
     105            3 :         r = mkfifo(fifo.c_str(), 0700);
     106            3 :         CATCH_REQUIRE(r == 0);
     107              : 
     108            3 :         std::string socket_name(folder);
     109            3 :         socket_name += "/file.socket";
     110            3 :         struct sockaddr_un addr = {};
     111            3 :         addr.sun_family = AF_UNIX;
     112            3 :         CATCH_REQUIRE(socket_name.length() < sizeof(addr.sun_path));
     113            3 :         strncpy(addr.sun_path, socket_name.c_str(), sizeof(addr.sun_path));
     114            3 :         int s = socket(AF_UNIX, SOCK_STREAM, 0);
     115            3 :         CATCH_REQUIRE(s != -1);
     116            3 :         sockets.push_back(s);
     117            3 :         r = bind(s, reinterpret_cast<sockaddr const *>(&addr), sizeof(addr));
     118            3 :         CATCH_REQUIRE(r == 0);
     119              : 
     120              :         // creating device files is a root matter
     121              :         //
     122            3 :         snapdev::as_root root;
     123            3 :         if(root.is_switched())
     124              :         {
     125            0 :             std::string char_dev(folder);
     126            0 :             char_dev += "/char.dev";
     127            0 :             r = mknod(char_dev.c_str(), 0700 | S_IFCHR, makedev(1, 5));    // like /dev/zero
     128            0 :             CATCH_REQUIRE(r == 0);
     129              : 
     130            0 :             std::string block_dev(folder);
     131            0 :             block_dev += "/block.dev";
     132            0 :             r = mknod(block_dev.c_str(), 0700 | S_IFBLK, makedev(7, 1));   // like /dev/loop1
     133            0 :             CATCH_REQUIRE(r == 0);
     134            0 :         }
     135              :         else
     136              :         {
     137              :             static bool message_shown(false);
     138            3 :             if(!message_shown)
     139              :             {
     140            1 :                 message_shown = true;
     141              : 
     142              :                 std::cout << "--- note that the special file test cannot create block and character devices.\n"
     143              :                     << "--- if you want to test those, make sure to run as root:\n"
     144            1 :                     << "---    sudo unittest ... rm_r\n";
     145              :             }
     146              :         }
     147            3 :     }
     148              : 
     149              :     // third create sub-directories and recursively create files/dirs
     150              :     // unless we already reached the maximum depth
     151              :     //
     152            4 :     if(depth >= max_depth)
     153              :     {
     154            3 :         return;
     155              :     }
     156            1 :     int const max_foo(rand() % 3 + 2);
     157            3 :     for(int foo(1); foo <= max_foo; ++foo)
     158              :     {
     159            2 :         std::string sub_folder(folder);
     160            2 :         sub_folder += "/foo";
     161            2 :         sub_folder += std::to_string(foo);
     162              : 
     163            2 :         create_files(sub_folder, depth + 1, max_depth, special_files, sockets);
     164            2 :     }
     165              : }
     166              : #if SNAPDEV_CHECK_GCC_VERSION(11, 3, 0)
     167              : #pragma GCC diagnostic pop
     168              : #endif
     169              : 
     170              : 
     171              : 
     172              : }
     173              : 
     174              : 
     175            5 : CATCH_TEST_CASE("rm_r", "[os]")
     176              : {
     177            5 :     int r(0);
     178              : 
     179            5 :     CATCH_START_SECTION("rm_r: missing")
     180              :     {
     181            1 :         std::string path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     182            1 :         path += "/missing";
     183              : 
     184            1 :         r = snapdev::rm_r(path);
     185            1 :         CATCH_REQUIRE(r == 0);
     186            1 :     }
     187            5 :     CATCH_END_SECTION()
     188              : 
     189            5 :     CATCH_START_SECTION("rm_r: empty directory")
     190              :     {
     191            1 :         std::string path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     192            1 :         path += "/empty";
     193              : 
     194            1 :         struct stat st;
     195            1 :         if(stat(path.c_str(), &st) == 0)
     196              :         {
     197              :             // if it already exists and it is a directory, go on
     198              :             //
     199            0 :             CATCH_REQUIRE(S_ISDIR(st.st_mode));
     200              :         }
     201              :         else
     202              :         {
     203            1 :             r = mkdir(path.c_str(), 0700);
     204            1 :             CATCH_REQUIRE(r == 0);
     205              :         }
     206              : 
     207            1 :         r = snapdev::rm_r(path);
     208            1 :         CATCH_REQUIRE(r == 0);
     209            1 :     }
     210            5 :     CATCH_END_SECTION()
     211              : 
     212            5 :     CATCH_START_SECTION("rm_r: directory with a few files")
     213              :     {
     214            1 :         std::string path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     215            1 :         path += "/directory";
     216              : 
     217            1 :         struct stat st;
     218            1 :         if(stat(path.c_str(), &st) == 0)
     219              :         {
     220              :             // if it already exists and it is a directory, go on
     221              :             //
     222            0 :             CATCH_REQUIRE(S_ISDIR(st.st_mode));
     223              :         }
     224              :         else
     225              :         {
     226            1 :             r = mkdir(path.c_str(), 0700);
     227            1 :             CATCH_REQUIRE(r == 0);
     228              :         }
     229              : 
     230            1 :         int const max(rand() % 5 + 1);
     231            2 :         for(int i(1); i <= max; ++i)
     232              :         {
     233            1 :             std::string filename(path);
     234            1 :             filename += '/';
     235            1 :             filename += "file";
     236            1 :             filename += std::to_string(i);
     237            1 :             filename += ".txt";
     238              : 
     239            1 :             std::ofstream out(filename);
     240            1 :             out << SNAP_CATCH2_NAMESPACE::random_string(10, 100);
     241            1 :         }
     242              : 
     243            1 :         r = snapdev::rm_r(path);
     244            1 :         CATCH_REQUIRE(r == 0);
     245            1 :     }
     246            5 :     CATCH_END_SECTION()
     247              : 
     248            5 :     CATCH_START_SECTION("rm_r: directory tree")
     249              :     {
     250            1 :         std::string path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     251            1 :         path += "/tree";
     252              : 
     253            1 :         std::vector<int> sockets;
     254            1 :         int const max_depth(rand() % 3 + 1);
     255            1 :         create_files(path, 1, max_depth, false, sockets);
     256              :         // ignore the sockets since we do not create special files
     257              : 
     258            1 :         r = snapdev::rm_r(path);
     259            1 :         CATCH_REQUIRE(r == 0);
     260            1 :     }
     261            5 :     CATCH_END_SECTION()
     262              : 
     263            5 :     CATCH_START_SECTION("rm_r: directory tree with special files")
     264              :     {
     265            1 :         std::string path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     266            1 :         path += "/special";
     267              : 
     268            1 :         std::vector<int> sockets;
     269            1 :         int const max_depth(rand() % 3 + 1);
     270            1 :         create_files(path, 1, max_depth, true, sockets);
     271              : 
     272              :         // deleting as is must fail
     273              :         // (i.e. we detect special files early and don't delete anything)
     274              :         //
     275            1 :         r = snapdev::rm_r(path);
     276            1 :         if(r == 0)
     277              :         {
     278            0 :             CATCH_REQUIRE(r != 0);
     279              :         }
     280            1 :         CATCH_REQUIRE(errno == EPERM);
     281              : 
     282              :         // try with SPECIAL_FILE_IGNORE
     283              :         // (i.e. delete everything exception special files)
     284              :         //
     285            1 :         r = snapdev::rm_r(path, snapdev::special_file_t::SPECIAL_FILE_IGNORE);
     286            1 :         CATCH_REQUIRE(r == 0);
     287              : 
     288              :         // try with SPECIAL_FILE_KEEP_DEVICES
     289              :         // (i.e. delete everything exception devices)
     290              :         //
     291            1 :         r = snapdev::rm_r(path, snapdev::special_file_t::SPECIAL_FILE_KEEP_DEVICES);
     292            1 :         CATCH_REQUIRE(r == 0);
     293              : 
     294              :         // try with SPECIAL_FILE_REMOVE
     295              :         // (i.e. delete absolutely everything)
     296              :         //
     297              :         // note: this requires root permissions (we do not create if we
     298              :         //       can't become root)
     299              :         {
     300            1 :             snapdev::as_root root;
     301            1 :             r = snapdev::rm_r(path, snapdev::special_file_t::SPECIAL_FILE_REMOVE);
     302            1 :             CATCH_REQUIRE(r == 0);
     303            1 :         }
     304              : 
     305              :         // now close all the sockets (ignore errors)
     306              :         //
     307            4 :         for(auto const & s : sockets)
     308              :         {
     309            3 :             close(s);
     310              :         }
     311              : 
     312              :         // finally make sure the directory is gone
     313              :         //
     314            1 :         struct stat st;
     315            1 :         CATCH_REQUIRE(stat(path.c_str(), &st) != 0);
     316            1 :     }
     317            5 :     CATCH_END_SECTION()
     318            5 : }
     319              : 
     320              : 
     321              : 
     322              : // vim: ts=4 sw=4 et
        

Generated by: LCOV version 2.0-1

Snap C++ | List of projects | List of versions