2005-09-20 21:26:39 +08:00
/** \file common.h
Prototypes for various functions , mostly string utilities , that are used by most parts of fish .
*/
2005-10-04 23:11:39 +08:00
# ifndef FISH_COMMON_H
2005-10-24 23:26:25 +08:00
/**
Header guard
*/
2005-10-04 23:11:39 +08:00
# define FISH_COMMON_H
2006-02-06 21:45:32 +08:00
# include <stdlib.h>
# include <stdio.h>
2005-10-04 23:11:39 +08:00
# include <wchar.h>
# include <termios.h>
2011-12-27 11:18:46 +08:00
# include <string>
# include <sstream>
# include <vector>
2005-10-04 23:11:39 +08:00
2011-12-27 11:18:46 +08:00
# include <errno.h>
2005-10-04 23:11:39 +08:00
# include "util.h"
2005-09-20 21:26:39 +08:00
2011-12-27 11:18:46 +08:00
/* Common string type */
typedef std : : wstring wcstring ;
2005-09-20 21:26:39 +08:00
/**
2005-11-03 00:49:13 +08:00
Maximum number of bytes used by a single utf - 8 character
2005-09-20 21:26:39 +08:00
*/
# define MAX_UTF8_BYTES 6
2006-01-28 10:03:29 +08:00
/**
This is in the unicode private use area .
*/
2006-02-14 19:35:14 +08:00
# define ENCODE_DIRECT_BASE 0xf100
2006-01-28 10:03:29 +08:00
2006-05-27 00:46:38 +08:00
/**
Highest legal ascii value
*/
# define ASCII_MAX 127u
/**
Highest legal 16 - bit unicode value
*/
# define UCS2_MAX 0xffffu
/**
Highest legal byte value
*/
# define BYTE_MAX 0xffu
2008-01-14 00:47:47 +08:00
/**
Escape special fish syntax characters like the semicolon
2007-10-06 18:51:31 +08:00
*/
2007-01-19 00:02:46 +08:00
# define UNESCAPE_SPECIAL 1
2008-01-14 00:47:47 +08:00
/**
2007-10-06 18:51:31 +08:00
Allow incomplete escape sequences
*/
2007-01-19 00:02:46 +08:00
# define UNESCAPE_INCOMPLETE 2
2007-10-06 18:51:31 +08:00
/**
Escape all characters , including magic characters like the semicolon
*/
# define ESCAPE_ALL 1
/**
Do not try to use ' simplified ' quoted escapes , and do not use empty quotes as the empty string
*/
# define ESCAPE_NO_QUOTED 2
2010-09-18 09:51:16 +08:00
/**
2011-12-27 11:18:46 +08:00
Helper macro for errors
*/
# define VOMIT_ON_FAILURE(a) do { if (0 != (a)) { int err = errno; fprintf(stderr, "%s failed on line %d in file %s: %d (%s)\n", #a, __LINE__, __FILE__, err, strerror(err)); abort(); }} while (0)
/**
2005-11-03 00:49:13 +08:00
Save the shell mode on startup so we can restore them on exit
*/
2011-12-27 11:18:46 +08:00
extern struct termios shell_modes ;
2005-09-20 21:26:39 +08:00
/**
2005-11-03 00:49:13 +08:00
The character to use where the text has been truncated . Is an
ellipsis on unicode system and a $ on other systems .
2005-09-20 21:26:39 +08:00
*/
extern wchar_t ellipsis_char ;
2005-09-25 03:31:17 +08:00
/**
2006-10-26 18:22:53 +08:00
The verbosity level of fish . If a call to debug has a severity
level higher than \ c debug_level , it will not be printed .
2005-09-25 03:31:17 +08:00
*/
extern int debug_level ;
2005-09-20 21:26:39 +08:00
/**
Profiling flag . True if commands should be profiled .
*/
extern char * profile ;
/**
Name of the current program . Should be set at startup . Used by the
debug function .
*/
2011-12-27 11:18:46 +08:00
extern const wchar_t * program_name ;
2005-10-25 17:39:45 +08:00
2006-06-21 08:48:36 +08:00
/**
This macro is used to check that an input argument is not null . It
is a bit lika a non - fatal form of assert . Instead of exit - ing on
failiure , the current function is ended at once . The second
2006-11-11 18:54:52 +08:00
parameter is the return value of the current function on failiure .
2006-06-21 08:48:36 +08:00
*/
2006-10-30 05:09:11 +08:00
# define CHECK( arg, retval ) \
2006-06-21 08:48:36 +08:00
if ( ! ( arg ) ) \
{ \
2006-11-17 22:58:25 +08:00
debug ( 0 , \
_ ( L " function %s called with null value for argument %s. " ) , \
2006-06-21 08:48:36 +08:00
__func__ , \
2006-11-17 22:58:25 +08:00
# arg ); \
bugreport ( ) ; \
2007-01-20 10:36:49 +08:00
show_stackframe ( ) ; \
2006-06-21 08:48:36 +08:00
return retval ; \
2006-10-30 05:09:11 +08:00
}
2006-06-21 08:48:36 +08:00
2007-01-20 10:36:49 +08:00
/**
2007-01-21 23:03:41 +08:00
Pause for input , then exit the program . If supported , print a backtrace first .
2007-01-20 10:36:49 +08:00
*/
2009-02-05 06:43:10 +08:00
# define FATAL_EXIT() \
{ \
int exit_read_count ; char exit_read_buff ; \
show_stackframe ( ) ; \
exit_read_count = read ( 0 , & exit_read_buff , 1 ) ; \
exit ( 1 ) ; \
} \
2011-12-27 11:18:46 +08:00
2007-01-20 10:36:49 +08:00
2006-07-03 18:39:57 +08:00
/**
2006-11-11 18:54:52 +08:00
Exit program at once , leaving an error message about running out of memory .
2006-07-03 18:39:57 +08:00
*/
2006-10-30 05:09:11 +08:00
# define DIE_MEM() \
2006-07-03 18:39:57 +08:00
{ \
2006-10-30 05:09:11 +08:00
fwprintf ( stderr , \
L " fish: Out of memory on line %d of file %s, shutting down fish \n " , \
__LINE__ , \
__FILE__ ) ; \
2007-01-21 23:03:41 +08:00
FATAL_EXIT ( ) ; \
2006-10-30 05:09:11 +08:00
}
/**
2006-11-11 18:54:52 +08:00
Check if signals are blocked . If so , print an error message and
return from the function performing this check .
2006-10-30 05:09:11 +08:00
*/
2006-11-17 22:58:25 +08:00
# define CHECK_BLOCK( retval ) \
2006-10-30 05:09:11 +08:00
if ( signal_is_blocked ( ) ) \
{ \
debug ( 0 , \
2006-11-17 22:58:25 +08:00
_ ( L " function %s called while blocking signals. " ) , \
__func__ ) ; \
bugreport ( ) ; \
2007-01-20 10:36:49 +08:00
show_stackframe ( ) ; \
2006-11-17 22:58:25 +08:00
return retval ; \
2006-10-30 05:09:11 +08:00
}
2011-12-27 11:18:46 +08:00
2006-07-20 06:55:49 +08:00
/**
Shorthand for wgettext call
*/
2011-12-27 11:18:46 +08:00
# define _(wstr) wgettext((const wchar_t *)wstr)
2006-07-20 06:55:49 +08:00
/**
2006-11-11 18:54:52 +08:00
Noop , used to tell xgettext that a string should be translated ,
2011-12-27 11:18:46 +08:00
even though it is not directly sent to wgettext .
2006-07-20 06:55:49 +08:00
*/
# define N_(wstr) wstr
2008-01-14 00:47:47 +08:00
/**
Check if the specified stringelement is a part of the specified string list
*/
2011-12-27 11:18:46 +08:00
# define contains( str,... ) contains_internal( str, __VA_ARGS__, NULL )
2008-01-14 00:47:47 +08:00
/**
Concatenate all the specified strings into a single newly allocated one
*/
2011-12-27 11:18:46 +08:00
# define wcsdupcat( str,... ) wcsdupcat_internal( str, __VA_ARGS__, NULL )
2007-04-17 05:40:41 +08:00
2008-01-14 00:47:47 +08:00
/**
2007-01-21 23:01:14 +08:00
Print a stack trace to stderr
*/
2007-01-20 10:36:49 +08:00
void show_stackframe ( ) ;
2005-09-20 21:26:39 +08:00
/**
2005-11-03 00:49:13 +08:00
Take an array_list_t containing wide strings and converts them to a
2006-02-06 23:18:17 +08:00
single null - terminated wchar_t * * . The array is allocated using
2007-01-21 23:01:14 +08:00
malloc , and needs to be fred ' s by the caller .
2005-09-20 21:26:39 +08:00
*/
2006-02-09 23:50:20 +08:00
wchar_t * * list_to_char_arr ( array_list_t * l ) ;
2005-09-20 21:26:39 +08:00
/**
Read a line from the stream f into the buffer buff of length len . If
buff is to small , it will be reallocated , and both buff and len will
be updated to reflect this . Returns the number of bytes read or - 1
2011-12-27 11:18:46 +08:00
on failiure .
2005-09-20 21:26:39 +08:00
If the carriage return character is encountered , it is
ignored . fgetws ( ) considers the line to end if reading the file
results in either a newline ( L ' \n ' ) character , the null ( L ' \ \ 0 ' )
character or the end of file ( WEOF ) character .
*/
int fgetws2 ( wchar_t * * buff , int * len , FILE * f ) ;
/**
2006-06-15 18:37:06 +08:00
Sorts an array_list of wide strings according to the
wcsfilecmp - function from the util library
2005-09-20 21:26:39 +08:00
*/
void sort_list ( array_list_t * comp ) ;
2011-12-27 11:18:46 +08:00
void sort_strings ( std : : vector < wcstring > & strings ) ;
2005-09-20 21:26:39 +08:00
/**
2005-11-03 00:49:13 +08:00
Returns a newly allocated wide character string equivalent of the
specified multibyte character string
2006-06-17 21:07:08 +08:00
This function encodes illegal character sequences in a reversible
way using the private use area .
2005-09-20 21:26:39 +08:00
*/
wchar_t * str2wcs ( const char * in ) ;
2006-06-17 21:07:08 +08:00
2011-12-27 11:18:46 +08:00
/**
Returns a newly allocated wide character string equivalent of the
specified multibyte character string
This function encodes illegal character sequences in a reversible
way using the private use area .
*/
wcstring str2wcstring ( const char * in ) ;
2006-06-17 21:07:08 +08:00
/**
Converts the narrow character string \ c in into it ' s wide
equivalent , stored in \ c out . \ c out must have enough space to fit
the entire string .
This function encodes illegal character sequences in a reversible
way using the private use area .
*/
2006-02-08 22:58:47 +08:00
wchar_t * str2wcs_internal ( const char * in , wchar_t * out ) ;
2005-09-20 21:26:39 +08:00
/**
2005-11-03 00:49:13 +08:00
Returns a newly allocated multibyte character string equivalent of
the specified wide character string
2006-06-17 21:07:08 +08:00
This function decodes illegal character sequences in a reversible
way using the private use area .
2005-09-20 21:26:39 +08:00
*/
char * wcs2str ( const wchar_t * in ) ;
2011-12-27 11:18:46 +08:00
std : : string wcs2string ( const wcstring & input ) ;
void assert_is_main_thread ( const char * who ) ;
# define ASSERT_IS_MAIN_THREAD_TRAMPOLINE(x) assert_is_main_thread(x)
# define ASSERT_IS_MAIN_THREAD() ASSERT_IS_MAIN_THREAD_TRAMPOLINE(__FUNCTION__)
void assert_is_background_thread ( const char * who ) ;
# define ASSERT_IS_BACKGROUND_THREAD_TRAMPOLINE(x) assert_is_background_thread(x)
# define ASSERT_IS_BACKGROUND_THREAD() ASSERT_IS_BACKGROUND_THREAD_TRAMPOLINE(__FUNCTION__)
2005-09-20 21:26:39 +08:00
2006-06-17 21:07:08 +08:00
/**
Converts the wide character string \ c in into it ' s narrow
equivalent , stored in \ c out . \ c out must have enough space to fit
the entire string .
This function decodes illegal character sequences in a reversible
way using the private use area .
*/
2006-02-08 22:58:47 +08:00
char * wcs2str_internal ( const wchar_t * in , char * out ) ;
2011-12-27 11:18:46 +08:00
/**
Converts some type to a wstring .
*/
template < typename T >
wcstring format_val ( T x ) {
std : : wstringstream stream ;
stream < < x ;
return stream . str ( ) ;
}
template < typename T >
T from_string ( const wcstring & x ) {
T result ;
std : : wstringstream stream ( x ) ;
stream > > result ;
return result ;
}
class scoped_lock {
pthread_mutex_t * lock ;
public :
scoped_lock ( pthread_mutex_t & mutex ) : lock ( & mutex ) {
VOMIT_ON_FAILURE ( pthread_mutex_lock ( lock ) ) ;
}
~ scoped_lock ( ) {
VOMIT_ON_FAILURE ( pthread_mutex_unlock ( lock ) ) ;
}
} ;
class wcstokenizer {
wchar_t * buffer , * str , * state ;
const wcstring sep ;
public :
wcstokenizer ( const wcstring & s , const wcstring & separator ) : sep ( separator ) {
wchar_t * wcsdup ( const wchar_t * s ) ;
buffer = wcsdup ( s . c_str ( ) ) ;
str = buffer ;
state = NULL ;
}
bool next ( wcstring & result ) {
wchar_t * tmp = wcstok ( str , sep . c_str ( ) , & state ) ;
str = NULL ;
if ( tmp ) result = tmp ;
return tmp ! = NULL ;
}
~ wcstokenizer ( ) {
free ( buffer ) ;
}
} ;
/**
Appends a path component , with a / if necessary
*/
void append_path_component ( wcstring & path , const wcstring & component ) ;
wcstring format_string ( const wchar_t * format , . . . ) ;
2005-09-20 21:26:39 +08:00
/**
2005-11-03 00:49:13 +08:00
Returns a newly allocated wide character string array equivalent of
the specified multibyte character string array
2005-09-20 21:26:39 +08:00
*/
char * * wcsv2strv ( const wchar_t * * in ) ;
/**
Returns a newly allocated multibyte character string array equivalent of the specified wide character string array
*/
wchar_t * * strv2wcsv ( const char * * in ) ;
/**
2005-11-03 00:49:13 +08:00
Returns a newly allocated concatenation of the specified wide
character strings . The last argument must be a null pointer .
2005-09-20 21:26:39 +08:00
*/
2007-09-29 05:32:27 +08:00
__sentinel wchar_t * wcsdupcat_internal ( const wchar_t * a , . . . ) ;
2005-09-20 21:26:39 +08:00
/**
Test if the given string is a valid variable name
*/
2006-04-21 22:29:39 +08:00
/**
2011-12-27 11:18:46 +08:00
Test if the given string is a valid variable name .
2006-04-21 22:29:39 +08:00
\ return null if this is a valid name , and a pointer to the first invalid character otherwise
*/
2006-10-19 23:39:50 +08:00
wchar_t * wcsvarname ( const wchar_t * str ) ;
2005-09-20 21:26:39 +08:00
2006-10-19 23:47:47 +08:00
/**
2011-12-27 11:18:46 +08:00
Test if the given string is a valid function name .
2006-10-19 23:47:47 +08:00
\ return null if this is a valid name , and a pointer to the first invalid character otherwise
*/
wchar_t * wcsfuncname ( const wchar_t * str ) ;
2006-06-02 07:04:38 +08:00
/**
2011-12-27 11:18:46 +08:00
Test if the given string is valid in a variable name
2006-06-02 07:04:38 +08:00
\ return 1 if this is a valid name , 0 otherwise
*/
int wcsvarchr ( wchar_t chr ) ;
2005-09-20 21:26:39 +08:00
/**
A wcswidth workalike . Fish uses this since the regular wcswidth seems flaky .
*/
int my_wcswidth ( const wchar_t * c ) ;
/**
2005-11-03 00:49:13 +08:00
This functions returns the end of the quoted substring beginning at
2006-02-01 20:27:15 +08:00
\ c in . The type of quoting character is detemrined by examining \ c
in . Returns 0 on error .
2005-11-03 00:49:13 +08:00
\ param in the position of the opening quote
2005-09-20 21:26:39 +08:00
*/
2006-06-14 21:22:40 +08:00
wchar_t * quote_end ( const wchar_t * in ) ;
2005-09-20 21:26:39 +08:00
/**
A call to this function will reset the error counter . Some
functions print out non - critical error messages . These should check
the error_count before , and skip printing the message if
MAX_ERROR_COUNT messages have been printed . The error_reset ( )
should be called after each interactive command executes , to allow
new messages to be printed .
*/
void error_reset ( ) ;
/**
2006-01-09 07:00:49 +08:00
This function behaves exactly like a wide character equivalent of
the C function setlocale , except that it will also try to detect if
the user is using a Unicode character set , and if so , use the
2011-12-27 11:18:46 +08:00
unicode ellipsis character as ellipsis , instead of ' $ ' .
2005-09-20 21:26:39 +08:00
*/
2006-01-09 07:00:49 +08:00
const wchar_t * wsetlocale ( int category , const wchar_t * locale ) ;
2005-09-20 21:26:39 +08:00
/**
2006-05-03 00:28:30 +08:00
Checks if \ c needle is included in the list of strings specified . A warning is printed if needle is zero .
2005-09-20 21:26:39 +08:00
2011-12-27 11:18:46 +08:00
\ param needle the string to search for in the list
2006-05-03 00:28:30 +08:00
2007-09-24 16:18:23 +08:00
\ return zero if needle is not found , of if needle is null , non - zero otherwise
2005-09-20 21:26:39 +08:00
*/
2007-09-29 05:32:27 +08:00
__sentinel int contains_internal ( const wchar_t * needle , . . . ) ;
2005-09-20 21:26:39 +08:00
/**
Call read while blocking the SIGCHLD signal . Should only be called
2007-09-24 16:18:23 +08:00
if you _know_ there is data available for reading , or the program
will hang until there is data .
2005-09-20 21:26:39 +08:00
*/
int read_blocked ( int fd , void * buf , size_t count ) ;
2009-02-23 04:28:52 +08:00
/**
Loop a write request while failiure is non - critical . Return - 1 and set errno
in case of critical error .
*/
ssize_t write_loop ( int fd , char * buff , size_t count ) ;
2005-09-20 21:26:39 +08:00
/**
2005-10-14 19:40:33 +08:00
Issue a debug message with printf - style string formating and
automatic line breaking . The string will begin with the string \ c
program_name , followed by a colon and a whitespace .
2006-12-14 18:01:31 +08:00
Because debug is often called to tell the user about an error ,
before using wperror to give a specific error message , debug will
never ever modify the value of errno .
2011-12-27 11:18:46 +08:00
2005-10-14 19:40:33 +08:00
\ param level the priority of the message . Lower number means higher priority . Messages with a priority_number higher than \ c debug_level will be ignored . .
2011-12-27 11:18:46 +08:00
\ param msg the message format string .
2005-10-14 19:40:33 +08:00
Example :
< code > debug ( 1 , L " Pi = %.3f " , M_PI ) ; < / code >
will print the string ' fish : Pi = 3.141 ' , given that debug_level is 1 or higher , and that program_name is ' fish ' .
2005-09-20 21:26:39 +08:00
*/
2006-01-04 20:51:02 +08:00
void debug ( int level , const wchar_t * msg , . . . ) ;
2005-09-20 21:26:39 +08:00
/**
2005-10-14 19:40:33 +08:00
Replace special characters with backslash escape sequences . Newline is
2011-12-27 11:18:46 +08:00
replaced with \ n , etc .
2005-09-20 21:26:39 +08:00
\ param in The string to be escaped
\ param escape_all Whether all characters wich hold special meaning in fish ( Pipe , semicolon , etc , ) should be escaped , or only unprintable characters
\ return The escaped string , or 0 if there is not enough memory
*/
2006-02-11 08:13:17 +08:00
wchar_t * escape ( const wchar_t * in , int escape_all ) ;
2011-12-27 11:18:46 +08:00
wcstring escape_string ( const wcstring & in , int escape_all ) ;
2005-09-20 21:26:39 +08:00
2005-10-24 23:26:25 +08:00
/**
Expand backslashed escapes and substitute them with their unescaped
counterparts . Also optionally change the wildcards , the tilde
character and a few more into constants which are defined in a
private use area of Unicode . This assumes wchar_t is a unicode
2005-10-25 19:03:52 +08:00
character set .
2005-10-24 23:26:25 +08:00
The result must be free ( ) d . The original string is not modified . If
an invalid sequence is specified , 0 is returned .
*/
2011-12-27 11:18:46 +08:00
wchar_t * unescape ( const wchar_t * in ,
2005-10-24 23:26:25 +08:00
int escape_special ) ;
2005-09-20 21:26:39 +08:00
2011-12-27 11:18:46 +08:00
void unescape_string ( wcstring & str ,
int escape_special ) ;
2005-10-24 23:26:25 +08:00
/**
2011-12-27 11:18:46 +08:00
Attempt to acquire a lock based on a lockfile , waiting LOCKPOLLINTERVAL
milliseconds between polls and timing out after timeout seconds ,
2005-10-24 23:26:25 +08:00
thereafter forcibly attempting to obtain the lock if force is non - zero .
Returns 1 on success , 0 on failure .
To release the lock the lockfile must be unlinked .
2011-12-27 11:18:46 +08:00
A unique temporary file named by appending characters to the lockfile name
2005-10-24 23:26:25 +08:00
is used ; any pre - existing file of the same name is subject to deletion .
*/
2005-10-14 19:40:33 +08:00
int acquire_lock_file ( const char * lockfile , const int timeout , int force ) ;
2011-12-27 11:18:46 +08:00
/**
2007-09-24 16:18:23 +08:00
Returns the width of the terminal window , so that not all
functions that use these values continually have to keep track of
it separately .
2005-10-14 19:40:33 +08:00
2007-09-24 16:18:23 +08:00
Only works if common_handle_winch is registered to handle winch signals .
2005-10-14 19:40:33 +08:00
*/
int common_get_width ( ) ;
/**
Returns the height of the terminal window , so that not all
functions that use these values continually have to keep track of
2007-09-24 16:18:23 +08:00
it separatly .
2005-10-14 19:40:33 +08:00
Only works if common_handle_winch is registered to handle winch signals .
*/
int common_get_height ( ) ;
2005-10-24 23:26:25 +08:00
/**
2005-10-14 19:40:33 +08:00
Handle a window change event by looking up the new window size and
saving it in an internal variable used by common_get_wisth and
common_get_height ( ) .
*/
void common_handle_winch ( int signal ) ;
2006-01-15 19:58:05 +08:00
/**
2006-05-14 17:47:21 +08:00
Write paragraph of output to the specified stringbuffer , and redo
the linebreaks to fit the current screen .
2006-01-15 19:58:05 +08:00
*/
2006-05-14 17:47:21 +08:00
void write_screen ( const wchar_t * msg , string_buffer_t * buff ) ;
2006-01-15 19:58:05 +08:00
2006-05-29 19:13:42 +08:00
/**
Tokenize the specified string into the specified array_list_t .
Each new element is allocated using malloc and must be freed by the
caller .
2011-12-27 11:18:46 +08:00
2006-05-29 19:13:42 +08:00
\ param val the input string . The contents of this string is not changed .
2011-12-27 11:18:46 +08:00
\ param out the list in which to place the elements .
2006-05-29 19:13:42 +08:00
*/
void tokenize_variable_array ( const wchar_t * val , array_list_t * out ) ;
2011-12-27 15:13:05 +08:00
void tokenize_variable_array2 ( const wcstring & val , std : : vector < wcstring > & out ) ;
2006-05-29 19:13:42 +08:00
2006-10-19 19:50:23 +08:00
/**
2007-09-24 16:18:23 +08:00
Make sure the specified direcotry exists . If needed , try to create
it and any currently not existing parent directories . .
2006-10-19 19:50:23 +08:00
2007-09-24 16:18:23 +08:00
\ return 0 if , at the time of function return the directory exists , - 1 otherwise .
2006-10-19 19:50:23 +08:00
*/
2011-12-27 11:18:46 +08:00
int create_directory ( const wchar_t * d ) ;
2006-10-19 19:50:23 +08:00
2006-11-17 22:58:25 +08:00
/**
Print a short message about how to file a bug report to stderr
*/
void bugreport ( ) ;
2007-10-15 17:51:08 +08:00
/**
Format the specified size ( in bytes , kilobytes , etc . ) into the specified stringbuffer .
*/
void sb_format_size ( string_buffer_t * sb ,
long long sz ) ;
2009-02-03 06:46:45 +08:00
/**
Return the number of seconds from the UNIX epoch , with subsecond
precision . This function uses the gettimeofday function , and will
have the same precision as that function .
If an error occurs , NAN is returned .
*/
double timef ( ) ;
2005-10-04 23:11:39 +08:00
# endif