Manual pages: mc • mcdiff • mcedit • mcview

root/tests/src/vfs/cpio/cpio_open_archive.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. message
  2. setup
  3. teardown
  4. pad
  5. append_member
  6. list_archive
  7. START_PARAMETRIZED_TEST
  8. START_PARAMETRIZED_TEST
  9. main

   1 /*
   2    src/vfs/cpio - tests for opening of cpio archives
   3 
   4    Copyright (C) 2026
   5    Free Software Foundation, Inc.
   6 
   7    Written by:
   8    Yury V. Zaytsev <yury@shurup.com>, 2026
   9 
  10    This file is part of the Midnight Commander.
  11 
  12    The Midnight Commander is free software: you can redistribute it
  13    and/or modify it under the terms of the GNU General Public License as
  14    published by the Free Software Foundation, either version 3 of the License,
  15    or (at your option) any later version.
  16 
  17    The Midnight Commander is distributed in the hope that it will be useful,
  18    but WITHOUT ANY WARRANTY; without even the implied warranty of
  19    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  20    GNU General Public License for more details.
  21 
  22    You should have received a copy of the GNU General Public License
  23    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  24  */
  25 
  26 #define TEST_SUITE_NAME "/src/vfs/cpio"
  27 
  28 #include "tests/mctest.h"
  29 
  30 #include <stdio.h>   // snprintf()
  31 #include <unistd.h>  // unlink(), rmdir()
  32 
  33 #include "lib/strutil.h"
  34 #include "src/vfs/local/local.h"
  35 
  36 #include "src/vfs/cpio/cpio.c"
  37 
  38 /* --------------------------------------------------------------------------------------------- */
  39 
  40 typedef enum
  41 {
  42     FORMAT_BIN,
  43     FORMAT_OLDC,
  44     FORMAT_NEWC
  45 } archive_format_t;
  46 
  47 /* --------------------------------------------------------------------------------------------- */
  48 /* mocked functions */
  49 /* --------------------------------------------------------------------------------------------- */
  50 
  51 // text of the last error message, if any
  52 static char *message__text = NULL;
  53 
  54 /* @Mock */
  55 void
  56 message (int flags, const char *title, const char *text, ...)
     /* [previous][next][first][last][top][bottom][index][help]  */
  57 {
  58     va_list ap;
  59 
  60     (void) flags;
  61     (void) title;
  62 
  63     g_free (message__text);
  64     va_start (ap, text);
  65     message__text = g_strdup_vprintf (text, ap);
  66     va_end (ap);
  67 }
  68 
  69 /* --------------------------------------------------------------------------------------------- */
  70 
  71 /* @Before */
  72 static void
  73 setup (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
  74 {
  75     str_init_strings (NULL);
  76 
  77     vfs_init ();
  78     vfs_init_localfs ();
  79     vfs_init_cpiofs ();
  80 }
  81 
  82 /* --------------------------------------------------------------------------------------------- */
  83 
  84 /* @After */
  85 static void
  86 teardown (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
  87 {
  88     vfs_shut ();
  89     str_uninit_strings ();
  90 
  91     MC_PTR_FREE (message__text);
  92 }
  93 
  94 /* --------------------------------------------------------------------------------------------- */
  95 
  96 static void
  97 pad (GByteArray *archive, guint alignment)
     /* [previous][next][first][last][top][bottom][index][help]  */
  98 {
  99     while (archive->len % alignment != 0)
 100         g_byte_array_append (archive, (const guint8 *) "", 1);
 101 }
 102 
 103 /* --------------------------------------------------------------------------------------------- */
 104 
 105 /* Append an archive member that declares data_size bytes of data, but carries only data_len. */
 106 static void
 107 append_member (GByteArray *archive, archive_format_t format, const char *name, mode_t mode,
     /* [previous][next][first][last][top][bottom][index][help]  */
 108                guint64 data_size, const char *data, size_t data_len)
 109 {
 110     const size_t name_size = strlen (name) + 1;
 111     char header[128];
 112 
 113     switch (format)
 114     {
 115     case FORMAT_BIN:
 116     {
 117         // host byte order, just as cpio_find_head() expects for CPIO_BIN
 118         const unsigned short h[13] = {
 119             070707,                                 // magic
 120             0,                                      // dev
 121             1,                                      // ino
 122             (unsigned short) mode,                  // mode
 123             0,                                      // uid
 124             0,                                      // gid
 125             1,                                      // nlink
 126             0,                                      // rdev
 127             0,                                      // mtime, high
 128             0,                                      // mtime, low
 129             (unsigned short) name_size,             // namesize
 130             (unsigned short) (data_size >> 16),     // filesize, high
 131             (unsigned short) (data_size & 0xFFFF),  // filesize, low
 132         };
 133 
 134         g_byte_array_append (archive, (const guint8 *) h, sizeof (h));
 135         g_byte_array_append (archive, (const guint8 *) name, name_size);
 136         pad (archive, 2);
 137         g_byte_array_append (archive, (const guint8 *) data, data_len);
 138         pad (archive, 2);
 139         break;
 140     }
 141     case FORMAT_OLDC:
 142         snprintf (header, sizeof (header),
 143                   "070707"       // magic
 144                   "000000"       // dev
 145                   "000001"       // ino
 146                   "%06o"         // mode
 147                   "000000"       // uid
 148                   "000000"       // gid
 149                   "000001"       // nlink
 150                   "000000"       // rdev
 151                   "00000000000"  // mtime
 152                   "%06o"         // namesize
 153                   "%011llo",     // filesize
 154                   (unsigned int) mode, (unsigned int) name_size, (unsigned long long) data_size);
 155         g_byte_array_append (archive, (const guint8 *) header, strlen (header));
 156         g_byte_array_append (archive, (const guint8 *) name, name_size);
 157         g_byte_array_append (archive, (const guint8 *) data, data_len);
 158         break;
 159     case FORMAT_NEWC:
 160         snprintf (header, sizeof (header),
 161                   "070701"     // magic
 162                   "00000001"   // ino
 163                   "%08X"       // mode
 164                   "00000000"   // uid
 165                   "00000000"   // gid
 166                   "00000001"   // nlink
 167                   "00000000"   // mtime
 168                   "%08llX"     // filesize
 169                   "00000000"   // devmajor
 170                   "00000000"   // devminor
 171                   "00000000"   // rdevmajor
 172                   "00000000"   // rdevminor
 173                   "%08X"       // namesize
 174                   "00000000",  // check
 175                   (unsigned int) mode, (unsigned long long) data_size, (unsigned int) name_size);
 176         g_byte_array_append (archive, (const guint8 *) header, strlen (header));
 177         g_byte_array_append (archive, (const guint8 *) name, name_size);
 178         pad (archive, 4);
 179         g_byte_array_append (archive, (const guint8 *) data, data_len);
 180         pad (archive, 4);
 181         break;
 182     default:
 183         g_assert_not_reached ();
 184     }
 185 }
 186 
 187 /* --------------------------------------------------------------------------------------------- */
 188 
 189 /* Write the archive into a temporary directory and list it through the cpio VFS. */
 190 static char **
 191 list_archive (GByteArray *archive, const char *link_name, char *link_target, size_t target_size)
     /* [previous][next][first][last][top][bottom][index][help]  */
 192 {
 193     static const guint8 zero_block[512] = { 0 };
 194     char *dir;
 195     char *path;
 196     char *archive_path;
 197     vfs_path_t *vpath;
 198     DIR *d;
 199     struct vfs_dirent *de;
 200     GPtrArray *listing;
 201 
 202     // a zero block after the members, as written by cpio(1)
 203     g_byte_array_append (archive, zero_block, sizeof (zero_block));
 204 
 205     dir = g_dir_make_tmp ("mctest-XXXXXX", NULL);
 206     mctest_assert_not_null (dir);
 207     path = g_build_filename (dir, "test.cpio", (char *) NULL);
 208     mctest_assert_true (
 209         g_file_set_contents (path, (const char *) archive->data, (gssize) archive->len, NULL));
 210 
 211     archive_path = g_strconcat (path, PATH_SEP_STR "ucpio" VFS_PATH_URL_DELIMITER, (char *) NULL);
 212     vpath = vfs_path_from_str (archive_path);
 213     d = mc_opendir (vpath);
 214     mctest_assert_not_null (d);
 215 
 216     listing = g_ptr_array_new ();
 217     while ((de = mc_readdir (d)) != NULL)
 218         g_ptr_array_add (listing, g_strdup (de->d_name));
 219     g_ptr_array_add (listing, NULL);
 220     mc_closedir (d);
 221 
 222     if (link_target != NULL)
 223     {
 224         vfs_path_t *link_vpath;
 225         ssize_t len;
 226 
 227         link_vpath = vfs_path_append_new (vpath, link_name, (char *) NULL);
 228         len = mc_readlink (link_vpath, link_target, target_size - 1);
 229         link_target[MAX (len, 0)] = '\0';
 230         vfs_path_free (link_vpath, TRUE);
 231     }
 232 
 233     vfs_path_free (vpath, TRUE);
 234     g_free (archive_path);
 235     unlink (path);
 236     g_free (path);
 237     rmdir (dir);
 238     g_free (dir);
 239 
 240     return (char **) g_ptr_array_free (listing, FALSE);
 241 }
 242 
 243 /* --------------------------------------------------------------------------------------------- */
 244 
 245 /* @DataSource("test_cpio_symlink_size_ds") */
 246 static const struct test_cpio_symlink_size_ds
 247 {
 248     archive_format_t format;
 249     guint64 link_size;
 250 } test_cpio_symlink_size_ds[] = {
 251     // the sizes that made g_malloc() abort or, with 32-bit size_t, wrap to g_malloc (0) (#5084)
 252     { FORMAT_BIN, 0xFFFFFFFF },
 253     { FORMAT_NEWC, 0xFFFFFFFF },
 254     { FORMAT_OLDC, 077777777777 },
 255     // longer than any path, but with the data actually present
 256     { FORMAT_BIN, MC_MAXPATHLEN + 1 },
 257     { FORMAT_NEWC, MC_MAXPATHLEN + 1 },
 258     { FORMAT_OLDC, MC_MAXPATHLEN + 1 },
 259 };
 260 
 261 /* @Test(dataSource = "test_cpio_symlink_size_ds") */
 262 START_PARAMETRIZED_TEST (test_cpio_symlink_size, test_cpio_symlink_size_ds)
     /* [previous][next][first][last][top][bottom][index][help]  */
 263 {
 264     GByteArray *archive;
 265     char *filler;
 266     size_t filler_len;
 267     char **listing;
 268 
 269     // given: a symlink, the target of which is too long, between two files
 270     archive = g_byte_array_new ();
 271     filler_len = MC_MAXPATHLEN + 1;
 272     filler = g_strnfill (filler_len, 'x');
 273     append_member (archive, data->format, "a", S_IFREG | 0644, 6, "hello\n", 6);
 274     append_member (archive, data->format, "link", S_IFLNK | 0777, data->link_size, filler,
 275                    MIN (filler_len, data->link_size));
 276     append_member (archive, data->format, "z", S_IFREG | 0644, 6, "hello\n", 6);
 277     append_member (archive, data->format, "TRAILER!!!", 0, 0, "", 0);
 278 
 279     // when
 280     listing = list_archive (archive, NULL, NULL, 0);
 281 
 282     // then: the archive is reported as corrupted, and the symlink is not added to the tree
 283     mctest_assert_not_null (message__text);
 284     mctest_assert_true (g_str_has_prefix (message__text, "Corrupted cpio header"));
 285     mctest_assert_true (g_strv_contains ((const char *const *) listing, "a"));
 286     mctest_assert_false (g_strv_contains ((const char *const *) listing, "link"));
 287 
 288     // cleanup
 289     g_strfreev (listing);
 290     g_free (filler);
 291     g_byte_array_free (archive, TRUE);
 292 }
 293 END_PARAMETRIZED_TEST
 294 
 295 /* --------------------------------------------------------------------------------------------- */
 296 
 297 /* @DataSource("test_cpio_symlink_long_target_ds") */
 298 static const struct test_cpio_symlink_long_target_ds
 299 {
 300     archive_format_t format;
 301 } test_cpio_symlink_long_target_ds[] = {
 302     { FORMAT_BIN },
 303     { FORMAT_NEWC },
 304     { FORMAT_OLDC },
 305 };
 306 
 307 /* @Test(dataSource = "test_cpio_symlink_long_target_ds") */
 308 START_PARAMETRIZED_TEST (test_cpio_symlink_long_target, test_cpio_symlink_long_target_ds)
     /* [previous][next][first][last][top][bottom][index][help]  */
 309 {
 310     GByteArray *archive;
 311     char *target;
 312     char link_target[MC_MAXPATHLEN + 1];
 313     char **listing;
 314     char *joined;
 315 
 316     // given: a symlink with a long target that a Linux file system can hold, between two files
 317     archive = g_byte_array_new ();
 318     target = g_strnfill (2000, 'd');
 319     append_member (archive, data->format, "a", S_IFREG | 0644, 6, "hello\n", 6);
 320     append_member (archive, data->format, "link", S_IFLNK | 0777, strlen (target), target,
 321                    strlen (target));
 322     append_member (archive, data->format, "z", S_IFREG | 0644, 6, "hello\n", 6);
 323     append_member (archive, data->format, "TRAILER!!!", 0, 0, "", 0);
 324 
 325     // when
 326     listing = list_archive (archive, "link", link_target, sizeof (link_target));
 327 
 328     // then: all members are listed and the symlink target is read completely
 329     joined = g_strjoinv (" ", listing);
 330     mctest_assert_null (message__text);
 331     mctest_assert_str_eq (joined, "a link z");
 332     mctest_assert_str_eq (link_target, target);
 333 
 334     // cleanup
 335     g_free (joined);
 336     g_strfreev (listing);
 337     g_free (target);
 338     g_byte_array_free (archive, TRUE);
 339 }
 340 END_PARAMETRIZED_TEST
 341 
 342 /* --------------------------------------------------------------------------------------------- */
 343 
 344 int
 345 main (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 346 {
 347     TCase *tc_core;
 348 
 349     tc_core = tcase_create ("Core");
 350 
 351     tcase_add_checked_fixture (tc_core, setup, teardown);
 352 
 353     // Add new tests here: ***************
 354     mctest_add_parameterized_test (tc_core, test_cpio_symlink_size, test_cpio_symlink_size_ds);
 355     mctest_add_parameterized_test (tc_core, test_cpio_symlink_long_target,
 356                                    test_cpio_symlink_long_target_ds);
 357     // ***********************************
 358 
 359     return mctest_run_all (tc_core);
 360 }
 361 
 362 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */