Commit 41739db2 authored by Thomas Perl's avatar Thomas Perl

[diskusage] Add unit tests for size calculation

parent ed2aa363
......@@ -27,6 +27,13 @@ Requires: %{name} = %{version}-%{release}
%description devel
%{summary}.
%package tests
Summary: System settings C++ library (unit tests)
Group: System/Libraries
%description tests
%{summary}.
%prep
%setup -q -n %{name}-%{version}
......@@ -53,3 +60,8 @@ rm -rf %{buildroot}
%{_libdir}/pkgconfig/systemsettings.pc
%{_includedir}/systemsettings/*
%{_libdir}/libsystemsettings.so
%files tests
%defattr(-,root,root,-)
%{_libdir}/%{name}-tests/ut_diskusage
%{_datadir}/%{name}-tests/tests.xml
......@@ -34,15 +34,9 @@
#include "diskusage_p.h"
#include <QThread>
#include <QDir>
#include <QProcess>
#include <QDebug>
#include <QJSEngine>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusReply>
#include <sys/statvfs.h>
DiskUsageWorker::DiskUsageWorker(QObject *parent)
: QObject(parent)
......@@ -54,89 +48,12 @@ DiskUsageWorker::~DiskUsageWorker()
{
}
static quint64 calculateSize(QString directory, QString *expandedPath)
{
// In lieu of wordexp(3) support in Qt, fake it
if (directory.startsWith("~/")) {
directory = QDir::homePath() + '/' + directory.mid(2);
}
if (expandedPath) {
*expandedPath = directory;
}
// "/data/media/" is mounted in "/home/nemo/android_storage/" with read
// access for the "nemo" user ("/data/media/" itself isn't readable);
// Mounted via FUSE and /system/bin/sdcard, see here:
// https://source.android.com/devices/storage/config.html
if (directory == "/data/media/") {
directory = "/home/nemo/android_storage/";
}
QDir d(directory);
if (!d.exists() || !d.isReadable()) {
return 0L;
}
QProcess du;
du.start("du", QStringList() << "-sxb" << directory, QIODevice::ReadOnly);
du.waitForFinished();
if (du.exitStatus() != QProcess::NormalExit) {
qWarning() << "Could not determine size of:" << directory;
return 0L;
}
QStringList size_directory = QString::fromUtf8(du.readAll()).split('\t');
if (size_directory.size() > 1) {
return size_directory[0].toULongLong();
}
return 0L;
}
static quint64 calculateRpmSize(const QString &glob)
{
QProcess rpm;
rpm.start("rpm", QStringList() << "-qa" << "--queryformat=%{name}|%{size}\\n" << glob, QIODevice::ReadOnly);
rpm.waitForFinished();
if (rpm.exitStatus() != QProcess::NormalExit) {
qWarning() << "Could not determine size of RPM packages matching:" << glob;
return 0L;
}
quint64 result = 0L;
QStringList lines = QString::fromUtf8(rpm.readAll()).split('\n', QString::SkipEmptyParts);
foreach (const QString &line, lines) {
int index = line.indexOf('|');
if (index == -1) {
qWarning() << "Could not parse RPM output line:" << line;
continue;
}
QString package = line.left(index);
result += line.mid(index+1).toULongLong();
}
return result;
}
static quint64 calculateApkdSize(const QString &rest)
void DiskUsageWorker::submit(QStringList paths, QJSValue *callback)
{
Q_UNUSED(rest)
QDBusMessage msg = QDBusMessage::createMethodCall("com.jolla.apkd",
"/com/jolla/apkd", "com.jolla.apkd", "getAndroidAppDataUsage");
QDBusReply<qulonglong> reply = QDBusConnection::systemBus().call(msg);
if (reply.isValid()) {
return quint64(reply.value());
}
qWarning() << "Could not determine Android app data usage";
return 0L;
emit finished(calculate(paths), callback);
}
void DiskUsageWorker::submit(QStringList paths, QJSValue *callback)
QVariantMap DiskUsageWorker::calculate(QStringList paths)
{
QVariantMap usage;
QMap<QString, QString> expandedPaths; // input path -> expanded path
......@@ -154,19 +71,6 @@ void DiskUsageWorker::submit(QStringList paths, QJSValue *callback)
// Pseudo-path for querying Android apps' data usage
QString rest = path.mid(6);
usage[path] = calculateApkdSize(rest);
} else if (path == "/") {
// Shortcut for getting usage of rootfs
// TODO: Once we have Qt 5.4, use QStorageInfo
struct statvfs stv;
memset(&stv, 0, sizeof(stv));
if (statvfs(path.toUtf8().constData(), &stv) != 0) {
// Do not make an entry for the usage here
qWarning() << "statvfs() failed on:" << path;
continue;
}
quint64 fsSize = float(stv.f_frsize) * float(stv.f_blocks);
quint64 freeSpace = float(stv.f_frsize) * float(stv.f_bfree);
usage[path] = fsSize - freeSpace;
} else {
QString expandedPath;
quint64 size = calculateSize(path, &expandedPath);
......@@ -221,10 +125,9 @@ void DiskUsageWorker::submit(QStringList paths, QJSValue *callback)
}
}
emit finished(usage, callback);
return usage;
}
class DiskUsagePrivate
{
Q_DISABLE_COPY(DiskUsagePrivate)
......
/*
* Copyright (C) 2015 Jolla Ltd.
* Contact: Thomas Perl <thomas.perl@jolla.com>
*
* You may use this file under the terms of the BSD license as follows:
*
* "Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Nemo Mobile nor the names of its contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
*/
#include "diskusage.h"
#include "diskusage_p.h"
#include <QDir>
#include <QProcess>
#include <QDebug>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusReply>
#include <sys/statvfs.h>
quint64 DiskUsageWorker::calculateSize(QString directory, QString *expandedPath)
{
// In lieu of wordexp(3) support in Qt, fake it
if (directory.startsWith("~/")) {
directory = QDir::homePath() + '/' + directory.mid(2);
}
if (expandedPath) {
*expandedPath = directory;
}
if (directory == "/") {
// Shortcut for getting usage of rootfs
// TODO: Once we have Qt 5.4, use QStorageInfo
struct statvfs stv;
memset(&stv, 0, sizeof(stv));
if (statvfs(directory.toUtf8().constData(), &stv) != 0) {
// Do not make an entry for the usage here
qWarning() << "statvfs() failed on:" << directory;
return 0L;
}
quint64 fsSize = float(stv.f_frsize) * float(stv.f_blocks);
quint64 freeSpace = float(stv.f_frsize) * float(stv.f_bfree);
return fsSize - freeSpace;
}
// "/data/media/" is mounted in "/home/nemo/android_storage/" with read
// access for the "nemo" user ("/data/media/" itself isn't readable);
// Mounted via FUSE and /system/bin/sdcard, see here:
// https://source.android.com/devices/storage/config.html
if (directory == "/data/media/") {
directory = "/home/nemo/android_storage/";
}
QDir d(directory);
if (!d.exists() || !d.isReadable()) {
return 0L;
}
QProcess du;
du.start("du", QStringList() << "-sxb" << directory, QIODevice::ReadOnly);
du.waitForFinished();
if (du.exitStatus() != QProcess::NormalExit) {
qWarning() << "Could not determine size of:" << directory;
return 0L;
}
QStringList size_directory = QString::fromUtf8(du.readAll()).split('\t');
if (size_directory.size() > 1) {
return size_directory[0].toULongLong();
}
return 0L;
}
quint64 DiskUsageWorker::calculateRpmSize(const QString &glob)
{
QProcess rpm;
rpm.start("rpm", QStringList() << "-qa" << "--queryformat=%{name}|%{size}\\n" << glob, QIODevice::ReadOnly);
rpm.waitForFinished();
if (rpm.exitStatus() != QProcess::NormalExit) {
qWarning() << "Could not determine size of RPM packages matching:" << glob;
return 0L;
}
quint64 result = 0L;
QStringList lines = QString::fromUtf8(rpm.readAll()).split('\n', QString::SkipEmptyParts);
foreach (const QString &line, lines) {
int index = line.indexOf('|');
if (index == -1) {
qWarning() << "Could not parse RPM output line:" << line;
continue;
}
QString package = line.left(index);
result += line.mid(index+1).toULongLong();
}
return result;
}
quint64 DiskUsageWorker::calculateApkdSize(const QString &rest)
{
Q_UNUSED(rest)
QDBusMessage msg = QDBusMessage::createMethodCall("com.jolla.apkd",
"/com/jolla/apkd", "com.jolla.apkd", "getAndroidAppDataUsage");
QDBusReply<qulonglong> reply = QDBusConnection::systemBus().call(msg);
if (reply.isValid()) {
return quint64(reply.value());
}
qWarning() << "Could not determine Android app data usage";
return 0L;
}
......@@ -54,7 +54,14 @@ signals:
void finished(QVariantMap usage, QJSValue *callback);
private:
QVariantMap calculate(QStringList paths);
quint64 calculateSize(QString directory, QString *expandedPath);
quint64 calculateRpmSize(const QString &glob);
quint64 calculateApkdSize(const QString &rest);
bool m_quit;
friend class Ut_DiskUsage;
};
#endif /* DISKUSAGE_P_H */
......@@ -22,7 +22,8 @@ SOURCES += \
aboutsettings.cpp \
devicelockiface.cpp \
developermodesettings.cpp \
diskusage.cpp
diskusage.cpp \
diskusage_impl.cpp
HEADERS += \
languagemodel.h \
......
......@@ -4,4 +4,4 @@ src_plugins.subdir = src/plugin
src_plugins.target = sub-plugins
src_plugins.depends = src
SUBDIRS = src src_plugins
SUBDIRS = src src_plugins tests
# based on tests.pro from libprofile-qt
PACKAGENAME = nemo-qml-plugin-systemsettings
QT += testlib qml dbus systeminfo
QT -= gui
system(sed -e s/@PACKAGENAME@/$${PACKAGENAME}/g tests.xml.template > tests.xml)
TEMPLATE = app
TARGET = ut_diskusage
target.path = /usr/lib/$${PACKAGENAME}-tests
xml.path = /usr/share/$${PACKAGENAME}-tests
xml.files = tests.xml
contains(cov, true) {
message("Coverage options enabled")
QMAKE_CXXFLAGS += --coverage
QMAKE_LFLAGS += --coverage
}
CONFIG += link_prl
DEFINES += UNIT_TEST
QMAKE_EXTRA_TARGETS = check
check.depends = $$TARGET
check.commands = LD_LIBRARY_PATH=../../lib ./$$TARGET
INCLUDEPATH += ../src/
SOURCES += ut_diskusage.cpp
HEADERS += ut_diskusage.h
SOURCES += ../src/diskusage.cpp
HEADERS += ../src/diskusage.h
HEADERS += ../src/diskusage_p.h
INSTALLS += target xml
<?xml version="1.0" encoding="UTF-8"?>
<testdefinition version="1.0">
<suite name="@PACKAGENAME@-tests" domain="Middleware">
<set name="@PACKAGENAME@-diskusage" description="ut_diskusage" feature="@PACKAGENAME@">
<case name="testSimple" description="Test basic functionality"
type="Functional" level="Component" timeout="600">
<step expected_result="0">/usr/lib/@PACKAGENAME@-tests/ut_diskusage testSimple</step>
</case>
<case name="testSubtractApkdFromRoot" description="Test if subtracting :apkd: from / works"
type="Functional" level="Component" timeout="600">
<step expected_result="0">/usr/lib/@PACKAGENAME@-tests/ut_diskusage testSubtractApkdFromRoot</step>
</case>
<case name="testSubtractRPMFromRoot" description="Test if subtracting :rpm: from / works"
type="Functional" level="Component" timeout="600">
<step expected_result="0">/usr/lib/@PACKAGENAME@-tests/ut_diskusage testSubtractRPMFromRoot</step>
</case>
<case name="testSubtractSubdirectory" description="Test if subtracting a subdirectory works"
type="Functional" level="Component" timeout="600">
<step expected_result="0">/usr/lib/@PACKAGENAME@-tests/ut_diskusage testSubtractSubdirectory</step>
</case>
<case name="testSubtractNestedSubdirectory" description="Test if subtracting nested subdirectories works"
type="Functional" level="Component" timeout="600">
<step expected_result="0">/usr/lib/@PACKAGENAME@-tests/ut_diskusage testSubtractNestedSubdirectory</step>
</case>
<case name="testSubtractNestedSubdirectoryMulti" description="Test if subtracting nested and sibling subdirectories works"
type="Functional" level="Component" timeout="600">
<step expected_result="0">/usr/lib/@PACKAGENAME@-tests/ut_diskusage testSubtractNestedSubdirectoryMulti</step>
</case>
</set>
</suite>
</testdefinition>
/*
* Copyright (C) 2015 Jolla Ltd.
* Contact: Thomas Perl <thomas.perl@jolla.com>
*
* You may use this file under the terms of the BSD license as follows:
*
* "Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Nemo Mobile nor the names of its contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
*/
#include "diskusage.h"
#include "diskusage_p.h"
#include "ut_diskusage.h"
#include <QtTest>
static QVariantMap g_mocked_file_size;
static QVariantMap g_mocked_rpm_size;
static QVariantMap g_mocked_apkd_size;
#define MB(x) ((x) * 1024 * 1024)
#define UT_DISKUSAGE_EXPECT_SIZE(path, size) { \
QVERIFY(usage.contains(path)); \
QCOMPARE(usage[path].toLongLong(), size); \
}
/* Mocked implementations of size calculation functions */
quint64 DiskUsageWorker::calculateSize(QString directory, QString *expandedPath)
{
if (expandedPath) {
*expandedPath = directory;
}
return quint64(g_mocked_file_size.value(directory, qlonglong(0)).toLongLong());
}
quint64 DiskUsageWorker::calculateRpmSize(const QString &glob)
{
return quint64(g_mocked_rpm_size.value(glob, qlonglong(0)).toLongLong());
}
quint64 DiskUsageWorker::calculateApkdSize(const QString &rest)
{
return quint64(g_mocked_apkd_size.value(rest, qlonglong(0)).toLongLong());
}
void Ut_DiskUsage::cleanup()
{
g_mocked_file_size.clear();
g_mocked_rpm_size.clear();
g_mocked_apkd_size.clear();
}
void Ut_DiskUsage::testSimple()
{
g_mocked_file_size["/"] = MB(1000);
g_mocked_file_size["/home/"] = MB(500);
g_mocked_file_size["/data/app/"] = MB(100);
QVariantMap usage = DiskUsageWorker().calculate(QStringList() << "/" << "/home/" << "/data/app/");
UT_DISKUSAGE_EXPECT_SIZE("/", MB(400))
UT_DISKUSAGE_EXPECT_SIZE("/home/", MB(500))
UT_DISKUSAGE_EXPECT_SIZE("/data/app/", MB(100))
}
void Ut_DiskUsage::testSubtractApkdFromRoot()
{
g_mocked_file_size["/"] = MB(100);
g_mocked_apkd_size[""] = MB(20);
QVariantMap usage = DiskUsageWorker().calculate(QStringList() << "/" << ":apkd:");
UT_DISKUSAGE_EXPECT_SIZE("/", MB(80))
UT_DISKUSAGE_EXPECT_SIZE(":apkd:", MB(20))
}
void Ut_DiskUsage::testSubtractRPMFromRoot()
{
g_mocked_file_size["/"] = MB(200);
g_mocked_rpm_size[""] = MB(100);
g_mocked_rpm_size["harbour-*"] = MB(20);
QVariantMap usage = DiskUsageWorker().calculate(QStringList() << "/" << ":rpm:" << ":rpm:harbour-*");
UT_DISKUSAGE_EXPECT_SIZE("/", MB(100))
UT_DISKUSAGE_EXPECT_SIZE(":rpm:", MB(80))
UT_DISKUSAGE_EXPECT_SIZE(":rpm:harbour-*", MB(20))
}
void Ut_DiskUsage::testSubtractSubdirectory()
{
g_mocked_file_size["/"] = MB(100);
g_mocked_file_size["/home/"] = MB(50);
QVariantMap usage = DiskUsageWorker().calculate(QStringList() << "/" << "/home/");
UT_DISKUSAGE_EXPECT_SIZE("/", MB(50))
UT_DISKUSAGE_EXPECT_SIZE("/home/", MB(50))
}
void Ut_DiskUsage::testSubtractNestedSubdirectory()
{
g_mocked_file_size["/"] = MB(1000);
g_mocked_file_size["/home/"] = MB(300);
g_mocked_file_size["/home/nemo/"] = MB(150);
g_mocked_file_size["/home/nemo/Documents/"] = MB(70);
QVariantMap usage = DiskUsageWorker().calculate(QStringList() <<
"/" << "/home/" << "/home/nemo/" << "/home/nemo/Documents/");
UT_DISKUSAGE_EXPECT_SIZE("/", MB(1000) - MB(300))
UT_DISKUSAGE_EXPECT_SIZE("/home/", MB(300) - MB(150))
UT_DISKUSAGE_EXPECT_SIZE("/home/nemo/", MB(150) - MB(70))
UT_DISKUSAGE_EXPECT_SIZE("/home/nemo/Documents/", MB(70))
}
void Ut_DiskUsage::testSubtractNestedSubdirectoryMulti()
{
g_mocked_file_size["/"] = MB(1000);
g_mocked_file_size["/home/"] = MB(300);
g_mocked_file_size["/home/nemo/"] = MB(150);
g_mocked_file_size["/home/nemo/Documents/"] = MB(70);
g_mocked_file_size["/opt/"] = MB(100);
g_mocked_file_size["/opt/foo/"] = MB(30);
g_mocked_file_size["/opt/foo/bar/"] = MB(20);
g_mocked_file_size["/opt/baz/"] = MB(10);
QVariantMap usage = DiskUsageWorker().calculate(QStringList() << "/" <<
"/home/" << "/home/nemo/" << "/home/nemo/Documents/" <<
"/opt/" << "/opt/foo/" << "/opt/foo/bar/" << "/opt/baz/");
UT_DISKUSAGE_EXPECT_SIZE("/", MB(1000) - MB(300) - MB(100))
UT_DISKUSAGE_EXPECT_SIZE("/home/", MB(300) - MB(150))
UT_DISKUSAGE_EXPECT_SIZE("/home/nemo/", MB(150) - MB(70))
UT_DISKUSAGE_EXPECT_SIZE("/home/nemo/Documents/", MB(70))
UT_DISKUSAGE_EXPECT_SIZE("/opt/", MB(100) - MB(30) - MB(10))
UT_DISKUSAGE_EXPECT_SIZE("/opt/foo/", MB(30) - MB(20))
UT_DISKUSAGE_EXPECT_SIZE("/opt/foo/bar/", MB(20))
UT_DISKUSAGE_EXPECT_SIZE("/opt/baz/", MB(10))
}
QTEST_APPLESS_MAIN(Ut_DiskUsage)
/*
* Copyright (C) 2015 Jolla Ltd.
* Contact: Thomas Perl <thomas.perl@jolla.com>
*
* You may use this file under the terms of the BSD license as follows:
*
* "Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Nemo Mobile nor the names of its contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
*/
#ifndef UT_DISKUSAGE_H
#define UT_DISKUSAGE_H
#include <QObject>
class Ut_DiskUsage : public QObject {
Q_OBJECT
private slots:
void cleanup();
void testSimple();
void testSubtractApkdFromRoot();
void testSubtractRPMFromRoot();
void testSubtractSubdirectory();
void testSubtractNestedSubdirectory();
void testSubtractNestedSubdirectoryMulti();
};
#endif /* UT_DISKUSAGE_H */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment