2014-06-25 15:24:53 +04:00
/*
* Copyright ( C ) Red Hat , Inc . 2014
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2.1 of the License , or ( at your option ) any later version .
*
* This library 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
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library . If not , see
* < http : //www.gnu.org/licenses/>.
*/
# include <config.h>
# include "testutils.h"
# include "domain_capabilities.h"
2019-04-04 13:42:14 +03:00
# include "virfilewrapper.h"
# include "configmake.h"
2014-06-25 15:24:53 +04:00
# define VIR_FROM_THIS VIR_FROM_NONE
2019-03-18 19:40:03 +03:00
# if WITH_QEMU || WITH_BHYVE
2014-09-17 03:52:54 +04:00
static int ATTRIBUTE_SENTINEL
fillStringValues ( virDomainCapsStringValuesPtr values , . . . )
{
int ret = 0 ;
va_list list ;
const char * str ;
va_start ( list , values ) ;
while ( ( str = va_arg ( list , const char * ) ) ) {
if ( VIR_REALLOC_N ( values - > values , values - > nvalues + 1 ) < 0 | |
VIR_STRDUP ( values - > values [ values - > nvalues ] , str ) < 0 ) {
ret = - 1 ;
break ;
}
values - > nvalues + + ;
}
va_end ( list ) ;
return ret ;
}
2019-03-18 19:40:03 +03:00
# endif /* WITH_QEMU || WITH_BHYVE */
2014-09-17 03:52:54 +04:00
2016-04-25 15:20:58 +03:00
# if WITH_QEMU
2014-06-25 20:39:29 +04:00
# include "testutilsqemu.h"
2017-07-21 15:24:51 +03:00
# include "testutilshostcpus.h"
2016-12-18 22:22:24 +03:00
2016-04-22 23:22:30 +03:00
static int
fakeHostCPU ( virCapsPtr caps ,
virArch arch )
{
virCPUDefPtr cpu ;
2017-07-21 15:24:51 +03:00
if ( ! ( cpu = testUtilsHostCpusGetDefForArch ( arch ) ) ) {
2016-04-22 23:22:30 +03:00
virReportError ( VIR_ERR_INTERNAL_ERROR ,
" cannot fake host CPU for arch %s " ,
virArchToString ( arch ) ) ;
return - 1 ;
}
2017-07-21 15:24:51 +03:00
qemuTestSetHostCPU ( caps , cpu ) ;
2016-04-22 23:22:30 +03:00
return 0 ;
}
2014-09-17 13:33:35 +04:00
static int
2014-06-25 20:39:29 +04:00
fillQemuCaps ( virDomainCapsPtr domCaps ,
2016-04-25 15:20:58 +03:00
const char * name ,
2016-05-10 20:59:48 +03:00
const char * arch ,
2016-04-28 19:01:18 +03:00
const char * machine ,
2016-04-25 15:20:58 +03:00
virQEMUDriverConfigPtr cfg )
2014-06-25 20:39:29 +04:00
{
2016-04-25 15:20:58 +03:00
int ret = - 1 ;
char * path = NULL ;
2016-04-22 23:22:30 +03:00
virCapsPtr caps = NULL ;
2016-04-25 15:20:58 +03:00
virQEMUCapsPtr qemuCaps = NULL ;
2015-01-21 21:38:57 +03:00
virDomainCapsLoaderPtr loader = & domCaps - > os . loader ;
2014-06-25 20:39:29 +04:00
2016-06-15 15:35:18 +03:00
if ( ! ( caps = virCapabilitiesNew ( domCaps - > arch , false , false ) ) | |
fakeHostCPU ( caps , domCaps - > arch ) < 0 )
goto cleanup ;
2019-04-16 13:31:00 +03:00
if ( virAsprintf ( & path , " %s/%s.%s.xml " ,
TEST_QEMU_CAPS_PATH , name , arch ) < 0 | |
2016-06-15 15:35:18 +03:00
! ( qemuCaps = qemuTestParseCapabilities ( caps , path ) ) )
2016-04-25 15:20:58 +03:00
goto cleanup ;
2017-12-06 16:56:54 +03:00
if ( machine ) {
VIR_FREE ( domCaps - > machine ) ;
if ( VIR_STRDUP ( domCaps - > machine ,
virQEMUCapsGetCanonicalMachine ( qemuCaps , machine ) ) < 0 )
goto cleanup ;
}
2016-04-28 19:01:18 +03:00
if ( ! domCaps - > machine & &
VIR_STRDUP ( domCaps - > machine ,
2018-08-10 17:06:38 +03:00
virQEMUCapsGetPreferredMachine ( qemuCaps ) ) < 0 )
2016-04-28 19:01:18 +03:00
goto cleanup ;
2016-04-22 23:22:30 +03:00
if ( virQEMUCapsFillDomainCaps ( caps , domCaps , qemuCaps ,
2019-04-04 13:42:14 +03:00
false ,
2016-05-18 01:45:27 +03:00
cfg - > firmwares ,
2016-06-27 16:12:34 +03:00
cfg - > nfirmwares ) < 0 )
2016-04-25 15:20:58 +03:00
goto cleanup ;
2014-06-25 20:39:29 +04:00
/* The function above tries to query host's KVM & VFIO capabilities by
* calling qemuHostdevHostSupportsPassthroughLegacy ( ) and
* qemuHostdevHostSupportsPassthroughVFIO ( ) which , however , can ' t be
* successfully mocked as they are not exposed as internal APIs . Therefore ,
* instead of mocking set the expected values here by hand . */
VIR_DOMAIN_CAPS_ENUM_SET ( domCaps - > hostdev . pciBackend ,
VIR_DOMAIN_HOSTDEV_PCI_BACKEND_DEFAULT ,
VIR_DOMAIN_HOSTDEV_PCI_BACKEND_KVM ,
VIR_DOMAIN_HOSTDEV_PCI_BACKEND_VFIO ) ;
2014-09-17 19:17:03 +04:00
2016-04-25 15:20:58 +03:00
/* As of f05b6a918e28 we are expecting to see OVMF_CODE.fd file which
* may not exists everywhere . */
2015-01-21 21:38:57 +03:00
while ( loader - > values . nvalues )
VIR_FREE ( loader - > values . values [ - - loader - > values . nvalues ] ) ;
if ( fillStringValues ( & loader - > values ,
2015-01-21 21:44:43 +03:00
" /usr/share/AAVMF/AAVMF_CODE.fd " ,
2017-07-20 22:56:55 +03:00
" /usr/share/AAVMF/AAVMF32_CODE.fd " ,
2015-01-21 21:38:57 +03:00
" /usr/share/OVMF/OVMF_CODE.fd " ,
NULL ) < 0 )
2014-06-25 15:24:53 +04:00
goto cleanup ;
2016-04-25 15:20:58 +03:00
ret = 0 ;
2014-06-25 15:24:53 +04:00
cleanup :
2016-04-22 23:22:30 +03:00
virObjectUnref ( caps ) ;
2016-04-25 15:20:58 +03:00
virObjectUnref ( qemuCaps ) ;
VIR_FREE ( path ) ;
2014-09-17 03:52:54 +04:00
return ret ;
2014-06-25 15:24:53 +04:00
}
2016-04-25 15:20:58 +03:00
# endif /* WITH_QEMU */
2016-05-17 00:21:59 +03:00
# ifdef WITH_LIBXL
# include "testutilsxen.h"
static int
fillXenCaps ( virDomainCapsPtr domCaps )
{
virFirmwarePtr * firmwares ;
int ret = - 1 ;
if ( VIR_ALLOC_N ( firmwares , 2 ) < 0 )
return ret ;
if ( VIR_ALLOC ( firmwares [ 0 ] ) < 0 | | VIR_ALLOC ( firmwares [ 1 ] ) < 0 )
goto cleanup ;
if ( VIR_STRDUP ( firmwares [ 0 ] - > name , " /usr/lib/xen/boot/hvmloader " ) < 0 | |
VIR_STRDUP ( firmwares [ 1 ] - > name , " /usr/lib/xen/boot/ovmf.bin " ) < 0 )
goto cleanup ;
if ( libxlMakeDomainCapabilities ( domCaps , firmwares , 2 ) < 0 )
goto cleanup ;
ret = 0 ;
cleanup :
virFirmwareFreeList ( firmwares , 2 ) ;
return ret ;
}
# endif /* WITH_LIBXL */
2017-03-18 23:17:13 +03:00
# ifdef WITH_BHYVE
# include "bhyve / bhyve_capabilities.h"
static int
fillBhyveCaps ( virDomainCapsPtr domCaps , unsigned int * bhyve_caps )
{
virDomainCapsStringValuesPtr firmwares = NULL ;
int ret = - 1 ;
if ( VIR_ALLOC ( firmwares ) < 0 )
return - 1 ;
if ( fillStringValues ( firmwares , " /foo/bar " , " /foo/baz " , NULL ) < 0 )
goto cleanup ;
if ( virBhyveDomainCapsFill ( domCaps , * bhyve_caps , firmwares ) < 0 )
goto cleanup ;
ret = 0 ;
cleanup :
VIR_FREE ( firmwares ) ;
return ret ;
}
# endif /* WITH_BHYVE */
2016-05-17 00:21:59 +03:00
2016-04-25 15:20:58 +03:00
enum testCapsType {
CAPS_NONE ,
CAPS_QEMU ,
2016-05-17 00:21:59 +03:00
CAPS_LIBXL ,
2017-03-18 23:17:13 +03:00
CAPS_BHYVE ,
2016-04-25 15:20:58 +03:00
} ;
2014-06-25 15:24:53 +04:00
2016-04-25 15:20:58 +03:00
struct testData {
const char * name ;
const char * emulator ;
2014-06-25 15:24:53 +04:00
const char * machine ;
2016-05-10 20:59:48 +03:00
const char * arch ;
2014-06-25 15:24:53 +04:00
virDomainVirtType type ;
2016-04-25 15:20:58 +03:00
enum testCapsType capsType ;
const char * capsName ;
void * capsOpaque ;
2014-06-25 15:24:53 +04:00
} ;
static int
test_virDomainCapsFormat ( const void * opaque )
{
2016-04-25 15:20:58 +03:00
const struct testData * data = opaque ;
2014-06-25 15:24:53 +04:00
virDomainCapsPtr domCaps = NULL ;
char * path = NULL ;
char * domCapsXML = NULL ;
int ret = - 1 ;
2016-05-10 21:29:17 +03:00
if ( virAsprintf ( & path , " %s/domaincapsschemadata/%s.xml " ,
2016-04-25 15:20:58 +03:00
abs_srcdir , data - > name ) < 0 )
2014-06-25 15:24:53 +04:00
goto cleanup ;
2016-05-10 20:59:48 +03:00
if ( ! ( domCaps = virDomainCapsNew ( data - > emulator , data - > machine ,
virArchFromString ( data - > arch ) ,
2016-04-25 15:20:58 +03:00
data - > type ) ) )
2014-06-25 15:24:53 +04:00
goto cleanup ;
2016-04-25 15:20:58 +03:00
switch ( data - > capsType ) {
case CAPS_NONE :
break ;
case CAPS_QEMU :
# if WITH_QEMU
2016-04-28 19:01:18 +03:00
if ( fillQemuCaps ( domCaps , data - > capsName , data - > arch , data - > machine ,
2016-04-25 15:19:49 +03:00
data - > capsOpaque ) < 0 )
2016-04-25 15:20:58 +03:00
goto cleanup ;
2016-05-17 00:21:59 +03:00
# endif
break ;
case CAPS_LIBXL :
# if WITH_LIBXL
if ( fillXenCaps ( domCaps ) < 0 )
goto cleanup ;
2017-03-18 23:17:13 +03:00
# endif
break ;
case CAPS_BHYVE :
# if WITH_BHYVE
if ( fillBhyveCaps ( domCaps , data - > capsOpaque ) < 0 )
goto cleanup ;
2016-04-25 15:20:58 +03:00
# endif
break ;
}
2014-06-25 15:24:53 +04:00
if ( ! ( domCapsXML = virDomainCapsFormat ( domCaps ) ) )
goto cleanup ;
2016-05-26 18:01:53 +03:00
if ( virTestCompareToFile ( domCapsXML , path ) < 0 )
2014-06-25 15:24:53 +04:00
goto cleanup ;
ret = 0 ;
cleanup :
VIR_FREE ( domCapsXML ) ;
VIR_FREE ( path ) ;
virObjectUnref ( domCaps ) ;
return ret ;
}
static int
mymain ( void )
{
int ret = 0 ;
2017-03-18 23:17:13 +03:00
# if WITH_BHYVE
unsigned int bhyve_caps = 0 ;
# endif
2016-04-25 15:20:58 +03:00
# if WITH_QEMU
2014-09-17 03:52:54 +04:00
virQEMUDriverConfigPtr cfg = virQEMUDriverConfigNew ( false ) ;
2015-01-26 19:09:36 +03:00
if ( ! cfg )
return EXIT_FAILURE ;
2016-04-25 15:20:58 +03:00
# endif
2017-11-03 15:09:47 +03:00
# define DO_TEST(Name, Emulator, Machine, Arch, Type, CapsType) \
do { \
struct testData data = { \
. name = Name , \
. emulator = Emulator , \
. machine = Machine , \
. arch = Arch , \
. type = Type , \
. capsType = CapsType , \
} ; \
if ( virTestRun ( Name , test_virDomainCapsFormat , & data ) < 0 ) \
ret = - 1 ; \
2016-04-25 15:20:58 +03:00
} while ( 0 )
2015-01-26 19:09:36 +03:00
2017-11-03 15:09:47 +03:00
# define DO_TEST_QEMU(Name, CapsName, Emulator, Machine, Arch, Type) \
do { \
char * name = NULL ; \
if ( virAsprintf ( & name , " qemu_%s%s%s.%s " , \
Name , \
Machine ? " - " : " " , Machine ? Machine : " " , \
Arch ) < 0 ) { \
ret = - 1 ; \
break ; \
} \
struct testData data = { \
. name = name , \
. emulator = Emulator , \
. machine = Machine , \
. arch = Arch , \
. type = Type , \
. capsType = CAPS_QEMU , \
. capsName = CapsName , \
. capsOpaque = cfg , \
} ; \
if ( virTestRun ( name , test_virDomainCapsFormat , & data ) < 0 ) \
ret = - 1 ; \
VIR_FREE ( name ) ; \
2014-06-25 20:39:29 +04:00
} while ( 0 )
2017-11-03 15:09:47 +03:00
# define DO_TEST_LIBXL(Name, Emulator, Machine, Arch, Type) \
do { \
struct testData data = { \
. name = Name , \
. emulator = Emulator , \
. machine = Machine , \
. arch = Arch , \
. type = Type , \
. capsType = CAPS_LIBXL , \
} ; \
if ( virTestRun ( Name , test_virDomainCapsFormat , & data ) < 0 ) \
ret = - 1 ; \
2016-05-17 00:21:59 +03:00
} while ( 0 )
2017-03-18 23:17:13 +03:00
# define DO_TEST_BHYVE(Name, Emulator, BhyveCaps, Type) \
2017-11-03 15:09:47 +03:00
do { \
char * name = NULL ; \
if ( virAsprintf ( & name , " bhyve_%s.x86_64 " , Name ) < 0 ) { \
ret = - 1 ; \
break ; \
} \
struct testData data = { \
. name = name , \
. emulator = Emulator , \
. arch = " x86_64 " , \
. type = Type , \
. capsType = CAPS_BHYVE , \
. capsOpaque = BhyveCaps , \
} ; \
if ( virTestRun ( name , test_virDomainCapsFormat , & data ) < 0 ) \
ret = - 1 ; \
VIR_FREE ( name ) ; \
2017-03-18 23:17:13 +03:00
} while ( 0 )
2019-02-19 20:44:34 +03:00
DO_TEST ( " empty " , " /bin/emulatorbin " , " my-machine-type " ,
" x86_64 " , VIR_DOMAIN_VIRT_KVM , CAPS_NONE ) ;
2016-04-25 15:20:58 +03:00
# if WITH_QEMU
2019-04-04 13:42:14 +03:00
virFileWrapperAddPrefix ( SYSCONFDIR " /qemu/firmware " ,
abs_srcdir " /qemufirmwaredata/etc/qemu/firmware " ) ;
virFileWrapperAddPrefix ( PREFIX " /share/qemu/firmware " ,
abs_srcdir " /qemufirmwaredata/usr/share/qemu/firmware " ) ;
virFileWrapperAddPrefix ( " /home/user/.config/qemu/firmware " ,
abs_srcdir " /qemufirmwaredata/home/user/.config/qemu/firmware " ) ;
2016-05-24 16:54:54 +03:00
DO_TEST_QEMU ( " 1.7.0 " , " caps_1.7.0 " ,
2016-04-28 19:01:18 +03:00
" /usr/bin/qemu-system-x86_64 " , NULL ,
2016-11-18 12:10:35 +03:00
" x86_64 " , VIR_DOMAIN_VIRT_KVM ) ;
2014-06-25 20:39:29 +04:00
2016-05-10 21:39:11 +03:00
DO_TEST_QEMU ( " 2.6.0 " , " caps_2.6.0 " ,
2016-04-28 15:24:51 +03:00
" /usr/bin/qemu-system-x86_64 " , NULL ,
2016-11-18 12:12:10 +03:00
" x86_64 " , VIR_DOMAIN_VIRT_KVM ) ;
2016-04-28 15:24:51 +03:00
2016-11-16 18:31:23 +03:00
DO_TEST_QEMU ( " 2.8.0 " , " caps_2.8.0 " ,
" /usr/bin/qemu-system-x86_64 " , NULL ,
" x86_64 " , VIR_DOMAIN_VIRT_KVM ) ;
DO_TEST_QEMU ( " 2.8.0-tcg " , " caps_2.8.0 " ,
" /usr/bin/qemu-system-x86_64 " , NULL ,
" x86_64 " , VIR_DOMAIN_VIRT_QEMU ) ;
2017-01-30 18:10:49 +03:00
DO_TEST_QEMU ( " 2.9.0 " , " caps_2.9.0 " ,
" /usr/bin/qemu-system-x86_64 " , NULL ,
" x86_64 " , VIR_DOMAIN_VIRT_KVM ) ;
2017-04-13 17:13:49 +03:00
DO_TEST_QEMU ( " 2.9.0 " , " caps_2.9.0 " ,
" /usr/bin/qemu-system-x86_64 " , " q35 " ,
" x86_64 " , VIR_DOMAIN_VIRT_KVM ) ;
2017-01-30 18:10:49 +03:00
DO_TEST_QEMU ( " 2.9.0-tcg " , " caps_2.9.0 " ,
" /usr/bin/qemu-system-x86_64 " , NULL ,
" x86_64 " , VIR_DOMAIN_VIRT_QEMU ) ;
2018-03-23 15:58:42 +03:00
DO_TEST_QEMU ( " 2.12.0 " , " caps_2.12.0 " ,
" /usr/bin/qemu-system-x86_64 " , NULL ,
" x86_64 " , VIR_DOMAIN_VIRT_KVM ) ;
2018-03-23 15:31:14 +03:00
DO_TEST_QEMU ( " 2.6.0 " , " caps_2.6.0 " ,
2018-03-23 15:38:32 +03:00
" /usr/bin/qemu-system-aarch64 " , NULL ,
" aarch64 " , VIR_DOMAIN_VIRT_KVM ) ;
2018-03-23 15:31:14 +03:00
DO_TEST_QEMU ( " 2.6.0 " , " caps_2.6.0 " ,
2018-03-23 15:38:32 +03:00
" /usr/bin/qemu-system-aarch64 " , " virt " ,
" aarch64 " , VIR_DOMAIN_VIRT_KVM ) ;
2018-03-23 15:31:14 +03:00
DO_TEST_QEMU ( " 2.12.0 " , " caps_2.12.0 " ,
2018-03-23 15:58:42 +03:00
" /usr/bin/qemu-system-aarch64 " , " virt " ,
" aarch64 " , VIR_DOMAIN_VIRT_KVM ) ;
2018-03-23 15:38:32 +03:00
DO_TEST_QEMU ( " 2.6.0 " , " caps_2.6.0 " ,
" /usr/bin/qemu-system-ppc64 " , NULL ,
" ppc64 " , VIR_DOMAIN_VIRT_KVM ) ;
2018-03-23 15:58:42 +03:00
DO_TEST_QEMU ( " 2.12.0 " , " caps_2.12.0 " ,
" /usr/bin/qemu-system-ppc64 " , NULL ,
" ppc64 " , VIR_DOMAIN_VIRT_KVM ) ;
2016-12-18 22:22:24 +03:00
DO_TEST_QEMU ( " 2.7.0 " , " caps_2.7.0 " ,
" /usr/bin/qemu-system-s390x " , NULL ,
" s390x " , VIR_DOMAIN_VIRT_KVM ) ;
DO_TEST_QEMU ( " 2.8.0 " , " caps_2.8.0 " ,
" /usr/bin/qemu-system-s390x " , NULL ,
" s390x " , VIR_DOMAIN_VIRT_KVM ) ;
2018-03-23 15:58:42 +03:00
DO_TEST_QEMU ( " 2.12.0 " , " caps_2.12.0 " ,
" /usr/bin/qemu-system-s390x " , NULL ,
" s390x " , VIR_DOMAIN_VIRT_KVM ) ;
2018-09-20 13:20:57 +03:00
DO_TEST_QEMU ( " 3.0.0 " , " caps_3.0.0 " ,
" /usr/bin/qemu-system-s390x " , NULL ,
" s390x " , VIR_DOMAIN_VIRT_KVM ) ;
2019-03-11 18:46:46 +03:00
DO_TEST_QEMU ( " 3.1.0 " , " caps_3.1.0 " ,
" /usr/bin/qemu-system-x86_64 " , NULL ,
" x86_64 " , VIR_DOMAIN_VIRT_KVM ) ;
2019-01-17 20:52:45 +03:00
DO_TEST_QEMU ( " 4.0.0 " , " caps_4.0.0 " ,
" /usr/bin/qemu-system-x86_64 " , NULL ,
" x86_64 " , VIR_DOMAIN_VIRT_KVM ) ;
2017-04-09 14:28:07 +03:00
virObjectUnref ( cfg ) ;
2019-04-10 16:08:46 +03:00
virFileWrapperClearPrefixes ( ) ;
2019-04-04 13:42:14 +03:00
2014-06-25 20:39:29 +04:00
# endif /* WITH_QEMU */
2016-05-17 00:21:59 +03:00
# if WITH_LIBXL
2019-02-08 20:00:22 +03:00
DO_TEST_LIBXL ( " libxl-xenpv " , " /usr/bin/qemu-system-x86_64 " ,
2016-05-17 00:21:59 +03:00
" xenpv " , " x86_64 " , VIR_DOMAIN_VIRT_XEN ) ;
2019-02-08 20:00:22 +03:00
DO_TEST_LIBXL ( " libxl-xenfv " , " /usr/bin/qemu-system-x86_64 " ,
2016-05-17 00:21:59 +03:00
" xenfv " , " x86_64 " , VIR_DOMAIN_VIRT_XEN ) ;
# endif /* WITH_LIBXL */
2017-03-18 23:17:13 +03:00
# if WITH_BHYVE
DO_TEST_BHYVE ( " basic " , " /usr/sbin/bhyve " , & bhyve_caps , VIR_DOMAIN_VIRT_BHYVE ) ;
bhyve_caps | = BHYVE_CAP_LPC_BOOTROM ;
DO_TEST_BHYVE ( " uefi " , " /usr/sbin/bhyve " , & bhyve_caps , VIR_DOMAIN_VIRT_BHYVE ) ;
bhyve_caps | = BHYVE_CAP_FBUF ;
DO_TEST_BHYVE ( " fbuf " , " /usr/sbin/bhyve " , & bhyve_caps , VIR_DOMAIN_VIRT_BHYVE ) ;
# endif /* WITH_BHYVE */
2014-06-25 15:24:53 +04:00
return ret ;
}
2017-03-08 15:32:46 +03:00
# if WITH_QEMU
2017-03-29 17:45:42 +03:00
VIR_TEST_MAIN_PRELOAD ( mymain ,
2017-03-08 15:32:46 +03:00
abs_builddir " /.libs/domaincapsmock.so " ,
abs_builddir " /.libs/qemucpumock.so " )
# else
2017-03-29 17:45:42 +03:00
VIR_TEST_MAIN_PRELOAD ( mymain , abs_builddir " /.libs/domaincapsmock.so " )
2017-03-08 15:32:46 +03:00
# endif