622 lines
20 KiB
C++
622 lines
20 KiB
C++
/*
|
|
* Interactive disassembler (IDA).
|
|
* Copyright (c) 1990-2020 Hex-Rays
|
|
* ALL RIGHTS RESERVED.
|
|
*
|
|
*/
|
|
|
|
#ifndef _DISKIO_HPP
|
|
#define _DISKIO_HPP
|
|
|
|
#include <stdio.h>
|
|
|
|
/*! \file diskio.hpp
|
|
|
|
\brief File I/O functions for IDA
|
|
|
|
You should not use standard C file I/O functions in modules.
|
|
Use functions from this header, pro.h and fpro.h instead.
|
|
|
|
This file also declares a call_system() function.
|
|
*/
|
|
|
|
//-------------------------------------------------------------------------
|
|
// S E A R C H F O R F I L E S
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
/// Get IDA directory (if subdir==NULL)
|
|
/// or the specified subdirectory (see \ref SUBDIR)
|
|
|
|
idaman THREAD_SAFE const char *ida_export idadir(const char *subdir);
|
|
|
|
|
|
/// Search for IDA system file.
|
|
/// This function searches for a file in:
|
|
/// -# each directory specified by %IDAUSR%
|
|
/// -# ida directory [+ subdir]
|
|
/// and returns the first match.
|
|
/// \param[out] buf buffer for file name
|
|
/// \param bufsize size of output buffer
|
|
/// \param filename name of file to search
|
|
/// \param subdir if specified, the file is looked for in the specified subdirectory
|
|
/// of the ida directory first (see \ref SUBDIR)
|
|
/// \return NULL if not found, otherwise a pointer to full file name.
|
|
|
|
idaman THREAD_SAFE char *ida_export getsysfile(
|
|
char *buf,
|
|
size_t bufsize,
|
|
const char *filename,
|
|
const char *subdir);
|
|
|
|
/// \defgroup SUBDIR IDA subdirectories
|
|
/// Passed as 'subdir' parameter to idadir(), getsysfile(), and others.
|
|
//@{
|
|
#define CFG_SUBDIR "cfg"
|
|
#define IDC_SUBDIR "idc"
|
|
#define IDS_SUBDIR "ids"
|
|
#define IDP_SUBDIR "procs"
|
|
#define LDR_SUBDIR "loaders"
|
|
#define SIG_SUBDIR "sig"
|
|
#define TIL_SUBDIR "til"
|
|
#define PLG_SUBDIR "plugins"
|
|
#define THM_SUBDIR "themes"
|
|
//@}
|
|
|
|
/// Get user ida related directory.
|
|
/// \code
|
|
/// - if $IDAUSR is defined:
|
|
/// - the first element in $IDAUSR
|
|
/// - else
|
|
/// - default user directory ($HOME/.idapro or %APPDATA%Hex-Rays/IDA Pro)
|
|
/// \endcode
|
|
|
|
idaman THREAD_SAFE const char *ida_export get_user_idadir(void);
|
|
|
|
|
|
/// Get list of directories in which to find a specific IDA resource
|
|
/// (see \ref SUBDIR). The order of the resulting list is as follows:
|
|
/// \code
|
|
/// - [$IDAUSR/subdir (0..N entries)]
|
|
/// - $IDADIR/subdir
|
|
/// \endcode
|
|
/// \param[out] dirs output vector for directory names
|
|
/// \param subdir name of the resource to list
|
|
/// \param flags \ref IDA_SUBDIR_ bits
|
|
/// \return number of directories appended to 'dirs'
|
|
|
|
idaman THREAD_SAFE int ida_export get_ida_subdirs(qstrvec_t *dirs, const char *subdir, int flags=0);
|
|
|
|
/// \defgroup IDA_SUBDIR_ Subdirectory modification flags
|
|
/// Passed as 'flags' parameter to get_ida_subdirs()
|
|
//@{
|
|
#define IDA_SUBDIR_IDP 0x0001 ///< append the processor name as a subdirectory
|
|
#define IDA_SUBDIR_IDADIR_FIRST 0x0002 ///< $IDADIR/subdir will be first, not last
|
|
#define IDA_SUBDIR_ONLY_EXISTING 0x0004 ///< only existing directories will be present
|
|
//@}
|
|
|
|
|
|
/// Get a folder location by CSIDL (see \ref CSIDL).
|
|
/// Path should be of at least MAX_PATH size
|
|
|
|
idaman THREAD_SAFE bool ida_export get_special_folder(char *buf, size_t bufsize, int csidl);
|
|
|
|
/// \defgroup CSIDL Common CSIDLs
|
|
/// Passed as 'csidl' parameter to get_special_folder()
|
|
//@{
|
|
#ifndef CSIDL_APPDATA
|
|
#define CSIDL_APPDATA 0x001a
|
|
#endif
|
|
#ifndef CSIDL_LOCAL_APPDATA
|
|
#define CSIDL_LOCAL_APPDATA 0x001c
|
|
#endif
|
|
#ifndef CSIDL_PROGRAM_FILES
|
|
#define CSIDL_PROGRAM_FILES 0x0026
|
|
#endif
|
|
#ifndef CSIDL_PROGRAM_FILES_COMMON
|
|
#define CSIDL_PROGRAM_FILES_COMMON 0x002b
|
|
#endif
|
|
#ifndef CSIDL_PROGRAM_FILESX86
|
|
#define CSIDL_PROGRAM_FILESX86 0x002a
|
|
#endif
|
|
//@}
|
|
|
|
/// Enumerate files in the specified directory.
|
|
/// \param[out] answer buffer to contain the file name for which
|
|
/// file_enumerator_t::visit_file returns non-zero value
|
|
/// (may be NULL)
|
|
/// \param answer_size size of 'answer'
|
|
/// \param path directory to enumerate files in
|
|
/// \param fname mask of file names to enumerate
|
|
/// \param fv file_enumerator_t::visit_file function called for each file
|
|
/// - file: full file name (with path)
|
|
/// - if returns non-zero value, the enumeration
|
|
/// is stopped and the return code is
|
|
/// is returned to the caller.
|
|
/// the callback function
|
|
/// \return zero or the code returned by 'func'
|
|
|
|
struct file_enumerator_t
|
|
{
|
|
virtual int visit_file(const char *file) = 0;
|
|
DEFINE_VIRTUAL_DTOR(file_enumerator_t)
|
|
};
|
|
|
|
idaman THREAD_SAFE int ida_export enumerate_files2(
|
|
char *answer,
|
|
size_t answer_size,
|
|
const char *path,
|
|
const char *fname,
|
|
file_enumerator_t &fv);
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
// O P E N / R E A D / W R I T E / C L O S E F I L E S
|
|
//-------------------------------------------------------------------------
|
|
|
|
/// \name Open/Read/Write/Close Files
|
|
/// There are two sets of "open file" functions.
|
|
/// The first set tries to open a file and returns success or failure.
|
|
/// The second set is "open or die": if the file cannot be opened
|
|
/// then the function will display an error message and exit.
|
|
//@{
|
|
|
|
/// Open a new file for write in text mode, deny write.
|
|
/// If a file exists, it will be removed.
|
|
/// \return NULL if failure
|
|
|
|
idaman THREAD_SAFE FILE *ida_export fopenWT(const char *file);
|
|
|
|
|
|
/// Open a new file for write in binary mode, deny read/write.
|
|
/// If a file exists, it will be removed.
|
|
/// \return NULL if failure
|
|
|
|
idaman THREAD_SAFE FILE *ida_export fopenWB(const char *file);
|
|
|
|
|
|
/// Open a file for read in text mode, deny none.
|
|
/// \return NULL if failure
|
|
|
|
idaman THREAD_SAFE FILE *ida_export fopenRT(const char *file);
|
|
|
|
|
|
/// Open a file for read in binary mode, deny none.
|
|
/// \return NULL if failure
|
|
|
|
idaman THREAD_SAFE FILE *ida_export fopenRB(const char *file);
|
|
|
|
|
|
/// Open a file for read/write in binary mode, deny write.
|
|
/// \return NULL if failure
|
|
|
|
idaman THREAD_SAFE FILE *ida_export fopenM(const char *file);
|
|
|
|
|
|
/// Open a file for append in text mode, deny none.
|
|
/// \return NULL if failure
|
|
|
|
idaman THREAD_SAFE FILE *ida_export fopenA(const char *file);
|
|
|
|
|
|
/// Open a file for read in binary mode or die, deny none.
|
|
/// If a file cannot be opened, this function displays a message and exits.
|
|
|
|
idaman THREAD_SAFE FILE *ida_export openR(const char *file);
|
|
|
|
|
|
/// Open a file for read in text mode or die, deny none.
|
|
/// If a file cannot be opened, this function displays a message and exits.
|
|
|
|
idaman THREAD_SAFE FILE *ida_export openRT(const char *file);
|
|
|
|
|
|
/// Open a file for read/write in binary mode or die, deny write.
|
|
/// If a file cannot be opened, this function displays a message and exits.
|
|
|
|
idaman THREAD_SAFE FILE *ida_export openM(const char *file);
|
|
|
|
|
|
//@}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// F I L E S I Z E / D I S K S P A C E
|
|
//-------------------------------------------------------------------------
|
|
|
|
/// Get length of file in bytes.
|
|
/// \param fp pointer to file
|
|
|
|
idaman THREAD_SAFE uint64 ida_export qfsize(FILE *fp);
|
|
|
|
|
|
/// Change size of file or die.
|
|
/// If an error occurs, this function displays a message and exits.
|
|
/// \param fp pointer to file
|
|
/// \param size new size of file
|
|
|
|
idaman THREAD_SAFE void ida_export echsize(FILE *fp, uint64 size);
|
|
|
|
|
|
/// Get free disk space in bytes.
|
|
/// \param path name of any directory on the disk to get information about
|
|
|
|
idaman THREAD_SAFE uint64 ida_export get_free_disk_space(const char *path);
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
// I / O P O R T D E F I N I T I O N S F I L E
|
|
//-------------------------------------------------------------------------
|
|
/// Describes an I/O port bit
|
|
struct ioport_bit_t
|
|
{
|
|
qstring name; ///< name of the bit
|
|
qstring cmt; ///< comment
|
|
};
|
|
DECLARE_TYPE_AS_MOVABLE(ioport_bit_t);
|
|
typedef qvector<ioport_bit_t> ioport_bits_t;
|
|
|
|
/// Describes an I/O port
|
|
struct ioport_t
|
|
{
|
|
ea_t address; ///< address of the port
|
|
qstring name; ///< name of the port
|
|
qstring cmt; ///< comment
|
|
ioport_bits_t bits; ///< bit names
|
|
void *userdata; ///< arbitrary data. initialized to NULL.
|
|
|
|
ioport_t()
|
|
: address(0), userdata(NULL)
|
|
{
|
|
}
|
|
};
|
|
DECLARE_TYPE_AS_MOVABLE(ioport_t);
|
|
typedef qvector<ioport_t> ioports_t;
|
|
|
|
/// Read i/o port definitions from a config file.
|
|
///
|
|
/// Each device definition in the input file begins with a line like this:
|
|
///
|
|
/// \v{.devicename}
|
|
///
|
|
/// After it go the port definitions in this format:
|
|
///
|
|
/// \v{portname address}
|
|
///
|
|
/// The bit definitions (optional) are represented like this:
|
|
///
|
|
/// \v{portname.bitname bitnumber}
|
|
///
|
|
/// Lines beginning with a space are ignored.
|
|
/// comment lines should be started with ';' character.
|
|
///
|
|
/// The default device is specified at the start of the file:
|
|
///
|
|
/// \v{.default device_name}
|
|
///
|
|
/// \note It is permissible to have a symbol mapped to several addresses
|
|
/// but all addresses must be unique.
|
|
/// \param[out] ports output vector
|
|
/// \param device contains device name to load. If default_device[0] == 0
|
|
/// then the default device is determined by .default directive
|
|
/// in the config file.
|
|
/// \param file config file name
|
|
/// \param callback callback to call when the input line can't be parsed normally.
|
|
/// - line: input line to parse
|
|
/// - returns error message. if NULL, then the line is parsed ok.
|
|
/// \return -1 on error or size of vector
|
|
|
|
idaman THREAD_SAFE ssize_t ida_export read_ioports(
|
|
ioports_t *ports,
|
|
qstring *device,
|
|
const char *file,
|
|
const char *(idaapi *callback)(
|
|
const ioports_t &ports,
|
|
const char *line)=NULL);
|
|
|
|
|
|
struct ioports_fallback_t
|
|
{
|
|
// returns success or fills ERRBUF with an error message
|
|
virtual bool handle(qstring *errbuf, const ioports_t &ports, const char *line) = 0;
|
|
};
|
|
|
|
idaman THREAD_SAFE ssize_t ida_export read_ioports2(
|
|
ioports_t *ports,
|
|
qstring *device,
|
|
const char *file,
|
|
ioports_fallback_t *callback=nullptr);
|
|
|
|
|
|
/// Allow the user to choose the ioport device.
|
|
/// \param[in,out] device in: contains default device name. If default_device[0] == 0
|
|
/// then the default device is determined by .default directive
|
|
/// in the config file.
|
|
/// out: the selected device name
|
|
/// \param file config file name
|
|
/// \param parse_params if present (non NULL), then defines a callback which
|
|
/// will be called for all lines not starting with a dot (.)
|
|
/// This callback may parse these lines are prepare a simple
|
|
/// processor parameter string. This string will be displayed
|
|
/// along with the device name.
|
|
/// If it returns #IOPORT_SKIP_DEVICE, then the current
|
|
/// device will not be included in the list.
|
|
/// \retval true the user selected a device, its name is in 'device'
|
|
/// \retval false the selection was cancelled. if device=="NONE" upon return,
|
|
/// then no devices were found in the configuration file
|
|
|
|
idaman THREAD_SAFE bool ida_export choose_ioport_device(
|
|
qstring *_device,
|
|
const char *file,
|
|
const char *(idaapi *parse_params)(
|
|
qstring *buf,
|
|
const char *line)=NULL);
|
|
|
|
struct choose_ioport_parser_t
|
|
{
|
|
/// \retval true and fill PARAM with a displayed string
|
|
/// \retval false and empty PARAM to skip the current device
|
|
/// \retval false and fill PARAM with an error message
|
|
virtual bool parse(qstring *param, const char *line) = 0;
|
|
};
|
|
|
|
idaman THREAD_SAFE bool ida_export choose_ioport_device2(
|
|
qstring *_device,
|
|
const char *file,
|
|
choose_ioport_parser_t *parse_params);
|
|
|
|
/// See 'parse_params' parameter to choose_ioport_device()
|
|
#define IOPORT_SKIP_DEVICE ((const char *)(-1))
|
|
|
|
|
|
/// Find ioport in the array of ioports
|
|
|
|
idaman THREAD_SAFE const ioport_t *ida_export find_ioport(const ioports_t &ports, ea_t address);
|
|
|
|
|
|
/// Find ioport bit in the array of ioports
|
|
|
|
idaman THREAD_SAFE const ioport_bit_t *ida_export find_ioport_bit(const ioports_t &ports, ea_t address, size_t bit);
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
// S Y S T E M S P E C I F I C C A L L S
|
|
//-------------------------------------------------------------------------
|
|
|
|
/// Execute a operating system command.
|
|
/// This function suspends the interface (Tvision), runs the command
|
|
/// and redraws the screen.
|
|
/// \param command command to execute. If NULL, an interactive shell is activated
|
|
/// \return the error code returned by system() call
|
|
|
|
idaman THREAD_SAFE int ida_export call_system(const char *command);
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
// L O A D E R I N P U T S O U R C E F U N C T I O N S
|
|
//-------------------------------------------------------------------------
|
|
|
|
/// \name Loader Input Source
|
|
/// Starting with v4.8 IDA can load and run remote files.
|
|
/// In order to do that, we replace the FILE* in the loader modules
|
|
/// with an abstract input source (linput_t). The source might be linked to
|
|
/// a local or remote file.
|
|
//@{
|
|
|
|
class linput_t; ///< loader input source
|
|
|
|
|
|
/// linput types
|
|
enum linput_type_t
|
|
{
|
|
LINPUT_NONE, ///< invalid linput
|
|
LINPUT_LOCAL, ///< local file
|
|
LINPUT_RFILE, ///< remote file (\dbg{open_file}, \dbg{read_file})
|
|
LINPUT_PROCMEM, ///< debugged process memory (read_dbg_memory())
|
|
LINPUT_GENERIC ///< generic linput
|
|
};
|
|
|
|
|
|
/// Read the input source.
|
|
/// If failed, inform the user and ask him if he wants to continue.
|
|
/// If he does not, this function will not return (loader_failure() will be called).
|
|
/// This function may be called only from loaders!
|
|
|
|
idaman void ida_export lread(linput_t *li, void *buf, size_t size);
|
|
|
|
|
|
/// Read the input source.
|
|
/// \return number of read bytes or -1
|
|
|
|
idaman ssize_t ida_export qlread(linput_t *li, void *buf, size_t size);
|
|
|
|
|
|
/// Read one line from the input source.
|
|
/// \return NULL if failure, otherwise 's'
|
|
|
|
idaman char *ida_export qlgets(char *s, size_t len, linput_t *li);
|
|
|
|
|
|
/// Read one character from the input source.
|
|
/// \return EOF if failure, otherwise the read character
|
|
|
|
idaman int ida_export qlgetc(linput_t *li);
|
|
|
|
|
|
/// Read multiple bytes and swap if necessary.
|
|
/// \param li input file
|
|
/// \param buf pointer to output buffer
|
|
/// \param size number of bytes to read
|
|
/// \param mf big endian?
|
|
/// \retval 0 ok
|
|
/// \retval -1 failure
|
|
|
|
idaman int ida_export lreadbytes(linput_t *li, void *buf, size_t size, bool mf);
|
|
|
|
/// Helper to define lread2bytes(), lread4bytes(), etc
|
|
#define DEF_LREADBYTES(read, type, size) \
|
|
/*! \brief Read a value from linput - also see lreadbytes() */ \
|
|
inline int idaapi read(linput_t *li, type *res, bool mf) \
|
|
{ return lreadbytes(li, res, size, mf); }
|
|
DEF_LREADBYTES(lread2bytes, int16, 2)
|
|
DEF_LREADBYTES(lread2bytes, uint16, 2)
|
|
DEF_LREADBYTES(lread4bytes, int32, 4)
|
|
DEF_LREADBYTES(lread4bytes, uint32, 4)
|
|
DEF_LREADBYTES(lread8bytes, int64, 8)
|
|
DEF_LREADBYTES(lread8bytes, uint64, 8)
|
|
#undef DEF_LREADBYTES
|
|
|
|
|
|
/// Read a zero-terminated string from the input.
|
|
/// If fpos == -1 then no seek will be performed.
|
|
|
|
idaman char *ida_export qlgetz(
|
|
linput_t *li,
|
|
int64 fpos,
|
|
char *buf,
|
|
size_t bufsize);
|
|
|
|
|
|
/// Get the input source size
|
|
|
|
idaman int64 ida_export qlsize(linput_t *li);
|
|
|
|
|
|
/// Set input source position.
|
|
/// \return the new position (not 0 as fseek!)
|
|
|
|
idaman qoff64_t ida_export qlseek(linput_t *li, qoff64_t pos, int whence=SEEK_SET);
|
|
|
|
|
|
/// Get input source position
|
|
|
|
inline qoff64_t idaapi qltell(linput_t *li) { return qlseek(li, 0, SEEK_CUR); }
|
|
|
|
|
|
/// Open loader input
|
|
|
|
idaman linput_t *ida_export open_linput(const char *file, bool remote);
|
|
|
|
|
|
/// Close loader input
|
|
|
|
idaman THREAD_SAFE void ida_export close_linput(linput_t *li);
|
|
|
|
|
|
/// Get FILE* from the input source.
|
|
/// If the input source is linked to a remote file, then return NULL.
|
|
/// Otherwise return the underlying FILE*
|
|
/// Please do not use this function if possible.
|
|
|
|
idaman THREAD_SAFE FILE *ida_export qlfile(linput_t *li);
|
|
|
|
|
|
/// Convert FILE * to input source.
|
|
/// Used for temporary linput_t objects - call unmake_linput() to free
|
|
/// the slot after the use.
|
|
|
|
idaman THREAD_SAFE linput_t *ida_export make_linput(FILE *fp);
|
|
|
|
/// Free an linput_t object (also see make_linput())
|
|
|
|
idaman THREAD_SAFE void ida_export unmake_linput(linput_t *li);
|
|
|
|
|
|
/// Generic linput class - may be used to create a linput_t instance for
|
|
/// any data source
|
|
struct generic_linput_t
|
|
{
|
|
/// \name Warning
|
|
/// The following two fields must be filled before calling create_generic_linput()
|
|
//@{
|
|
uint64 filesize; ///< input file size
|
|
uint32 blocksize; ///< preferred block size to work with
|
|
///< read/write sizes will be in multiples of this number.
|
|
///< for example, 4096 is a nice value
|
|
///< blocksize 0 means that the filesize is unknown.
|
|
///< the internal cache will be disabled in this case.
|
|
///< also, seeks from the file end will fail.
|
|
///< blocksize=-1 means error.
|
|
//@}
|
|
virtual ssize_t idaapi read(qoff64_t off, void *buffer, size_t nbytes) = 0;
|
|
DEFINE_VIRTUAL_DTOR(generic_linput_t)
|
|
};
|
|
|
|
/// Create a generic linput
|
|
/// \param gl linput description.
|
|
/// this object will be destroyed by close_linput()
|
|
/// using "delete gl;"
|
|
|
|
idaman THREAD_SAFE linput_t *ida_export create_generic_linput(generic_linput_t *gl);
|
|
|
|
/// Trivial memory linput
|
|
|
|
idaman THREAD_SAFE linput_t *ida_export create_bytearray_linput(const uchar *start, size_t size);
|
|
|
|
|
|
/// Create a linput for process memory.
|
|
/// This linput will use read_dbg_memory() to read data.
|
|
/// \param start starting address of the input
|
|
/// \param size size of the memory area to represent as linput
|
|
/// if unknown, may be passed as 0
|
|
|
|
idaman linput_t *ida_export create_memory_linput(ea_t start, asize_t size);
|
|
|
|
/// Get linput type
|
|
|
|
inline THREAD_SAFE linput_type_t idaapi get_linput_type(linput_t *li)
|
|
{
|
|
return *(linput_type_t *)li;
|
|
}
|
|
|
|
/// Object that will free an linput_t at destruction-time
|
|
typedef janitor_t<linput_t*> linput_janitor_t;
|
|
/// Free the linput_t
|
|
template <> inline linput_janitor_t::~janitor_t()
|
|
{
|
|
close_linput(resource);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
/// Helper class - adapts linput to be used in extract_... functions
|
|
/// as a data supplier (see kernwin.hpp)
|
|
class linput_buffer_t
|
|
{
|
|
public:
|
|
linput_buffer_t(linput_t *linput, int64 size=0): li(linput), lsize(size) {}
|
|
ssize_t read(void *buf, size_t n)
|
|
{
|
|
return qlread(li, buf, n);
|
|
}
|
|
bool eof()
|
|
{
|
|
if ( lsize == 0 )
|
|
lsize = qlsize(li);
|
|
return qltell(li) >= lsize;
|
|
}
|
|
protected:
|
|
linput_t *li;
|
|
private:
|
|
int64 lsize;
|
|
};
|
|
|
|
//@}
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
#ifndef NO_OBSOLETE_FUNCS
|
|
idaman DEPRECATED THREAD_SAFE FILE *ida_export ecreate(const char *file);
|
|
idaman DEPRECATED THREAD_SAFE void ida_export eclose(FILE *fp);
|
|
idaman DEPRECATED THREAD_SAFE void ida_export eread(FILE *fp, void *buf, size_t size);
|
|
idaman DEPRECATED THREAD_SAFE void ida_export ewrite(FILE *fp, const void *buf, size_t size);
|
|
idaman DEPRECATED THREAD_SAFE void ida_export eseek(FILE *fp, qoff64_t pos);
|
|
idaman DEPRECATED THREAD_SAFE int ida_export enumerate_files(
|
|
char *answer,
|
|
size_t answer_size,
|
|
const char *path,
|
|
const char *fname,
|
|
int (idaapi*func)(const char *file,void *ud),
|
|
void *ud=NULL);
|
|
#endif
|
|
#endif // _DISKIO_HPP
|