495 lines
16 KiB
C++
495 lines
16 KiB
C++
/* Copyright (C) 2017 Mike Fleetwood
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* Test BlockSpecial
|
|
*
|
|
* In order to make the tests runable by non-root users in a wide
|
|
* variety of environments as possible the tests try to assume as small
|
|
* a set of available files as possible and discovers used block device
|
|
* names and symbolic link name. The following files are explicitly
|
|
* needed for the tests:
|
|
*
|
|
* Name Access Note
|
|
* ----------------------- ------- -----
|
|
* / Stat
|
|
* /proc/partitions Stat
|
|
* Read To find any two block
|
|
* devices
|
|
* /dev/BLOCK0 Stat First entry from
|
|
* /proc/partitions
|
|
* /dev/BLOCK1 stat Second entry from
|
|
* /proc/partitions
|
|
* /dev/disk/by-id/ Readdir To find any symlink to a
|
|
* block device
|
|
* /dev/disk/by-id/SYMLINK Stat First directory entry
|
|
* /dev/RBLOCK Stat Device to which SYMLINK
|
|
* refers
|
|
*
|
|
* Other dummy names are pre-loaded into the internal BlockSpecial cache
|
|
* and never queried from the file system.
|
|
*/
|
|
|
|
#include "BlockSpecial.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
#include <string>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <stdio.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
#include <dirent.h>
|
|
#include <limits.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <glibmm/ustring.h>
|
|
|
|
|
|
namespace GParted
|
|
{
|
|
|
|
// Print method for a BlockSpecial object
|
|
std::ostream& operator<<( std::ostream & out, const BlockSpecial & bs )
|
|
{
|
|
out << "BlockSpecial{\"" << bs.m_name << "\"," << bs.m_major << "," << bs.m_minor << "}";
|
|
return out;
|
|
}
|
|
|
|
// Helper to construct and return message for equality assertion used in:
|
|
// EXPECT_BSEQTUP( bs, name, major, minor )
|
|
// Reference:
|
|
// Google Test, AdvancedGuide, Using a Predicate-Formatter
|
|
::testing::AssertionResult CompareHelperBS2TUP( const char * bs_expr, const char * name_expr,
|
|
const char * major_expr, const char * minor_expr,
|
|
const BlockSpecial & bs, const Glib::ustring & name,
|
|
unsigned long major, unsigned long minor )
|
|
{
|
|
if ( bs.m_name == name && bs.m_major == major && bs.m_minor == minor )
|
|
return ::testing::AssertionSuccess();
|
|
else
|
|
return ::testing::AssertionFailure()
|
|
<< " Expected: " << bs_expr << "\n"
|
|
<< " Which is: " << bs << "\n"
|
|
<< "To be equal to: (" << name_expr << "," << major_expr << "," << major_expr << ")";
|
|
}
|
|
|
|
// Nonfatal assertion that BlockSpecial is equal to tuple (name, major, minor)
|
|
#define EXPECT_BSEQTUP(bs, name, major, minor) \
|
|
EXPECT_PRED_FORMAT4(CompareHelperBS2TUP, bs, name, major, minor)
|
|
|
|
// Helper to print two compared BlockSpecial objects on failure.
|
|
// Usage:
|
|
// EXPECT_TRUE( bs1 == bs2 ) << ON_FAILURE_WHERE( bs1, bs2 );
|
|
#define ON_FAILURE_WHERE(b1, b2) \
|
|
" Where: " << #b1 << " = " << (b1) << "\n" \
|
|
" And: " << #b2 << " = " << (b2);
|
|
|
|
|
|
#define SKIP_IF_BLOCK_DEVICE_DOESNT_EXIST(name) \
|
|
{ \
|
|
struct stat sb; \
|
|
if (stat(name.c_str(), &sb) != 0 && errno == ENOENT) \
|
|
{ \
|
|
std::cout << __FILE__ << ":" << __LINE__ << ": Skip test. Block " \
|
|
<< "device '" << name << "' does not exist" << std::endl; \
|
|
return; \
|
|
} \
|
|
}
|
|
|
|
|
|
// Return block device names numbered 0 upwards like "/dev/sda" by reading entries from
|
|
// /proc/partitions.
|
|
static std::string get_block_name( unsigned want )
|
|
{
|
|
std::ifstream proc_partitions( "/proc/partitions" );
|
|
if ( ! proc_partitions )
|
|
{
|
|
ADD_FAILURE() << __func__ << "(): Failed to open '/proc/partitions'";
|
|
return "";
|
|
}
|
|
|
|
std::string line;
|
|
char name[100] = "";
|
|
unsigned entry = 0;
|
|
while ( getline( proc_partitions, line ) )
|
|
{
|
|
if ( sscanf( line.c_str(), "%*u %*u %*u %99s", name ) == 1 )
|
|
{
|
|
if ( entry == want )
|
|
break;
|
|
entry ++;
|
|
}
|
|
}
|
|
proc_partitions.close();
|
|
|
|
if ( entry == want )
|
|
return std::string( "/dev/" ) + name;
|
|
|
|
ADD_FAILURE() << __func__ << "(): Entry " << want << " not found in '/proc/partitions'";
|
|
return "";
|
|
}
|
|
|
|
// Return symbolic link to a block device by reading the first entry in the directory
|
|
// /dev/disk/by-id/.
|
|
static std::string get_link_name()
|
|
{
|
|
DIR * dir = opendir( "/dev/disk/by-id" );
|
|
if (dir == nullptr)
|
|
{
|
|
ADD_FAILURE() << __func__ << "(): Failed to open directory '/dev/disk/by-id'";
|
|
return "";
|
|
}
|
|
|
|
std::string name;
|
|
bool found = false;
|
|
struct dirent * dentry;
|
|
// Silence GCC [-Wparentheses] warning with double parentheses
|
|
while ( ( dentry = readdir( dir ) ) )
|
|
{
|
|
if ( strcmp( dentry->d_name, "." ) != 0 &&
|
|
strcmp( dentry->d_name, ".." ) != 0 )
|
|
{
|
|
name = dentry->d_name;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
closedir( dir );
|
|
|
|
if ( found )
|
|
return std::string("/dev/disk/by-id/") + name;
|
|
|
|
ADD_FAILURE() << __func__ << "(): No entries found in directory '/dev/disk/by-id'";
|
|
return "";
|
|
}
|
|
|
|
// Follow symbolic link return real path.
|
|
static std::string follow_link_name( std::string link )
|
|
{
|
|
char* rpath = realpath(link.c_str(), nullptr);
|
|
if (rpath == nullptr)
|
|
{
|
|
ADD_FAILURE() << __func__ << "(): Failed to resolve symbolic link '" << link << "'";
|
|
return "";
|
|
}
|
|
|
|
std::string rpath_copy = rpath;
|
|
free( rpath );
|
|
return rpath_copy;
|
|
}
|
|
|
|
TEST( BlockSpecialTest, UnnamedBlockSpecialObject )
|
|
{
|
|
// Test default constructor produces empty BlockSpecial object ("", 0, 0).
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial bs;
|
|
EXPECT_BSEQTUP( bs, "", 0, 0 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, NamedBlockSpecialObjectPlainFile )
|
|
{
|
|
// Test any named plain file or directory (actually any non-block special name)
|
|
// produces BlockSpecial object (name, 0, 0).
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial bs( "/" );
|
|
EXPECT_BSEQTUP( bs, "/", 0, 0 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, NamedBlockSpecialObjectPlainFileDuplicate )
|
|
{
|
|
// Test second constructed BlockSpecial object with the same named plain file or
|
|
// directory, where the major and minor numbers are retrieved via internal
|
|
// BlockSpecial cache, produce an equivalent object (name, 0, 0).
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial bs1( "/" );
|
|
BlockSpecial bs2( "/" );
|
|
EXPECT_BSEQTUP( bs1, "/", 0, 0 );
|
|
EXPECT_BSEQTUP( bs2, "/", 0, 0 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, NamedBlockSpecialObjectBlockDevice )
|
|
{
|
|
std::string bname = get_block_name( 0 );
|
|
SKIP_IF_BLOCK_DEVICE_DOESNT_EXIST(bname);
|
|
|
|
// Test any block special name produces BlockSpecial object (name, major, minor).
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial bs( bname );
|
|
EXPECT_STREQ( bs.m_name.c_str(), bname.c_str() );
|
|
EXPECT_TRUE( bs.m_major > 0 || bs.m_minor > 0 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, NamedBlockSpecialObjectBlockDeviceDuplicate )
|
|
{
|
|
std::string bname = get_block_name( 0 );
|
|
SKIP_IF_BLOCK_DEVICE_DOESNT_EXIST(bname);
|
|
|
|
// Test that a second object with the same name of a block device produces the
|
|
// same (name, major, minor). Checking internal BlockSpecial caching again.
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial bs1( bname );
|
|
BlockSpecial bs2( bname );
|
|
EXPECT_STREQ( bs1.m_name.c_str(), bs2.m_name.c_str() );
|
|
EXPECT_EQ( bs1.m_major, bs2.m_major );
|
|
EXPECT_EQ( bs1.m_minor, bs2.m_minor );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, TwoNamedBlockSpecialObjectBlockDevices )
|
|
{
|
|
std::string bname1 = get_block_name( 0 );
|
|
SKIP_IF_BLOCK_DEVICE_DOESNT_EXIST(bname1);
|
|
std::string bname2 = get_block_name( 1 );
|
|
SKIP_IF_BLOCK_DEVICE_DOESNT_EXIST(bname2);
|
|
|
|
// Test that two different named block devices produce different
|
|
// (name, major, minor).
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial bs1( bname1 );
|
|
BlockSpecial bs2( bname2 );
|
|
EXPECT_STRNE( bs1.m_name.c_str(), bs2.m_name.c_str() );
|
|
EXPECT_TRUE( bs1.m_major != bs2.m_major || bs1.m_minor != bs2.m_minor );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, NamedBlockSpecialObjectBySymlinkMatches )
|
|
{
|
|
std::string lname = get_link_name();
|
|
SKIP_IF_BLOCK_DEVICE_DOESNT_EXIST(lname);
|
|
std::string bname = follow_link_name( lname );
|
|
SKIP_IF_BLOCK_DEVICE_DOESNT_EXIST(bname);
|
|
|
|
// Test that a symbolic link to a block device and the named block device produce
|
|
// BlockSpecial objects with different names but the same major, minor pair.
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial lnk( lname );
|
|
BlockSpecial bs( bname );
|
|
EXPECT_STRNE( lnk.m_name.c_str(), bs.m_name.c_str() );
|
|
EXPECT_EQ( lnk.m_major, bs.m_major );
|
|
EXPECT_EQ( lnk.m_minor, bs.m_minor );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, PreRegisteredCacheUsedBeforeFileSystem )
|
|
{
|
|
// Test that the cache is used before querying the file system by registering a
|
|
// deliberately wrong major, minor pair for /dev/null and ensuring those are the
|
|
// major, minor numbers reported for the device.
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial::register_block_special( "/dev/null", 4, 8 );
|
|
BlockSpecial bs( "/dev/null" );
|
|
EXPECT_BSEQTUP( bs, "/dev/null", 4, 8 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, OperatorEqualsTwoEmptyObjects )
|
|
{
|
|
// Test equality of two empty BlockSpecial objects.
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial bs1;
|
|
BlockSpecial bs2;
|
|
EXPECT_TRUE( bs1 == bs2 ) << ON_FAILURE_WHERE( bs1, bs2 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, OperatorEqualsSamePlainFiles )
|
|
{
|
|
// Test equality of two named plain file or directory BlockSpecial objects;
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial bs1( "/" );
|
|
BlockSpecial bs2( "/" );
|
|
EXPECT_TRUE( bs1 == bs2 ) << ON_FAILURE_WHERE( bs1, bs2 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, OperatorEqualsEmptyObjectAndPlainFile )
|
|
{
|
|
// Test inequality of empty and plain file BlockSpecial objects.
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial bs1;
|
|
BlockSpecial bs2( "/" );
|
|
EXPECT_FALSE( bs1 == bs2 ) << ON_FAILURE_WHERE( bs1, bs2 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, OperatorEqualsDifferentPlainFiles )
|
|
{
|
|
// Test inequality of two different plain file or directory BlockSpecial objects;
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial bs1( "/" );
|
|
BlockSpecial bs2( "/proc/partitions" );
|
|
EXPECT_FALSE( bs1 == bs2 ) << ON_FAILURE_WHERE( bs1, bs2 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, OperatorEqualsSameBlockDevices )
|
|
{
|
|
std::string bname = get_block_name( 0 );
|
|
SKIP_IF_BLOCK_DEVICE_DOESNT_EXIST(bname);
|
|
|
|
// Test equality of two BlockSpecial objects using the same named block device.
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial bs1( bname );
|
|
BlockSpecial bs2( bname );
|
|
EXPECT_TRUE( bs1 == bs2 ) << ON_FAILURE_WHERE( bs1, bs2 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, OperatorEqualsEmptyObjectAndBlockDevice )
|
|
{
|
|
std::string bname = get_block_name( 0 );
|
|
SKIP_IF_BLOCK_DEVICE_DOESNT_EXIST(bname);
|
|
|
|
// Test inequality of empty and named block device BlockSpecial objects.
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial bs1;
|
|
BlockSpecial bs2( bname );
|
|
EXPECT_FALSE( bs1 == bs2 ) << ON_FAILURE_WHERE( bs1, bs2 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, OperatorEqualsPlainFileAndBlockDevice )
|
|
{
|
|
std::string bname = get_block_name( 0 );
|
|
SKIP_IF_BLOCK_DEVICE_DOESNT_EXIST(bname);
|
|
|
|
// Test inequality of plain file and block device BlockSpecial objects.
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial bs1( "/" );
|
|
BlockSpecial bs2( bname );
|
|
EXPECT_FALSE( bs1 == bs2 ) << ON_FAILURE_WHERE( bs1, bs2 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, OperatorEqualsTwoDifferentBlockDevices )
|
|
{
|
|
std::string bname1 = get_block_name( 0 );
|
|
SKIP_IF_BLOCK_DEVICE_DOESNT_EXIST(bname1);
|
|
std::string bname2 = get_block_name( 1 );
|
|
SKIP_IF_BLOCK_DEVICE_DOESNT_EXIST(bname2);
|
|
|
|
// Test inequality of two different named block device BlockSpecial objects.
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial bs1( bname1 );
|
|
BlockSpecial bs2( bname2 );
|
|
EXPECT_FALSE( bs1 == bs2 ) << ON_FAILURE_WHERE( bs1, bs2 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, OperatorEqualsSameBlockDevicesWithMinorZero )
|
|
{
|
|
// Test for fix in bug #771670. Ensure that block devices with minor number 0 are
|
|
// compared by major, minor pair rather than by name.
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial::register_block_special( "/dev/mapper/encrypted_swap", 254, 0 );
|
|
BlockSpecial::register_block_special( "/dev/dm-0", 254, 0 );
|
|
BlockSpecial bs1( "/dev/mapper/encrypted_swap" );
|
|
BlockSpecial bs2( "/dev/dm-0" );
|
|
EXPECT_TRUE( bs1 == bs2 ) << ON_FAILURE_WHERE( bs1, bs2 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, OperatorLessThanTwoEmptyObjects )
|
|
{
|
|
// Test one empty BlockSpecial object is not ordered before another empty one.
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial bs1;
|
|
BlockSpecial bs2;
|
|
EXPECT_FALSE( bs1 < bs2 ) << ON_FAILURE_WHERE( bs1, bs2 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, OperatorLessThanSamePlainFiles )
|
|
{
|
|
// Test one plain file BlockSpecial object is not ordered before another of the
|
|
// same name.
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial bs1( "/" );
|
|
BlockSpecial bs2( "/" );
|
|
EXPECT_FALSE( bs1 < bs2 ) << ON_FAILURE_WHERE( bs1, bs2 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, OperatorLessThanDifferentPlainFiles )
|
|
{
|
|
// Test one plain file BlockSpecial object with the lower name is ordered before
|
|
// the other plain file with a higher name.
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial::register_block_special( "/dummy_file1", 0, 0 );
|
|
BlockSpecial::register_block_special( "/dummy_file2", 0, 0 );
|
|
BlockSpecial bs1( "/dummy_file1" );
|
|
BlockSpecial bs2( "/dummy_file2" );
|
|
EXPECT_TRUE( bs1 < bs2 ) << ON_FAILURE_WHERE( bs1, bs2 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, OperatorLessThanEmptyObjectAndPlainFile )
|
|
{
|
|
// Test empty BlockSpecial object with name "" is before a plain file of any name.
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial bs1;
|
|
BlockSpecial bs2( "/" );
|
|
EXPECT_TRUE( bs1 < bs2 ) << ON_FAILURE_WHERE( bs1, bs2 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, OperatorLessThanSameBlockDevices )
|
|
{
|
|
std::string bname = get_block_name( 0 );
|
|
SKIP_IF_BLOCK_DEVICE_DOESNT_EXIST(bname);
|
|
|
|
// Test one name block device BlockSpecial object is not ordered before another
|
|
// from the same name.
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial bs1( bname );
|
|
BlockSpecial bs2( bname );
|
|
EXPECT_FALSE( bs1 < bs2 ) << ON_FAILURE_WHERE( bs1, bs2 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, OperatorLessThanEmptyObjectAndBlockDevice )
|
|
{
|
|
std::string bname = get_block_name( 0 );
|
|
SKIP_IF_BLOCK_DEVICE_DOESNT_EXIST(bname);
|
|
|
|
// Test empty BlockSpecial object with name "" is before any block device.
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial bs1;
|
|
BlockSpecial bs2( bname );
|
|
EXPECT_TRUE( bs1 < bs2 ) << ON_FAILURE_WHERE( bs1, bs2 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, OperatorLessThanPlainFileAndBlockDevice )
|
|
{
|
|
std::string bname = get_block_name( 0 );
|
|
SKIP_IF_BLOCK_DEVICE_DOESNT_EXIST(bname);
|
|
|
|
// Test one plain file BlockSpecial object is ordered before any block device.
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial bs1( "/" );
|
|
BlockSpecial bs2( bname );
|
|
EXPECT_TRUE( bs1 < bs2 ) << ON_FAILURE_WHERE( bs1, bs2 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, OperatorLessThanTwoDifferentBlockDevicesMajorNumbers )
|
|
{
|
|
// Test one block device BlockSpecial object is ordered before another based on
|
|
// major numbers.
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial::register_block_special( "/dummy_block2", 1, 1 );
|
|
BlockSpecial::register_block_special( "/dummy_block1", 2, 0 );
|
|
BlockSpecial bs1( "/dummy_block2" );
|
|
BlockSpecial bs2( "/dummy_block1" );
|
|
EXPECT_TRUE( bs1 < bs2 ) << ON_FAILURE_WHERE( bs1, bs2 );
|
|
}
|
|
|
|
TEST( BlockSpecialTest, OperatorLessThanTwoDifferentBlockDevicesMinorNumbers )
|
|
{
|
|
// Test one block device BlockSpecial object is ordered before another based on
|
|
// minor numbers.
|
|
BlockSpecial::clear_cache();
|
|
BlockSpecial::register_block_special( "/dummy_block2", 2, 0 );
|
|
BlockSpecial::register_block_special( "/dummy_block1", 2, 1 );
|
|
BlockSpecial bs1( "/dummy_block2" );
|
|
BlockSpecial bs2( "/dummy_block1" );
|
|
EXPECT_TRUE( bs1 < bs2 ) << ON_FAILURE_WHERE( bs1, bs2 );
|
|
}
|
|
|
|
} // namespace GParted
|