Made consolidaed operating system adapters for ease of porting

This commit is contained in:
Thomas Schmitt 2006-11-16 11:17:55 +00:00
parent 628e935fe5
commit ff284b3f51
12 changed files with 277 additions and 111 deletions

View File

@ -39,6 +39,7 @@ libburn_libburn_la_SOURCES = \
libburn/null.h \ libburn/null.h \
libburn/options.c \ libburn/options.c \
libburn/options.h \ libburn/options.h \
libburn/os.h \
libburn/read.c \ libburn/read.c \
libburn/read.h \ libburn/read.h \
libburn/sbc.c \ libburn/sbc.c \
@ -184,6 +185,8 @@ EXTRA_DIST = \
cdrskin/wiki_plain.txt \ cdrskin/wiki_plain.txt \
cdrskin/cleanup.h \ cdrskin/cleanup.h \
cdrskin/cleanup.c \ cdrskin/cleanup.c \
libburn/os-freebsd.h \
libburn/os-linux.h \
libburn/sg-freebsd.c \ libburn/sg-freebsd.c \
libburn/sg-linux.c \ libburn/sg-linux.c \
COPYING COPYING

View File

@ -23,31 +23,27 @@ typedef void (*sighandler_t)(int);
#include "cleanup.h" #include "cleanup.h"
#ifdef __FreeBSD__
#ifndef Cleanup_has_no_libburn_os_H
#include "../libburn/os.h"
/* see os.h for name of particular os-*.h where this is defined */
static int signal_list[]= { BURN_OS_SIGNAL_MACRO_LIST , -1};
static char *signal_name_list[]= { BURN_OS_SIGNAL_NAME_LIST , "@"};
static int signal_list_count= BURN_OS_SIGNAL_COUNT;
static int non_signal_list[]= { BURN_OS_NON_SIGNAL_MACRO_LIST, -1};
static int non_signal_list_count= BURN_OS_NON_SIGNAL_COUNT;
#else /* ! Cleanup_has_no_libburn_os_H */
/* Outdated. Linux only. For backward compatibility with pre-libburn-0.2.3 */
/* Signals to be caught */ /* Signals to be caught */
static int signal_list[]= { static int signal_list[]= {
SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT,
SIGFPE, SIGSEGV, SIGPIPE, SIGALRM, SIGTERM,
SIGUSR1, SIGUSR2, SIGXCPU, SIGTSTP, SIGTTIN,
SIGTTOU,
SIGBUS, SIGPROF, SIGSYS, SIGTRAP,
SIGVTALRM, SIGXCPU, SIGXFSZ, -1
};
static char *signal_name_list[]= {
"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGABRT",
"SIGFPE", "SIGSEGV", "SIGPIPE", "SIGALRM", "SIGTERM",
"SIGUSR1", "SIGUSR2", "SIGXCPU", "SIGTSTP", "SIGTTIN",
"SIGTTOU",
"SIGBUS", "SIGPROF", "SIGSYS", "SIGTRAP",
"SIGVTALRM", "SIGXCPU", "SIGXFSZ", "@"
};
static int signal_list_count= 23;
#else /* __FreeBSD__ */
/* Signals to be caught */
static int signal_list[]= {
SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT,
SIGFPE, SIGSEGV, SIGPIPE, SIGALRM, SIGTERM, SIGFPE, SIGSEGV, SIGPIPE, SIGALRM, SIGTERM,
SIGUSR1, SIGUSR2, SIGXCPU, SIGTSTP, SIGTTIN, SIGUSR1, SIGUSR2, SIGXCPU, SIGTSTP, SIGTTIN,
@ -55,7 +51,7 @@ static int signal_list[]= {
SIGBUS, SIGPOLL, SIGPROF, SIGSYS, SIGTRAP, SIGBUS, SIGPOLL, SIGPROF, SIGSYS, SIGTRAP,
SIGVTALRM, SIGXCPU, SIGXFSZ, -1 SIGVTALRM, SIGXCPU, SIGXFSZ, -1
}; };
static char *signal_name_list[]= { static char *signal_name_list[]= {
"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGABRT", "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGABRT",
"SIGFPE", "SIGSEGV", "SIGPIPE", "SIGALRM", "SIGTERM", "SIGFPE", "SIGSEGV", "SIGPIPE", "SIGALRM", "SIGTERM",
"SIGUSR1", "SIGUSR2", "SIGXCPU", "SIGTSTP", "SIGTTIN", "SIGUSR1", "SIGUSR2", "SIGXCPU", "SIGTSTP", "SIGTTIN",
@ -65,15 +61,17 @@ static char *signal_name_list[]= {
}; };
static int signal_list_count= 24; static int signal_list_count= 24;
#endif /* ! __FreeBSD__ */
/* Signals not to be caught */ /* Signals not to be caught */
static int non_signal_list[]= { static int non_signal_list[]= {
SIGKILL, SIGCHLD, SIGSTOP, SIGURG, -1 SIGKILL, SIGCHLD, SIGSTOP, SIGURG, -1
}; };
static int non_signal_list_count= 4; static int non_signal_list_count= 4;
#endif /* Cleanup_has_no_libburn_os_H */
/* run time dynamic part */ /* run time dynamic part */
static char cleanup_msg[4096]= {""}; static char cleanup_msg[4096]= {""};
static int cleanup_exiting= 0; static int cleanup_exiting= 0;

View File

@ -32,13 +32,13 @@ do
libvers="-DCdrskin_libburn_cvs_A60220_tS" libvers="-DCdrskin_libburn_cvs_A60220_tS"
libdax_audioxtr_o= libdax_audioxtr_o=
libdax_msgs_o="libburn/message.o" libdax_msgs_o="libburn/message.o"
cleanup_src_or_obj="cdrskin/cleanup.c" cleanup_src_or_obj="-DCleanup_has_no_libburn_os_H cdrskin/cleanup.c"
elif test "$i" = "-libburn_0_2_2" elif test "$i" = "-libburn_0_2_2"
then then
libvers="-DCdrskin_libburn_0_2_2" libvers="-DCdrskin_libburn_0_2_2"
libdax_audioxtr_o= libdax_audioxtr_o=
libdax_msgs_o="libburn/message.o" libdax_msgs_o="libburn/message.o"
cleanup_src_or_obj="cdrskin/cleanup.c" cleanup_src_or_obj="-DCleanup_has_no_libburn_os_H cdrskin/cleanup.c"
elif test "$i" = "-libburn_0_2_3" elif test "$i" = "-libburn_0_2_3"
then then
libvers="-DCdrskin_libburn_0_2_3" libvers="-DCdrskin_libburn_0_2_3"
@ -51,6 +51,7 @@ do
elif test "$i" = "-oldfashioned" elif test "$i" = "-oldfashioned"
then then
def_opts="$def_opts -DCdrskin_oldfashioned_api_usE" def_opts="$def_opts -DCdrskin_oldfashioned_api_usE"
cleanup_src_or_obj="-DCleanup_has_no_libburn_os_H cdrskin/cleanup.c"
elif test "$i" = "-no_largefile" elif test "$i" = "-no_largefile"
then then
largefile_opts= largefile_opts=

View File

@ -23,31 +23,27 @@ typedef void (*sighandler_t)(int);
#include "cleanup.h" #include "cleanup.h"
#ifdef __FreeBSD__
#ifndef Cleanup_has_no_libburn_os_H
#include "../libburn/os.h"
/* see os.h for name of particular os-*.h where this is defined */
static int signal_list[]= { BURN_OS_SIGNAL_MACRO_LIST , -1};
static char *signal_name_list[]= { BURN_OS_SIGNAL_NAME_LIST , "@"};
static int signal_list_count= BURN_OS_SIGNAL_COUNT;
static int non_signal_list[]= { BURN_OS_NON_SIGNAL_MACRO_LIST, -1};
static int non_signal_list_count= BURN_OS_NON_SIGNAL_COUNT;
#else /* ! Cleanup_has_no_libburn_os_H */
/* Outdated. Linux only. For backward compatibility with pre-libburn-0.2.3 */
/* Signals to be caught */ /* Signals to be caught */
static int signal_list[]= { static int signal_list[]= {
SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT,
SIGFPE, SIGSEGV, SIGPIPE, SIGALRM, SIGTERM,
SIGUSR1, SIGUSR2, SIGXCPU, SIGTSTP, SIGTTIN,
SIGTTOU,
SIGBUS, SIGPROF, SIGSYS, SIGTRAP,
SIGVTALRM, SIGXCPU, SIGXFSZ, -1
};
static char *signal_name_list[]= {
"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGABRT",
"SIGFPE", "SIGSEGV", "SIGPIPE", "SIGALRM", "SIGTERM",
"SIGUSR1", "SIGUSR2", "SIGXCPU", "SIGTSTP", "SIGTTIN",
"SIGTTOU",
"SIGBUS", "SIGPROF", "SIGSYS", "SIGTRAP",
"SIGVTALRM", "SIGXCPU", "SIGXFSZ", "@"
};
static int signal_list_count= 23;
#else /* __FreeBSD__ */
/* Signals to be caught */
static int signal_list[]= {
SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT,
SIGFPE, SIGSEGV, SIGPIPE, SIGALRM, SIGTERM, SIGFPE, SIGSEGV, SIGPIPE, SIGALRM, SIGTERM,
SIGUSR1, SIGUSR2, SIGXCPU, SIGTSTP, SIGTTIN, SIGUSR1, SIGUSR2, SIGXCPU, SIGTSTP, SIGTTIN,
@ -55,7 +51,7 @@ static int signal_list[]= {
SIGBUS, SIGPOLL, SIGPROF, SIGSYS, SIGTRAP, SIGBUS, SIGPOLL, SIGPROF, SIGSYS, SIGTRAP,
SIGVTALRM, SIGXCPU, SIGXFSZ, -1 SIGVTALRM, SIGXCPU, SIGXFSZ, -1
}; };
static char *signal_name_list[]= { static char *signal_name_list[]= {
"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGABRT", "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGABRT",
"SIGFPE", "SIGSEGV", "SIGPIPE", "SIGALRM", "SIGTERM", "SIGFPE", "SIGSEGV", "SIGPIPE", "SIGALRM", "SIGTERM",
"SIGUSR1", "SIGUSR2", "SIGXCPU", "SIGTSTP", "SIGTTIN", "SIGUSR1", "SIGUSR2", "SIGXCPU", "SIGTSTP", "SIGTTIN",
@ -65,15 +61,17 @@ static char *signal_name_list[]= {
}; };
static int signal_list_count= 24; static int signal_list_count= 24;
#endif /* ! __FreeBSD__ */
/* Signals not to be caught */ /* Signals not to be caught */
static int non_signal_list[]= { static int non_signal_list[]= {
SIGKILL, SIGCHLD, SIGSTOP, SIGURG, -1 SIGKILL, SIGCHLD, SIGSTOP, SIGURG, -1
}; };
static int non_signal_list_count= 4; static int non_signal_list_count= 4;
#endif /* Cleanup_has_no_libburn_os_H */
/* run time dynamic part */ /* run time dynamic part */
static char cleanup_msg[4096]= {""}; static char cleanup_msg[4096]= {""};
static int cleanup_exiting= 0; static int cleanup_exiting= 0;

72
libburn/os-freebsd.h Normal file
View File

@ -0,0 +1,72 @@
/* os-freebsd.h
Operating system specific libburn definitions and declarations. Included
by os.h in case of compilation for
FreeBSD with CAM
Copyright (C) 2006 Thomas Schmitt <scdbackup@gmx.net>, provided under GPL
*/
#ifndef BURN_OS_H_INCLUDED
#define BURN_OS_H_INCLUDED 1
/** List of all signals which shall be caught by signal handlers and trigger
a graceful abort of libburn. (See man 7 signal.)
*/
/* Once as system defined macros */
#define BURN_OS_SIGNAL_MACRO_LIST \
SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, \
SIGFPE, SIGSEGV, SIGPIPE, SIGALRM, SIGTERM, \
SIGUSR1, SIGUSR2, SIGXCPU, SIGTSTP, SIGTTIN, \
SIGTTOU, \
SIGBUS, SIGPROF, SIGSYS, SIGTRAP, \
SIGVTALRM, SIGXCPU, SIGXFSZ
/* Once as text 1:1 list of strings for messages and interpreters */
#define BURN_OS_SIGNAL_NAME_LIST \
"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGABRT", \
"SIGFPE", "SIGSEGV", "SIGPIPE", "SIGALRM", "SIGTERM", \
"SIGUSR1", "SIGUSR2", "SIGXCPU", "SIGTSTP", "SIGTTIN", \
"SIGTTOU", \
"SIGBUS", "SIGPROF", "SIGSYS", "SIGTRAP", \
"SIGVTALRM", "SIGXCPU", "SIGXFSZ"
/* The number of above list items */
#define BURN_OS_SIGNAL_COUNT 23
/** To list all signals which shall surely not be caught */
#define BURN_OS_NON_SIGNAL_MACRO_LIST \
SIGKILL, SIGCHLD, SIGSTOP, SIGURG
/* The number of above list items */
#define BURN_OS_NON_SIGNAL_COUNT 4
/* The maximum size for a (SCSI) i/o transaction */
#define BURN_OS_TRANSPORT_BUFFER_SIZE 65536/2
/** To hold all state information of BSD device enumeration
which are now local in sg_enumerate() . So that sg_give_next_adr()
can work in BSD and sg_enumerate() can use it.
*/
#define BURN_OS_DEFINE_DRIVE_ENUMERATOR_T \
struct burn_drive_enumeration_state { \
union ccb ccb; \
int bufsize, fd; \
unsigned int i; \
int skip_device; \
}; \
typedef struct burn_drive_enumeration_state burn_drive_enumerator_t;
/* The list of operating system dependent elements in struct burn_drive.
To be initialized and used within sg-*.c .
*/
#define BURN_OS_TRANSPORT_DRIVE_ELEMENTS \
struct cam_device* cam;
#endif /* ! BURN_OS_H_INCLUDED */

63
libburn/os-linux.h Normal file
View File

@ -0,0 +1,63 @@
/* os-linux.h
Operating system specific libburn definitions and declarations. Included
by os.h in case of compilation for
Linux kernels 2.4 and 2.6 with Linux SCSI Generic (sg)
Copyright (C) 2006 Thomas Schmitt <scdbackup@gmx.net>, provided under GPL
*/
/** List of all signals which shall be caught by signal handlers and trigger
a graceful abort of libburn. (See man 7 signal.)
*/
/* Once as system defined macros */
#define BURN_OS_SIGNAL_MACRO_LIST \
SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, \
SIGFPE, SIGSEGV, SIGPIPE, SIGALRM, SIGTERM, \
SIGUSR1, SIGUSR2, SIGXCPU, SIGTSTP, SIGTTIN, \
SIGTTOU, \
SIGBUS, SIGPOLL, SIGPROF, SIGSYS, SIGTRAP, \
SIGVTALRM, SIGXCPU, SIGXFSZ
/* Once as text 1:1 list of strings for messages and interpreters */
#define BURN_OS_SIGNAL_NAME_LIST \
"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGABRT", \
"SIGFPE", "SIGSEGV", "SIGPIPE", "SIGALRM", "SIGTERM", \
"SIGUSR1", "SIGUSR2", "SIGXCPU", "SIGTSTP", "SIGTTIN", \
"SIGTTOU", \
"SIGBUS", "SIGPOLL", "SIGPROF", "SIGSYS", "SIGTRAP", \
"SIGVTALRM", "SIGXCPU", "SIGXFSZ"
/* The number of above list items */
#define BURN_OS_SIGNAL_COUNT 24
/** To list all signals which shall surely not be caught */
#define BURN_OS_NON_SIGNAL_MACRO_LIST \
SIGKILL, SIGCHLD, SIGSTOP, SIGURG
/* The number of above list items */
#define BURN_OS_NON_SIGNAL_COUNT 4
/* The maximum size for a (SCSI) i/o transaction */
#define BURN_OS_TRANSPORT_BUFFER_SIZE 65536
/* To hold the index number of the most recently delivered address from
device enumeration.
*/
#define BURN_OS_DEFINE_DRIVE_ENUMERATOR_T \
typedef int burn_drive_enumerator_t;
/* The list of operating system dependent elements in struct burn_drive.
Usually they are initialized in sg-*.c:enumerate_common().
*/
#define BURN_OS_TRANSPORT_DRIVE_ELEMENTS \
int fd; \
\
/* ts A60926 : trying to lock against growisofs /dev/srN, /dev/scdN */ \
int sibling_count; \
int sibling_fds[LIBBURN_SG_MAX_SIBLINGS];

34
libburn/os.h Normal file
View File

@ -0,0 +1,34 @@
/* os.h
Operating system specific libburn definitions and declarations.
The macros defined here are used by libburn modules in order to
avoid own system dependent case distinctions.
Copyright (C) 2006 Thomas Schmitt <scdbackup@gmx.net>, provided under GPL
*/
#ifndef BURN_OS_H_INCLUDED
#define BURN_OS_H_INCLUDED 1
/*
Operating system case distinction
*/
#ifdef __FreeBSD__
/* ----------------------------- FreeBSD with CAM -------------------------- */
#include "os-freebsd.h"
#else /* operating system case distinction */
/* --------- Linux kernels 2.4 and 2.6 with Linux SCSI Generic (sg) -------- */
#include "os-linux.h"
#endif /* End of operating system case distinction */
#endif /* ! BURN_OS_H_INCLUDED */

View File

@ -2,15 +2,30 @@
/* /*
This is the operating system dependent SCSI part of libburn. It implements the This is the main operating system dependent SCSI part of libburn. It implements
transport level aspects of SCSI control and command i/o. the transport level aspects of SCSI control and command i/o.
Present implementation: FreeBSD CAM (untested) Present implementation: FreeBSD CAM (untested)
PORTING: PORTING:
There are public functions, used by other parts of libburn, which have to be Porting libburn typically will consist of adding a new operating system case
implemented in a way that provides libburn with the desired services: to the following switcher files:
os.h Operating system specific libburn definitions and declarations.
sg.c Operating system dependent transport level modules.
and to derive the following system specific files from existing examples:
os-*.h Included by os.h. You will need some general system knowledge
about signals and knowledge about the storage object needs of your
transport level module sg-*.c.
sg-*.c This source module. You will need special system knowledge about
how to detect all potentially available drives and how to perform
low-level SCSI and drive operations. You will not need to know
about CD burning, MMC or other high level SCSI aspects.
Said low-level operations are defined by a public function interface, which has
to be implemented in a way that provides libburn with the desired services:
sg_give_next_adr() iterates over the set of potentially useful drive sg_give_next_adr() iterates over the set of potentially useful drive
address strings. address strings.
@ -35,12 +50,6 @@ sg_obtain_scsi_adr() tries to obtain SCSI address parameters.
Porting hints are marked by the text "PORTING:". Porting hints are marked by the text "PORTING:".
Send feedback to libburn-hackers@pykix.org . Send feedback to libburn-hackers@pykix.org .
Other source modules where to expect OS dependencies (look for "__FreeBSD__"):
cleanup.c signal_list : add or delete as described in your man 7 signal
transport.h struct burn_drive : OS dependent i/o attributes
BUFFER_SIZE maximum size for a (SCSI) i/o transaction
*/ */

View File

@ -2,15 +2,30 @@
/* /*
This is the operating system dependent SCSI part of libburn. It implements the This is the main operating system dependent SCSI part of libburn. It implements
transport level aspects of SCSI control and command i/o. the transport level aspects of SCSI control and command i/o.
Present implementation: Linux SCSI Generic (sg) Present implementation: Linux SCSI Generic (sg)
PORTING: PORTING:
There are public functions, used by other parts of libburn, which have to be Porting libburn typically will consist of adding a new operating system case
implemented in a way that provides libburn with the desired services: to the following switcher files:
os.h Operating system specific libburn definitions and declarations.
sg.c Operating system dependent transport level modules.
and to derive the following system specific files from existing examples:
os-*.h Included by os.h. You will need some general system knowledge
about signals and knowledge about the storage object needs of your
transport level module sg-*.c.
sg-*.c This source module. You will need special system knowledge about
how to detect all potentially available drives and how to perform
low-level SCSI and drive operations. You will not need to know
about CD burning, MMC or other high level SCSI aspects.
Said low-level operations are defined by a public function interface, which has
to be implemented in a way that provides libburn with the desired services:
sg_give_next_adr() iterates over the set of potentially useful drive sg_give_next_adr() iterates over the set of potentially useful drive
address strings. address strings.
@ -38,11 +53,6 @@ Send feedback to libburn-hackers@pykix.org .
Hint: You should also look into sg-freebsd-port.c, which is a younger and Hint: You should also look into sg-freebsd-port.c, which is a younger and
in some aspects more straightforward implementation of this interface. in some aspects more straightforward implementation of this interface.
Other source modules where to expect OS dependencies (look for "__FreeBSD__"):
cleanup.c signal_list : add or delete as described in your man 7 signal
transport.h struct burn_drive : OS dependent i/o attributes
BUFFER_SIZE maximum size for a (SCSI) i/o transaction
*/ */

View File

@ -1,5 +1,9 @@
/* ts A61013 : It would be nice if autotools could do that job */ /* sg.c
Switcher for operating system dependent transport level modules of libburn.
Copyright (C) 2006 Thomas Schmitt <scdbackup@gmx.net>, provided under GPL
*/
#ifdef __FreeBSD__ #ifdef __FreeBSD__

View File

@ -3,30 +3,13 @@
#ifndef __SG #ifndef __SG
#define __SG #define __SG
#ifdef __FreeBSD__
/* To hold all state information of BSD device enumeration #include "os.h"
which are now local in sg_enumerate() . So that sg_give_next_adr()
can work in BSD and sg_enumerate() can use it. */
struct burn_drive_enumeration_state {
union ccb ccb;
int bufsize, fd;
unsigned int i;
int skip_device;
};
typedef struct burn_drive_enumeration_state burn_drive_enumerator_t;
#else /* __FreeBSD__ */
/* <<< just for testing the C syntax */ /* see os.h for name of particular os-*.h where this is defined */
struct burn_drive_enumeration_state { BURN_OS_DEFINE_DRIVE_ENUMERATOR_T
int dummy;
};
typedef struct burn_drive_enumeration_state burn_drive_enumerator_tX;
typedef int burn_drive_enumerator_t;
#endif /* ! __FreeBSD__ */
struct burn_drive; struct burn_drive;
struct command; struct command;

View File

@ -4,20 +4,16 @@
#define __TRANSPORT #define __TRANSPORT
#include "libburn.h" #include "libburn.h"
#include "os.h"
#include <pthread.h> #include <pthread.h>
/* sg data structures */ /* sg data structures */
#include <sys/types.h> #include <sys/types.h>
#ifdef __FreeBSD__
#define BUFFER_SIZE 65536/2 /* see os.h for name of particular os-*.h where this is defined */
#define BUFFER_SIZE BURN_OS_TRANSPORT_BUFFER_SIZE
#else /* __FreeBSD__ */
#define BUFFER_SIZE 65536
#endif /* ! __FreeBSD__ */
enum transfer_direction enum transfer_direction
{ TO_DRIVE, FROM_DRIVE, NO_TRANSFER }; { TO_DRIVE, FROM_DRIVE, NO_TRANSFER };
@ -107,15 +103,10 @@ struct burn_drive
int lun; int lun;
char *devname; char *devname;
#if defined(__FreeBSD__)
struct cam_device* cam;
#else
int fd;
/* ts A60926 : trying to lock against growisofs /dev/srN, /dev/scdN */ /* see os.h for name of particular os-*.h where this is defined */
int sibling_count; BURN_OS_TRANSPORT_DRIVE_ELEMENTS
int sibling_fds[LIBBURN_SG_MAX_SIBLINGS];
#endif
/* ts A60904 : ticket 62, contribution by elmom */ /* ts A60904 : ticket 62, contribution by elmom */
/** /**