diff options
Diffstat (limited to 'extra')
131 files changed, 56200 insertions, 128 deletions
diff --git a/extra/CMakeLists.txt b/extra/CMakeLists.txt index f2738752c56..5b650413bf3 100644 --- a/extra/CMakeLists.txt +++ b/extra/CMakeLists.txt @@ -13,15 +13,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -INCLUDE_DIRECTORIES( -${CMAKE_SOURCE_DIR}/include -${ZLIB_INCLUDE_DIR} -# Following is for perror, in case NDB is compiled in. -${CMAKE_SOURCE_DIR}/storage/ndb/include -${CMAKE_SOURCE_DIR}/storage/ndb/include/util -${CMAKE_SOURCE_DIR}/storage/ndb/include/ndbapi -${CMAKE_SOURCE_DIR}/storage/ndb/include/portlib -${CMAKE_SOURCE_DIR}/storage/ndb/include/mgmapi) +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include ${ZLIB_INCLUDE_DIR}) # Default install component for the files is Server here SET(MYSQL_INSTALL_COMPONENT Server) @@ -72,20 +64,7 @@ IF(CMAKE_SYSTEM_NAME STREQUAL "SunOS") ENDIF() ENDIF() -MYSQL_ADD_EXECUTABLE(replace replace.c COMPONENT Server) -TARGET_LINK_LIBRARIES(replace mysys) -IF(UNIX) - MYSQL_ADD_EXECUTABLE(resolve_stack_dump resolve_stack_dump.c) - TARGET_LINK_LIBRARIES(resolve_stack_dump mysys) - - MYSQL_ADD_EXECUTABLE(mysql_waitpid mysql_waitpid.c COMPONENT Client) - TARGET_LINK_LIBRARIES(mysql_waitpid mysys) - - MYSQL_ADD_EXECUTABLE(mysqld_safe_helper mysqld_safe_helper.c COMPONENT Server) - TARGET_LINK_LIBRARIES(mysqld_safe_helper mysys) -ENDIF() - - +IF(WITH_INNOBASE_STORAGE_ENGINE OR WITH_XTRADB_STORAGE_ENGINE) # Add path to the InnoDB headers INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/storage/innobase/include @@ -93,6 +72,7 @@ ENDIF() # We use the InnoDB code directly in case the code changes. ADD_DEFINITIONS("-DUNIV_INNOCHECKSUM") + SET(INNOBASE_SOURCES ../storage/innobase/buf/buf0checksum.cc ../storage/innobase/ut/ut0crc32.cc @@ -100,7 +80,29 @@ ENDIF() ../storage/innobase/page/page0zip.cc ) + IF(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64le") + enable_language(ASM) + LIST(APPEND INNOBASE_SOURCES + ../storage/innobase/ut/crc32_power8/crc32.S + ../storage/innobase/ut/crc32_power8/crc32_wrapper.c + ) + ENDIF() + MYSQL_ADD_EXECUTABLE(innochecksum innochecksum.cc ${INNOBASE_SOURCES}) TARGET_LINK_LIBRARIES(innochecksum mysys mysys_ssl) ADD_DEPENDENCIES(innochecksum GenError) +ENDIF() + +MYSQL_ADD_EXECUTABLE(replace replace.c COMPONENT Server) +TARGET_LINK_LIBRARIES(replace mysys) +IF(UNIX) + MYSQL_ADD_EXECUTABLE(resolve_stack_dump resolve_stack_dump.c) + TARGET_LINK_LIBRARIES(resolve_stack_dump mysys) + + MYSQL_ADD_EXECUTABLE(mysql_waitpid mysql_waitpid.c COMPONENT Client) + TARGET_LINK_LIBRARIES(mysql_waitpid mysys) + + MYSQL_ADD_EXECUTABLE(mysqld_safe_helper mysqld_safe_helper.c COMPONENT Server) + TARGET_LINK_LIBRARIES(mysqld_safe_helper mysys) +ENDIF() diff --git a/extra/charset2html.c b/extra/charset2html.c index cafac2c1cd5..5851f206a1c 100644 --- a/extra/charset2html.c +++ b/extra/charset2html.c @@ -97,7 +97,7 @@ static void print_cs(CHARSET_INFO *cs) { /* Control characters 0x0080..0x009F are dysplayed by some - browers as if they were letters. Don't print them to + browsers as if they were letters. Don't print them to avoid confusion. */ printf("<TD>ctrl<TD>ctrl<TD>ctrl"); diff --git a/extra/innochecksum.cc b/extra/innochecksum.cc index 04807334baa..28191da2c48 100644 --- a/extra/innochecksum.cc +++ b/extra/innochecksum.cc @@ -1,5 +1,6 @@ /* Copyright (c) 2005, 2012, Oracle and/or its affiliates. + Copyright (c) 2014, 2015, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -37,15 +38,16 @@ #include <my_getopt.h> #include <m_string.h> #include <welcome_copyright_notice.h> /* ORACLE_WELCOME_COPYRIGHT_NOTICE */ -#include <string.h> /* Only parts of these files are included from the InnoDB codebase. The parts not included are excluded by #ifndef UNIV_INNOCHECKSUM. */ #include "univ.i" /* include all of this */ +#define FLST_BASE_NODE_SIZE (4 + 2 * FIL_ADDR_SIZE) #define FLST_NODE_SIZE (2 * FIL_ADDR_SIZE) #define FSEG_PAGE_DATA FIL_PAGE_DATA +#define MLOG_1BYTE (1) #include "ut0ut.h" #include "ut0byte.h" @@ -59,8 +61,8 @@ The parts not included are excluded by #ifndef UNIV_INNOCHECKSUM. */ #include "trx0undo.h" /* TRX_* */ #include "fsp0fsp.h" /* fsp_flags_get_page_size() & fsp_flags_get_zip_size() */ -#include "mach0data.h" /* mach_read_from_4() */ #include "ut0crc32.h" /* ut_crc32_init() */ +#include "fsp0pagecompress.h" /* fil_get_compression_alg_name */ #ifdef UNIV_NONINL # include "fsp0fsp.ic" @@ -78,11 +80,83 @@ static ulong end_page; static ulong do_page; static my_bool use_end_page; static my_bool do_one_page; +static my_bool per_page_details; +static my_bool do_leaf; +static ulong n_merge; ulong srv_page_size; /* replaces declaration in srv0srv.c */ static ulong physical_page_size; /* Page size in bytes on disk. */ static ulong logical_page_size; /* Page size when uncompressed. */ static bool compressed= false; /* Is tablespace compressed */ +int n_undo_state_active; +int n_undo_state_cached; +int n_undo_state_to_free; +int n_undo_state_to_purge; +int n_undo_state_prepared; +int n_undo_state_other; +int n_undo_insert, n_undo_update, n_undo_other; +int n_bad_checksum; +int n_fil_page_index; +int n_fil_page_undo_log; +int n_fil_page_inode; +int n_fil_page_ibuf_free_list; +int n_fil_page_allocated; +int n_fil_page_ibuf_bitmap; +int n_fil_page_type_sys; +int n_fil_page_type_trx_sys; +int n_fil_page_type_fsp_hdr; +int n_fil_page_type_allocated; +int n_fil_page_type_xdes; +int n_fil_page_type_blob; +int n_fil_page_type_zblob; +int n_fil_page_type_other; +int n_fil_page_type_page_compressed; +int n_fil_page_type_page_compressed_encrypted; + +int n_fil_page_max_index_id; + +#define SIZE_RANGES_FOR_PAGE 10 +#define NUM_RETRIES 3 +#define DEFAULT_RETRY_DELAY 1000000 + +struct per_page_stats { + ulint n_recs; + ulint data_size; + ulint left_page_no; + ulint right_page_no; + per_page_stats(ulint n, ulint data, ulint left, ulint right) : + n_recs(n), data_size(data), left_page_no(left), right_page_no(right) {} + per_page_stats() : n_recs(0), data_size(0), left_page_no(0), right_page_no(0) {} +}; + +struct per_index_stats { + unsigned long long pages; + unsigned long long leaf_pages; + ulint first_leaf_page; + ulint count; + ulint free_pages; + ulint max_data_size; + unsigned long long total_n_recs; + unsigned long long total_data_bytes; + + /*!< first element for empty pages, + last element for pages with more than logical_page_size */ + unsigned long long pages_in_size_range[SIZE_RANGES_FOR_PAGE+2]; + + std::map<ulint, per_page_stats> leaves; + + per_index_stats():pages(0), leaf_pages(0), first_leaf_page(0), + count(0), free_pages(0), max_data_size(0), total_n_recs(0), + total_data_bytes(0) + { + memset(pages_in_size_range, 0, sizeof(pages_in_size_range)); + } +}; + +std::map<unsigned long long, per_index_stats> index_ids; + +bool encrypted = false; + /* Get the page size of the filespace from the filespace header. */ static my_bool @@ -128,6 +202,8 @@ get_page_size( { compressed= true; } + + return TRUE; } @@ -160,6 +236,14 @@ static struct my_option innochecksum_options[] = {"page", 'p', "Check only this page (0 based).", &do_page, &do_page, 0, GET_ULONG, REQUIRED_ARG, 0, 0, (longlong) 2L*1024L*1024L*1024L, 0, 1, 0}, + {"per_page_details", 'i', "Print out per-page detail information.", + &per_page_details, &per_page_details, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0} + , + {"leaf", 'l', "Examine leaf index pages", + &do_leaf, &do_leaf, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"merge", 'm', "leaf page count if merge given number of consecutive pages", + &n_merge, &n_merge, 0, GET_ULONG, REQUIRED_ARG, + 0, 0, (longlong)10L, 0, 1, 0}, {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} }; @@ -231,12 +315,372 @@ static int get_options( return 0; } /* get_options */ +/*********************************************************************//** +Gets the file page type. +@return type; NOTE that if the type has not been written to page, the +return value not defined */ +ulint +fil_page_get_type( +/*==============*/ + uchar* page) /*!< in: file page */ +{ + return(mach_read_from_2(page + FIL_PAGE_TYPE)); +} + +/**************************************************************//** +Gets the index id field of a page. +@return index id */ +ib_uint64_t +btr_page_get_index_id( +/*==================*/ + uchar* page) /*!< in: index page */ +{ + return(mach_read_from_8(page + PAGE_HEADER + PAGE_INDEX_ID)); +} + +/********************************************************//** +Gets the next index page number. +@return next page number */ +ulint +btr_page_get_next( +/*==============*/ + const page_t* page) /*!< in: index page */ +{ + return(mach_read_from_4(page + FIL_PAGE_NEXT)); +} + +/********************************************************//** +Gets the previous index page number. +@return prev page number */ +ulint +btr_page_get_prev( +/*==============*/ + const page_t* page) /*!< in: index page */ +{ + return(mach_read_from_4(page + FIL_PAGE_PREV)); +} + +void +parse_page( +/*=======*/ + uchar* page, /* in: buffer page */ + uchar* xdes) /* in: extend descriptor page */ +{ + ib_uint64_t id; + ulint x; + ulint n_recs; + ulint page_no; + ulint left_page_no; + ulint right_page_no; + ulint data_bytes; + int is_leaf; + int size_range_id; + + switch (fil_page_get_type(page)) { + case FIL_PAGE_INDEX: + n_fil_page_index++; + id = btr_page_get_index_id(page); + n_recs = page_get_n_recs(page); + page_no = page_get_page_no(page); + left_page_no = btr_page_get_prev(page); + right_page_no = btr_page_get_next(page); + data_bytes = page_get_data_size(page); + is_leaf = page_is_leaf(page); + size_range_id = (data_bytes * SIZE_RANGES_FOR_PAGE + + logical_page_size - 1) / + logical_page_size; + if (size_range_id > SIZE_RANGES_FOR_PAGE + 1) { + /* data_bytes is bigger than logical_page_size */ + size_range_id = SIZE_RANGES_FOR_PAGE + 1; + } + if (per_page_details) { + printf("index " IB_ID_FMT " page " ULINTPF + " leaf %d n_recs " ULINTPF " data_bytes " ULINTPF + "\n", id, page_no, is_leaf, n_recs, data_bytes); + } + /* update per-index statistics */ + { + if (index_ids.count(id) == 0) { + index_ids[id] = per_index_stats(); + } + std::map<unsigned long long, per_index_stats>::iterator it; + it = index_ids.find(id); + per_index_stats &index = (it->second); + uchar* des = xdes + XDES_ARR_OFFSET + + XDES_SIZE * ((page_no & (physical_page_size - 1)) + / FSP_EXTENT_SIZE); + if (xdes_get_bit(des, XDES_FREE_BIT, + page_no % FSP_EXTENT_SIZE)) { + index.free_pages++; + return; + } + index.pages++; + if (is_leaf) { + index.leaf_pages++; + if (data_bytes > index.max_data_size) { + index.max_data_size = data_bytes; + } + struct per_page_stats pp(n_recs, data_bytes, + left_page_no, right_page_no); + + index.leaves[page_no] = pp; + + if (left_page_no == ULINT32_UNDEFINED) { + index.first_leaf_page = page_no; + index.count++; + } + } + index.total_n_recs += n_recs; + index.total_data_bytes += data_bytes; + index.pages_in_size_range[size_range_id] ++; + } + + break; + case FIL_PAGE_UNDO_LOG: + if (per_page_details) { + printf("FIL_PAGE_UNDO_LOG\n"); + } + n_fil_page_undo_log++; + x = mach_read_from_2(page + TRX_UNDO_PAGE_HDR + + TRX_UNDO_PAGE_TYPE); + if (x == TRX_UNDO_INSERT) + n_undo_insert++; + else if (x == TRX_UNDO_UPDATE) + n_undo_update++; + else + n_undo_other++; + + x = mach_read_from_2(page + TRX_UNDO_SEG_HDR + TRX_UNDO_STATE); + switch (x) { + case TRX_UNDO_ACTIVE: n_undo_state_active++; break; + case TRX_UNDO_CACHED: n_undo_state_cached++; break; + case TRX_UNDO_TO_FREE: n_undo_state_to_free++; break; + case TRX_UNDO_TO_PURGE: n_undo_state_to_purge++; break; + case TRX_UNDO_PREPARED: n_undo_state_prepared++; break; + default: n_undo_state_other++; break; + } + break; + case FIL_PAGE_INODE: + if (per_page_details) { + printf("FIL_PAGE_INODE\n"); + } + n_fil_page_inode++; + break; + case FIL_PAGE_IBUF_FREE_LIST: + if (per_page_details) { + printf("FIL_PAGE_IBUF_FREE_LIST\n"); + } + n_fil_page_ibuf_free_list++; + break; + case FIL_PAGE_TYPE_ALLOCATED: + if (per_page_details) { + printf("FIL_PAGE_TYPE_ALLOCATED\n"); + } + n_fil_page_type_allocated++; + break; + case FIL_PAGE_IBUF_BITMAP: + if (per_page_details) { + printf("FIL_PAGE_IBUF_BITMAP\n"); + } + n_fil_page_ibuf_bitmap++; + break; + case FIL_PAGE_TYPE_SYS: + if (per_page_details) { + printf("FIL_PAGE_TYPE_SYS\n"); + } + n_fil_page_type_sys++; + break; + case FIL_PAGE_TYPE_TRX_SYS: + if (per_page_details) { + printf("FIL_PAGE_TYPE_TRX_SYS\n"); + } + n_fil_page_type_trx_sys++; + break; + case FIL_PAGE_TYPE_FSP_HDR: + if (per_page_details) { + printf("FIL_PAGE_TYPE_FSP_HDR\n"); + } + memcpy(xdes, page, physical_page_size); + n_fil_page_type_fsp_hdr++; + break; + case FIL_PAGE_TYPE_XDES: + if (per_page_details) { + printf("FIL_PAGE_TYPE_XDES\n"); + } + memcpy(xdes, page, physical_page_size); + n_fil_page_type_xdes++; + break; + case FIL_PAGE_TYPE_BLOB: + if (per_page_details) { + printf("FIL_PAGE_TYPE_BLOB\n"); + } + n_fil_page_type_blob++; + break; + case FIL_PAGE_TYPE_ZBLOB: + case FIL_PAGE_TYPE_ZBLOB2: + if (per_page_details) { + printf("FIL_PAGE_TYPE_ZBLOB/2\n"); + } + n_fil_page_type_zblob++; + break; + case FIL_PAGE_PAGE_COMPRESSED: + if (per_page_details) { + printf("FIL_PAGE_PAGE_COMPRESSED\n"); + } + n_fil_page_type_page_compressed++; + break; + case FIL_PAGE_PAGE_COMPRESSED_ENCRYPTED: + if (per_page_details) { + printf("FIL_PAGE_PAGE_COMPRESSED_ENCRYPTED\n"); + } + n_fil_page_type_page_compressed_encrypted++; + break; + default: + if (per_page_details) { + printf("FIL_PAGE_TYPE_OTHER\n"); + } + n_fil_page_type_other++; + } +} + +void print_index_leaf_stats(unsigned long long id, const per_index_stats& index) +{ + ulint page_no = index.first_leaf_page; + std::map<ulint, per_page_stats>::const_iterator it_page = index.leaves.find(page_no); + printf("\nindex: %llu leaf page stats: n_pages = %llu\n", + id, index.leaf_pages); + printf("page_no\tdata_size\tn_recs\n"); + while (it_page != index.leaves.end()) { + const per_page_stats& stat = it_page->second; + printf(ULINTPF "\t" ULINTPF "\t" ULINTPF "\n", + it_page->first, stat.data_size, stat.n_recs); + page_no = stat.right_page_no; + it_page = index.leaves.find(page_no); + } +} + +void defrag_analysis(unsigned long long id, const per_index_stats& index) +{ + // TODO: make it work for compressed pages too + std::map<ulint, per_page_stats>::const_iterator it = index.leaves.find(index.first_leaf_page); + ulint n_pages = 0; + ulint n_leaf_pages = 0; + while (it != index.leaves.end()) { + ulint data_size_total = 0; + for (ulong i = 0; i < n_merge; i++) { + const per_page_stats& stat = it->second; + n_leaf_pages ++; + data_size_total += stat.data_size; + it = index.leaves.find(stat.right_page_no); + if (it == index.leaves.end()) { + break; + } + } + if (index.max_data_size) { + n_pages += data_size_total / index.max_data_size; + if (data_size_total % index.max_data_size != 0) { + n_pages += 1; + } + } + } + if (!n_leaf_pages) { + n_leaf_pages=1; + } + printf("count = " ULINTPF " free = " ULINTPF "\n", + index.count, index.free_pages); + if (n_leaf_pages) { + printf("%llu\t\t%llu\t\t" + ULINTPF "\t\t%lu\t\t" ULINTPF "\t\t%.2f\t" ULINTPF "\n", + id, index.leaf_pages, n_leaf_pages, n_merge, n_pages, + 1.0 - (double)n_pages / (double)n_leaf_pages, index.max_data_size); + } +} + +void print_leaf_stats() +{ + printf("\n**************************************************\n"); + printf("index_id\t#leaf_pages\t#actual_leaf_pages\tn_merge\t" + "#leaf_after_merge\tdefrag\n"); + for (std::map<unsigned long long, per_index_stats>::const_iterator it = index_ids.begin(); it != index_ids.end(); it++) { + const per_index_stats& index = it->second; + if (verbose) { + print_index_leaf_stats(it->first, index); + } + if (n_merge) { + defrag_analysis(it->first, index); + } + } +} + +void +print_stats() +/*========*/ +{ + unsigned long long i; + + printf("%d\tbad checksum\n", n_bad_checksum); + printf("%d\tFIL_PAGE_INDEX\n", n_fil_page_index); + printf("%d\tFIL_PAGE_UNDO_LOG\n", n_fil_page_undo_log); + printf("%d\tFIL_PAGE_INODE\n", n_fil_page_inode); + printf("%d\tFIL_PAGE_IBUF_FREE_LIST\n", n_fil_page_ibuf_free_list); + printf("%d\tFIL_PAGE_TYPE_ALLOCATED\n", n_fil_page_type_allocated); + printf("%d\tFIL_PAGE_IBUF_BITMAP\n", n_fil_page_ibuf_bitmap); + printf("%d\tFIL_PAGE_TYPE_SYS\n", n_fil_page_type_sys); + printf("%d\tFIL_PAGE_TYPE_TRX_SYS\n", n_fil_page_type_trx_sys); + printf("%d\tFIL_PAGE_TYPE_FSP_HDR\n", n_fil_page_type_fsp_hdr); + printf("%d\tFIL_PAGE_TYPE_XDES\n", n_fil_page_type_xdes); + printf("%d\tFIL_PAGE_TYPE_BLOB\n", n_fil_page_type_blob); + printf("%d\tFIL_PAGE_TYPE_ZBLOB\n", n_fil_page_type_zblob); + printf("%d\tFIL_PAGE_PAGE_COMPRESSED\n", n_fil_page_type_page_compressed); + printf("%d\tFIL_PAGE_PAGE_COMPRESSED_ENCRYPTED\n", n_fil_page_type_page_compressed_encrypted); + printf("%d\tother\n", n_fil_page_type_other); + printf("%d\tmax index_id\n", n_fil_page_max_index_id); + printf("undo type: %d insert, %d update, %d other\n", + n_undo_insert, n_undo_update, n_undo_other); + printf("undo state: %d active, %d cached, %d to_free, %d to_purge," + " %d prepared, %d other\n", n_undo_state_active, + n_undo_state_cached, n_undo_state_to_free, + n_undo_state_to_purge, n_undo_state_prepared, + n_undo_state_other); + + printf("index_id\t#pages\t\t#leaf_pages\t#recs_per_page" + "\t#bytes_per_page\n"); + for (std::map<unsigned long long, per_index_stats>::const_iterator it = index_ids.begin(); it != index_ids.end(); it++) { + const per_index_stats& index = it->second; + ulonglong recs_per_page = index.total_n_recs; + ulonglong bytes_per_page = index.total_data_bytes; + if (index.total_n_recs && index.pages) { + recs_per_page = index.total_n_recs / index.pages; + } + if (index.total_data_bytes && index.pages) { + bytes_per_page = index.total_data_bytes / index.pages; + } + printf("%llu\t\t%llu\t\t%llu\t\t%llu\t\t%llu\n", + it->first, index.pages, index.leaf_pages, + recs_per_page, + bytes_per_page); + } + printf("\n"); + printf("index_id\tpage_data_bytes_histgram(empty,...,oversized)\n"); + for (std::map<unsigned long long, per_index_stats>::const_iterator it = index_ids.begin(); it != index_ids.end(); it++) { + printf("%llu\t", it->first); + const per_index_stats& index = it->second; + for (i = 0; i < SIZE_RANGES_FOR_PAGE+2; i++) { + printf("\t%llu", index.pages_in_size_range[i]); + } + printf("\n"); + } + if (do_leaf) { + print_leaf_stats(); + } +} + int main(int argc, char **argv) { FILE* f; /* our input file */ char* filename; /* our input filename. */ unsigned char *big_buf= 0, *buf; - + unsigned char *big_xdes= 0, *xdes; ulong bytes; /* bytes read count */ ulint ct; /* current page number (0 based) */ time_t now; /* current time */ @@ -265,7 +709,7 @@ int main(int argc, char **argv) if (*filename == '\0') { fprintf(stderr, "Error; File name missing\n"); - goto error; + goto error_out; } #ifdef _WIN32 @@ -313,7 +757,7 @@ int main(int argc, char **argv) if (stat(filename, &st)) { fprintf(stderr, "Error; %s cannot be found\n", filename); - goto error; + goto error_out; } size= st.st_size; @@ -325,7 +769,7 @@ int main(int argc, char **argv) { fprintf(stderr, "Error; %s cannot be opened", filename); perror(" "); - goto error; + goto error_out; } big_buf = (unsigned char *)malloc(2 * UNIV_PAGE_SIZE_MAX); @@ -333,13 +777,26 @@ int main(int argc, char **argv) { fprintf(stderr, "Error; failed to allocate memory\n"); perror(""); - goto error; + goto error_f; } /* Make sure the page is aligned */ buf = (unsigned char*)ut_align_down(big_buf + UNIV_PAGE_SIZE_MAX, UNIV_PAGE_SIZE_MAX); + big_xdes = (unsigned char *)malloc(2 * UNIV_PAGE_SIZE_MAX); + if (big_xdes == NULL) + { + fprintf(stderr, "Error; failed to allocate memory\n"); + perror(""); + goto error_big_buf; + } + + /* Make sure the page is aligned */ + xdes = (unsigned char*)ut_align_down(big_xdes + + UNIV_PAGE_SIZE_MAX, UNIV_PAGE_SIZE_MAX); + + if (!get_page_size(f, buf, &logical_page_size, &physical_page_size)) goto error; @@ -408,7 +865,10 @@ int main(int argc, char **argv) lastt= 0; while (!feof(f)) { + int page_ok = 1; + bytes= fread(buf, 1, physical_page_size, f); + if (!bytes && feof(f)) goto ok; @@ -419,52 +879,133 @@ int main(int argc, char **argv) goto error; } - if (compressed) { - /* compressed pages */ - if (!page_zip_verify_checksum(buf, physical_page_size)) { - fprintf(stderr, "Fail; page " ULINTPF - " invalid (fails compressed page checksum).\n", ct); - if (!skip_corrupt) - goto error; - } + ulint page_type = mach_read_from_2(buf+FIL_PAGE_TYPE); + ulint key_version = mach_read_from_4(buf + FIL_PAGE_FILE_FLUSH_LSN_OR_KEY_VERSION); + + if (key_version && page_type != FIL_PAGE_PAGE_COMPRESSED) { + encrypted = true; } else { + encrypted = false; + } + + ulint comp_method = 0; + + if (encrypted) { + comp_method = mach_read_from_2(buf+FIL_PAGE_DATA+FIL_PAGE_COMPRESSED_SIZE); + } else { + comp_method = mach_read_from_8(buf+FIL_PAGE_FILE_FLUSH_LSN_OR_KEY_VERSION); + } + + ulint comp_size = mach_read_from_2(buf+FIL_PAGE_DATA); + ib_uint32_t encryption_checksum = mach_read_from_4(buf+FIL_PAGE_FILE_FLUSH_LSN_OR_KEY_VERSION + 4); - /* check the "stored log sequence numbers" */ - logseq= mach_read_from_4(buf + FIL_PAGE_LSN + 4); - logseqfield= mach_read_from_4(buf + logical_page_size - FIL_PAGE_END_LSN_OLD_CHKSUM + 4); + + if (page_type == FIL_PAGE_PAGE_COMPRESSED) { + /* Page compressed tables do not have any checksum */ if (debug) - printf("page " ULINTPF - ": log sequence number: first = " ULINTPF - "; second = " ULINTPF "\n", - ct, logseq, logseqfield); - if (logseq != logseqfield) - { - fprintf(stderr, "Fail; page " ULINTPF - " invalid (fails log sequence number check)\n", ct); - if (!skip_corrupt) - goto error; + fprintf(stderr, "Page " ULINTPF + " page compressed with method %s real_size " ULINTPF "\n", ct, + fil_get_compression_alg_name(comp_method), comp_size); + page_ok = 1; + } else if (compressed) { + /* compressed pages */ + ulint crccsum = page_zip_calc_checksum(buf, physical_page_size, SRV_CHECKSUM_ALGORITHM_CRC32); + ulint icsum = page_zip_calc_checksum(buf, physical_page_size, SRV_CHECKSUM_ALGORITHM_INNODB); + + if (debug) { + if (key_version != 0) { + fprintf(stderr, + "Page " ULINTPF + " encrypted key_version " ULINTPF + " calculated = " ULINTPF "; crc32 = " ULINTPF + "; recorded = %u\n", + ct, key_version, icsum, crccsum, encryption_checksum); + } + } + + if (encrypted) { + if (encryption_checksum != 0 && crccsum != encryption_checksum && icsum != encryption_checksum) { + if (debug) + fprintf(stderr, "page " ULINTPF + ": compressed: calculated = " ULINTPF + "; crc32 = " ULINTPF "; recorded = %u\n", + ct, icsum, crccsum, encryption_checksum); + fprintf(stderr, "Fail; page " ULINTPF + " invalid (fails compressed page checksum).\n", ct); + } + } else { + if (!page_zip_verify_checksum(buf, physical_page_size)) { + fprintf(stderr, "Fail; page " ULINTPF + " invalid (fails compressed page checksum).\n", ct); + if (!skip_corrupt) + goto error; + page_ok = 0; + } + } + } else { + if (key_version != 0) { + /* Encrypted page */ + if (debug) { + if (page_type == FIL_PAGE_PAGE_COMPRESSED_ENCRYPTED) { + fprintf(stderr, + "Page " ULINTPF + " page compressed with method %s real_size " ULINTPF + " and encrypted key_version " ULINTPF " checksum %u\n", + ct, fil_get_compression_alg_name(comp_method), comp_size, key_version, encryption_checksum); + } else { + fprintf(stderr, + "Page " ULINTPF + " encrypted key_version " ULINTPF " checksum %u\n", + ct, key_version, encryption_checksum); + } + } } - /* check old method of checksumming */ - oldcsum= buf_calc_page_old_checksum(buf); - oldcsumfield= mach_read_from_4(buf + logical_page_size - FIL_PAGE_END_LSN_OLD_CHKSUM); - if (debug) - printf("page " ULINTPF - ": old style: calculated = " ULINTPF - "; recorded = " ULINTPF "\n", - ct, oldcsum, oldcsumfield); - if (oldcsumfield != mach_read_from_4(buf + FIL_PAGE_LSN) && oldcsumfield != oldcsum) - { - fprintf(stderr, "Fail; page " ULINTPF - " invalid (fails old style checksum)\n", ct); - if (!skip_corrupt) - goto error; + /* Page compressed tables do not contain FIL tailer */ + if (page_type != FIL_PAGE_PAGE_COMPRESSED_ENCRYPTED && page_type != FIL_PAGE_PAGE_COMPRESSED) { + /* check the "stored log sequence numbers" */ + logseq= mach_read_from_4(buf + FIL_PAGE_LSN + 4); + logseqfield= mach_read_from_4(buf + logical_page_size - FIL_PAGE_END_LSN_OLD_CHKSUM + 4); + if (debug) + printf("page " ULINTPF + ": log sequence number: first = " ULINTPF + "; second = " ULINTPF "\n", + ct, logseq, logseqfield); + if (logseq != logseqfield) + { + fprintf(stderr, "Fail; page " ULINTPF + " invalid (fails log sequence number check)\n", ct); + if (!skip_corrupt) + goto error; + page_ok = 0; + } + + /* check old method of checksumming */ + oldcsum= buf_calc_page_old_checksum(buf); + oldcsumfield= mach_read_from_4(buf + logical_page_size - FIL_PAGE_END_LSN_OLD_CHKSUM); + if (debug) + printf("page " ULINTPF + ": old style: calculated = " ULINTPF + "; recorded = " ULINTPF "\n", + ct, oldcsum, oldcsumfield); + if (oldcsumfield != mach_read_from_4(buf + FIL_PAGE_LSN) && oldcsumfield != oldcsum) + { + fprintf(stderr, "Fail; page " ULINTPF + " invalid (fails old style checksum)\n", ct); + if (!skip_corrupt) + goto error; + page_ok = 0; + } } /* now check the new method */ csum= buf_calc_page_new_checksum(buf); crc32= buf_calc_page_crc32(buf); csumfield= mach_read_from_4(buf + FIL_PAGE_SPACE_OR_CHKSUM); + + if (key_version) + csumfield = encryption_checksum; + if (debug) printf("page " ULINTPF ": new style: calculated = " ULINTPF @@ -476,14 +1017,36 @@ int main(int argc, char **argv) " invalid (fails innodb and crc32 checksum)\n", ct); if (!skip_corrupt) goto error; + page_ok = 0; } } /* end if this was the last page we were supposed to check */ if (use_end_page && (ct >= end_page)) goto ok; + if (per_page_details) + { + printf("page " ULINTPF " ", ct); + } + /* do counter increase and progress printing */ ct++; + + if (!page_ok) + { + if (per_page_details) + { + printf("BAD_CHECKSUM\n"); + } + n_bad_checksum++; + continue; + } + + /* Can't parse compressed or/and encrypted pages */ + if (page_type != FIL_PAGE_PAGE_COMPRESSED && !encrypted) { + parse_page(buf, xdes); + } + if (verbose) { if (ct % 64 == 0) @@ -501,12 +1064,21 @@ int main(int argc, char **argv) } ok: + if (!just_count) + print_stats(); + free(big_xdes); free(big_buf); + fclose(f); my_end(0); exit(0); error: + free(big_xdes); +error_big_buf: free(big_buf); +error_f: + fclose(f); +error_out: my_end(0); exit(1); } diff --git a/extra/mariabackup/CMakeLists.txt b/extra/mariabackup/CMakeLists.txt new file mode 100644 index 00000000000..693082b765a --- /dev/null +++ b/extra/mariabackup/CMakeLists.txt @@ -0,0 +1,214 @@ +# Copyright (c) 2013, 2017 Percona LLC and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + +OPTION(WITH_MARIABACKUP "Include mariabackup" ON) +IF(NOT WITH_MARIABACKUP) + RETURN() +ENDIF() + + +IF(NOT WIN32) + CHECK_SYMBOL_EXISTS(regcomp regex.h HAVE_SYSTEM_REGEX) + IF(HAVE_SYSTEM_REGEX) + ADD_DEFINITIONS(-DHAVE_SYSTEM_REGEX) + ENDIF() +ENDIF() + +IF(WITH_LIBARCHIVE STREQUAL "STATIC") + SET(CMAKE_FIND_LIBRARY_SUFFIXES .a .lib) +ENDIF() + +FIND_PACKAGE(LibArchive) + +IF(NOT DEFINED WITH_LIBARCHIVE) + IF(LibArchive_FOUND) + SET(WITH_LIBARCHIVE_DEFAULT ON) + ELSE() + SET(WITH_LIBARCHIVE_DEFAULT OFF) + ENDIF() + SET(WITH_LIBARCHIVE ${WITH_LIBARCHIVE_DEFAULT} CACHE STRING "Use libarchive for streaming features (ON, OFF or STATIC)" ) +ENDIF() + +IF(NOT WITH_LIBARCHIVE MATCHES "^(ON|OFF|STATIC)$") + MESSAGE(FATAL_ERROR "Invalid value for WITH_LIBARCHIVE: '${WITH_LIBARCHIVE}'. Use one of ON, OFF or STATIC") +ENDIF() + +IF(UNIX) + SET(PIC_FLAG -fPIC) +ENDIF() + +IF((NOT WITH_LIBARCHIVE STREQUAL "OFF") AND (NOT LibArchive_FOUND)) + IF(CMAKE_VERSION VERSION_LESS "2.8.12") + MESSAGE("libarchive can't be built, old cmake") + ELSE() + # Build a local version + INCLUDE(ExternalProject) + SET(LIBARCHIVE_DIR ${CMAKE_CURRENT_BINARY_DIR}/libarchive) + SET(libarchive_PREFIX ${CMAKE_CURRENT_BINARY_DIR}/libarchive) + SET(libarchive_CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DENABLE_ICONV=OFF + -DENABLE_TAR=ON + -DENABLE_OPENSSL=OFF + -DENABLE_TEST=OFF + "-DCMAKE_C_FLAGS_DEBUG=${CMAKE_C_FLAGS_DEBUG} ${PIC_FLAG}" + "-DCMAKE_C_FLAGS_RELWITHDEBINFO=${CMAKE_C_FLAGS_RELWITHDEBINFO} ${PIC_FLAG}" + "-DCMAKE_C_FLAGS_RELEASE=${CMAKE_C_FLAGS_RELEASE} ${PIC_FLAG}" + "-DCMAKE_C_FLAGS_MINSIZEREL=${CMAKE_C_FLAGS_MINSIZEREL} ${PIC_FLAG}" + ) + IF(WIN32) + SET(libarchive_CMAKE_ARGS ${libarchive_CMAKE_ARGS} -DWINDOWS_VERSION=WIN7 -DCMAKE_DEBUG_POSTFIX=d) + SET(LIBARCHIVE_RELEASE_LIB ${LIBARCHIVE_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}archive_static${CMAKE_STATIC_LIBRARY_SUFFIX}) + SET(LIBARCHIVE_DEBUG_LIB ${LIBARCHIVE_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}archive_staticd${CMAKE_STATIC_LIBRARY_SUFFIX}) + SET(byproducts ${LIBARCHIVE_RELEASE_LIB} ${LIBARCHIVE_DEBUG_LIB}) + ELSE() + SET(LIBARCHIVE_LIB ${LIBARCHIVE_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}archive${CMAKE_STATIC_LIBRARY_SUFFIX}) + SET(byproducts ${LIBARCHIVE_LIB}) + ENDIF() + + IF(CMAKE_VERSION VERSION_GREATER "3.1") + SET(byproducts BUILD_BYPRODUCTS ${byproducts}) + ENDIF() + + ExternalProject_Add(libarchive + PREFIX ${libarchive_PREFIX} + DOWNLOAD_DIR ${LIBARCHIVE_DIR} + URL http://www.libarchive.org/downloads/libarchive-3.2.2.tar.gz + INSTALL_DIR ${LIBARCHIVE_DIR} + CMAKE_ARGS ${libarchive_CMAKE_ARGS} + ${byproducts} + ) + ADD_LIBRARY(archive_static STATIC IMPORTED) + ADD_DEPENDENCIES(archive_static libarchive) + IF(WIN32) + SET_PROPERTY(TARGET archive_static PROPERTY IMPORTED_LOCATION_RELWITHDEBINFO ${LIBARCHIVE_RELEASE_LIB}) + SET_PROPERTY(TARGET archive_static PROPERTY IMPORTED_LOCATION_RELEASE ${LIBARCHIVE_RELEASE_LIB}) + SET_PROPERTY(TARGET archive_static PROPERTY IMPORTED_LOCATION_DEBUG ${LIBARCHIVE_DEBUG_LIB}) + SET_PROPERTY(TARGET archive_static PROPERTY IMPORTED_LOCATION_MINSIZEREL ${LIBARCHIVE_RELEASE_LIB}) + ELSE() + SET_PROPERTY(TARGET archive_static PROPERTY IMPORTED_LOCATION ${LIBARCHIVE_LIB}) + ENDIF() + + SET(LibArchive_FOUND ON ) + SET(LibArchive_INCLUDE_DIRS ${LIBARCHIVE_DIR}/include ) + SET(LibArchive_LIBRARIES archive_static) + IF(WIN32) + SET(LIBARCHIVE_STATIC 1) + ENDIF() + ENDIF() +ENDIF() + + +IF(WITH_LIBARCHIVE AND LibArchive_FOUND) + ADD_DEFINITIONS(-DHAVE_LIBARCHIVE) + IF(LIBARCHIVE_STATIC) + ADD_DEFINITIONS(-DLIBARCHIVE_STATIC) + ENDIF() + INCLUDE_DIRECTORIES(${LibArchive_INCLUDE_DIRS}) + LINK_LIBRARIES(${LibArchive_LIBRARIES}) + SET(DS_ARCHIVE_SOURCE ds_archive.c) +ENDIF() + +INCLUDE_DIRECTORIES( + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/storage/xtradb/include + ${CMAKE_SOURCE_DIR}/sql + ${CMAKE_CURRENT_SOURCE_DIR}/quicklz + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/crc + ) + +IF(NOT HAVE_SYSTEM_REGEX) + INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/pcre) +ENDIF() + +ADD_DEFINITIONS(-UMYSQL_SERVER) +######################################################################## +# xtrabackup binary +######################################################################## + +IF(WIN32) + SET(NT_SERVICE_SOURCE ${PROJECT_SOURCE_DIR}/sql/nt_servc.cc) +ELSE() + SET(NT_SERVICE_SOURCE) +ENDIF() + +ADD_DEFINITIONS(-DPCRE_STATIC=1) + +MYSQL_ADD_EXECUTABLE(mariabackup + xtrabackup.cc + innobackupex.cc + changed_page_bitmap.cc + datasink.c + ${DS_ARCHIVE_SOURCE} + ds_buffer.c + ds_compress.c + ds_local.c + ds_stdout.c + ds_tmpfile.c + ds_xbstream.c + fil_cur.cc + quicklz/quicklz.c + read_filt.cc + write_filt.cc + wsrep.cc + xbstream_write.c + backup_mysql.cc + backup_copy.cc + encryption_plugin.cc + ${PROJECT_SOURCE_DIR}/libmysql/libmysql.c + ${PROJECT_SOURCE_DIR}/sql/net_serv.cc + ${NT_SERVICE_SOURCE} + COMPONENT backup + ) + + +# Export all symbols on Unix, for better crash callstacks +SET_TARGET_PROPERTIES(mariabackup PROPERTIES ENABLE_EXPORTS TRUE) +ADD_SUBDIRECTORY(crc) + + +TARGET_LINK_LIBRARIES(mariabackup sql crc) + +IF(NOT HAVE_SYSTEM_REGEX) + TARGET_LINK_LIBRARIES(mariabackup pcreposix) +ENDIF() + + +######################################################################## +# xbstream binary +######################################################################## +MYSQL_ADD_EXECUTABLE(mbstream + ds_buffer.c + ds_local.c + ds_stdout.c + datasink.c + xbstream.c + xbstream_read.c + xbstream_write.c + COMPONENT backup + ) + + +TARGET_LINK_LIBRARIES(mbstream + mysys + crc +) + +IF(MSVC) + SET_TARGET_PROPERTIES(mbstream PROPERTIES LINK_FLAGS setargv.obj) +ENDIF() diff --git a/extra/mariabackup/backup_copy.cc b/extra/mariabackup/backup_copy.cc new file mode 100644 index 00000000000..1565e20d732 --- /dev/null +++ b/extra/mariabackup/backup_copy.cc @@ -0,0 +1,2075 @@ +/****************************************************** +hot backup tool for InnoDB +(c) 2009-2015 Percona LLC and/or its affiliates +(c) 2017 MariaDB +Originally Created 3/3/2009 Yasufumi Kinoshita +Written by Alexey Kopytov, Aleksandr Kuzminsky, Stewart Smith, Vadim Tkachenko, +Yasufumi Kinoshita, Ignacio Nin and Baron Schwartz. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +******************************************************* + +This file incorporates work covered by the following copyright and +permission notice: + +Copyright (c) 2000, 2011, MySQL AB & Innobase Oy. All Rights Reserved. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 59 Temple +Place, Suite 330, Boston, MA 02111-1307 USA + +*******************************************************/ + +#include <my_global.h> +#include <os0file.h> +#include <my_dir.h> +#include <ut0mem.h> +#include <srv0start.h> +#include <fil0fil.h> +#include <set> +#include <string> +#include <mysqld.h> +#include <sstream> +#include "fil_cur.h" +#include "xtrabackup.h" +#include "common.h" +#include "backup_copy.h" +#include "backup_mysql.h" +#include <btr0btr.h> +#include "xb0xb.h" + + +/* list of files to sync for --rsync mode */ +static std::set<std::string> rsync_list; +/* locations of tablespaces read from .isl files */ +static std::map<std::string, std::string> tablespace_locations; + +/* Whether LOCK BINLOG FOR BACKUP has been issued during backup */ +bool binlog_locked; + +/************************************************************************ +Struct represents file or directory. */ +struct datadir_node_t { + ulint dbpath_len; + char *filepath; + ulint filepath_len; + char *filepath_rel; + ulint filepath_rel_len; + bool is_empty_dir; + bool is_file; +}; + +/************************************************************************ +Holds the state needed to enumerate files in MySQL data directory. */ +struct datadir_iter_t { + char *datadir_path; + char *dbpath; + ulint dbpath_len; + char *filepath; + ulint filepath_len; + char *filepath_rel; + ulint filepath_rel_len; + os_ib_mutex_t mutex; + os_file_dir_t dir; + os_file_dir_t dbdir; + os_file_stat_t dbinfo; + os_file_stat_t fileinfo; + dberr_t err; + bool is_empty_dir; + bool is_file; + bool skip_first_level; +}; + + +/************************************************************************ +Represents the context of the thread processing MySQL data directory. */ +struct datadir_thread_ctxt_t { + datadir_iter_t *it; + uint n_thread; + uint *count; + os_ib_mutex_t count_mutex; + os_thread_id_t id; + bool ret; +}; + +static bool backup_files_from_datadir(const char *dir_path); + +/************************************************************************ +Retirn true if character if file separator */ +bool +is_path_separator(char c) +{ + return(c == FN_LIBCHAR || c == FN_LIBCHAR2); +} + + +/************************************************************************ +Fill the node struct. Memory for node need to be allocated and freed by +the caller. It is caller responsibility to initialize node with +datadir_node_init and cleanup the memory with datadir_node_free. +Node can not be shared between threads. */ +static +void +datadir_node_fill(datadir_node_t *node, datadir_iter_t *it) +{ + if (node->filepath_len < it->filepath_len) { + free(node->filepath); + node->filepath = (char*)(ut_malloc(it->filepath_len)); + node->filepath_len = it->filepath_len; + } + if (node->filepath_rel_len < it->filepath_rel_len) { + free(node->filepath_rel); + node->filepath_rel = (char*)(ut_malloc(it->filepath_rel_len)); + node->filepath_rel_len = it->filepath_rel_len; + } + + strcpy(node->filepath, it->filepath); + strcpy(node->filepath_rel, it->filepath_rel); + node->is_empty_dir = it->is_empty_dir; + node->is_file = it->is_file; +} + +static +void +datadir_node_free(datadir_node_t *node) +{ + ut_free(node->filepath); + ut_free(node->filepath_rel); + memset(node, 0, sizeof(datadir_node_t)); +} + +static +void +datadir_node_init(datadir_node_t *node) +{ + memset(node, 0, sizeof(datadir_node_t)); +} + + +/************************************************************************ +Create the MySQL data directory iterator. Memory needs to be released +with datadir_iter_free. Position should be advanced with +datadir_iter_next_file. Iterator can be shared between multiple +threads. It is guaranteed that each thread receives unique file from +data directory into its local node struct. */ +static +datadir_iter_t * +datadir_iter_new(const char *path, bool skip_first_level = true) +{ + datadir_iter_t *it; + + it = static_cast<datadir_iter_t *>(ut_malloc(sizeof(datadir_iter_t))); + memset(it, 0, sizeof(datadir_iter_t)); + + it->mutex = os_mutex_create(); + it->datadir_path = strdup(path); + + it->dir = os_file_opendir(it->datadir_path, TRUE); + + if (it->dir == NULL) { + + goto error; + } + + it->err = DB_SUCCESS; + + it->dbpath_len = FN_REFLEN; + it->dbpath = static_cast<char*>(ut_malloc(it->dbpath_len)); + + it->filepath_len = FN_REFLEN; + it->filepath = static_cast<char*>(ut_malloc(it->filepath_len)); + + it->filepath_rel_len = FN_REFLEN; + it->filepath_rel = static_cast<char*>(ut_malloc(it->filepath_rel_len)); + + it->skip_first_level = skip_first_level; + + return(it); + +error: + ut_free(it); + + return(NULL); +} + +static +bool +datadir_iter_next_database(datadir_iter_t *it) +{ + if (it->dbdir != NULL) { + if (os_file_closedir(it->dbdir) != 0) { + + msg("Warning: could not" + " close database directory %s\n", it->dbpath); + + it->err = DB_ERROR; + + } + it->dbdir = NULL; + } + + while (os_file_readdir_next_file(it->datadir_path, + it->dir, &it->dbinfo) == 0) { + ulint len; + + if ((it->dbinfo.type == OS_FILE_TYPE_FILE + && it->skip_first_level) + || it->dbinfo.type == OS_FILE_TYPE_UNKNOWN) { + + continue; + } + + /* We found a symlink or a directory; try opening it to see + if a symlink is a directory */ + + len = strlen(it->datadir_path) + + strlen (it->dbinfo.name) + 2; + if (len > it->dbpath_len) { + it->dbpath_len = len; + + if (it->dbpath) { + + ut_free(it->dbpath); + } + + it->dbpath = static_cast<char*> + (ut_malloc(it->dbpath_len)); + } + ut_snprintf(it->dbpath, it->dbpath_len, + "%s/%s", it->datadir_path, + it->dbinfo.name); + srv_normalize_path_for_win(it->dbpath); + + if (it->dbinfo.type == OS_FILE_TYPE_FILE) { + it->is_file = true; + return(true); + } + + if (check_if_skip_database_by_path(it->dbpath)) { + msg("Skipping db: %s\n", it->dbpath); + continue; + } + + /* We want wrong directory permissions to be a fatal error for + XtraBackup. */ + it->dbdir = os_file_opendir(it->dbpath, TRUE); + + if (it->dbdir != NULL) { + + it->is_file = false; + return(true); + } + + } + + return(false); +} + +/************************************************************************ +Concatenate n parts into single path */ +static +void +make_path_n(int n, char **path, ulint *path_len, ...) +{ + ulint len_needed = n + 1; + char *p; + int i; + va_list vl; + + ut_ad(n > 0); + + va_start(vl, path_len); + for (i = 0; i < n; i++) { + p = va_arg(vl, char*); + len_needed += strlen(p); + } + va_end(vl); + + if (len_needed < *path_len) { + ut_free(*path); + *path = static_cast<char*>(ut_malloc(len_needed)); + } + + va_start(vl, path_len); + p = va_arg(vl, char*); + strcpy(*path, p); + for (i = 1; i < n; i++) { + size_t plen; + p = va_arg(vl, char*); + plen = strlen(*path); + if (!is_path_separator((*path)[plen - 1])) { + (*path)[plen] = FN_LIBCHAR; + (*path)[plen + 1] = 0; + } + strcat(*path + plen, p); + } + va_end(vl); +} + +static +bool +datadir_iter_next_file(datadir_iter_t *it) +{ + if (it->is_file && it->dbpath) { + make_path_n(2, &it->filepath, &it->filepath_len, + it->datadir_path, it->dbinfo.name); + + make_path_n(1, &it->filepath_rel, &it->filepath_rel_len, + it->dbinfo.name); + + it->is_empty_dir = false; + it->is_file = false; + + return(true); + } + + if (!it->dbpath || !it->dbdir) { + + return(false); + } + + while (os_file_readdir_next_file(it->dbpath, it->dbdir, + &it->fileinfo) == 0) { + + if (it->fileinfo.type == OS_FILE_TYPE_DIR) { + + continue; + } + + /* We found a symlink or a file */ + make_path_n(3, &it->filepath, &it->filepath_len, + it->datadir_path, it->dbinfo.name, + it->fileinfo.name); + + make_path_n(2, &it->filepath_rel, &it->filepath_rel_len, + it->dbinfo.name, it->fileinfo.name); + + it->is_empty_dir = false; + + return(true); + } + + return(false); +} + +static +bool +datadir_iter_next(datadir_iter_t *it, datadir_node_t *node) +{ + bool ret = true; + + os_mutex_enter(it->mutex); + + if (datadir_iter_next_file(it)) { + + datadir_node_fill(node, it); + + goto done; + } + + while (datadir_iter_next_database(it)) { + + if (datadir_iter_next_file(it)) { + + datadir_node_fill(node, it); + + goto done; + } + + make_path_n(2, &it->filepath, &it->filepath_len, + it->datadir_path, it->dbinfo.name); + + make_path_n(1, &it->filepath_rel, &it->filepath_rel_len, + it->dbinfo.name); + + it->is_empty_dir = true; + + datadir_node_fill(node, it); + + goto done; + } + + /* nothing found */ + ret = false; + +done: + os_mutex_exit(it->mutex); + + return(ret); +} + +/************************************************************************ +Interface to read MySQL data file sequentially. One should open file +with datafile_open to get cursor and close the cursor with +datafile_close. Cursor can not be shared between multiple +threads. */ +static +void +datadir_iter_free(datadir_iter_t *it) +{ + os_mutex_free(it->mutex); + + if (it->dbdir) { + + os_file_closedir(it->dbdir); + } + + if (it->dir) { + + os_file_closedir(it->dir); + } + + ut_free(it->dbpath); + ut_free(it->filepath); + ut_free(it->filepath_rel); + free(it->datadir_path); + ut_free(it); +} + + +/************************************************************************ +Holds the state needed to copy single data file. */ +struct datafile_cur_t { + os_file_t file; + char rel_path[FN_REFLEN]; + char abs_path[FN_REFLEN]; + MY_STAT statinfo; + uint thread_n; + byte* orig_buf; + byte* buf; + size_t buf_size; + size_t buf_read; + size_t buf_offset; +}; + +static +void +datafile_close(datafile_cur_t *cursor) +{ + if (cursor->file != 0) { + os_file_close(cursor->file); + } + ut_free(cursor->buf); +} + +static +bool +datafile_open(const char *file, datafile_cur_t *cursor, uint thread_n) +{ + ulint success; + + memset(cursor, 0, sizeof(datafile_cur_t)); + + strncpy(cursor->abs_path, file, sizeof(cursor->abs_path)); + + /* Get the relative path for the destination tablespace name, i.e. the + one that can be appended to the backup root directory. Non-system + tablespaces may have absolute paths for remote tablespaces in MySQL + 5.6+. We want to make "local" copies for the backup. */ + strncpy(cursor->rel_path, + xb_get_relative_path(cursor->abs_path, FALSE), + sizeof(cursor->rel_path)); + + cursor->file = os_file_create_simple_no_error_handling(0, + cursor->abs_path, + OS_FILE_OPEN, + OS_FILE_READ_ONLY, + &success, 0); + if (!success) { + /* The following call prints an error message */ + os_file_get_last_error(TRUE); + + msg("[%02u] error: cannot open " + "file %s\n", + thread_n, cursor->abs_path); + + return(false); + } + + if (!my_stat(cursor->abs_path, &cursor->statinfo, 0)) { + msg("[%02u] error: cannot stat %s\n", + thread_n, cursor->abs_path); + + datafile_close(cursor); + + return(false); + } + + posix_fadvise(cursor->file, 0, 0, POSIX_FADV_SEQUENTIAL); + + cursor->buf_size = 10 * 1024 * 1024; + cursor->buf = static_cast<byte *>(ut_malloc((ulint)cursor->buf_size)); + + return(true); +} + + +static +xb_fil_cur_result_t +datafile_read(datafile_cur_t *cursor) +{ + ulint success; + ulint to_read; + + xtrabackup_io_throttling(); + + to_read = (ulint)MY_MIN(cursor->statinfo.st_size - cursor->buf_offset, + cursor->buf_size); + + if (to_read == 0) { + return(XB_FIL_CUR_EOF); + } + + success = os_file_read(cursor->file, cursor->buf, cursor->buf_offset, + to_read); + if (!success) { + return(XB_FIL_CUR_ERROR); + } + + posix_fadvise(cursor->file, cursor->buf_offset, to_read, + POSIX_FADV_DONTNEED); + + cursor->buf_read = to_read; + cursor->buf_offset += to_read; + + return(XB_FIL_CUR_SUCCESS); +} + + + +/************************************************************************ +Check to see if a file exists. +Takes name of the file to check. +@return true if file exists. */ +static +bool +file_exists(const char *filename) +{ + MY_STAT stat_arg; + + if (!my_stat(filename, &stat_arg, MYF(0))) { + + return(false); + } + + return(true); +} + +/************************************************************************ +Trim leading slashes from absolute path so it becomes relative */ +static +const char * +trim_dotslash(const char *path) +{ + while (*path) { + if (is_path_separator(*path)) { + ++path; + continue; + } + if (*path == '.' && is_path_separator(path[1])) { + path += 2; + continue; + } + break; + } + + return(path); +} + + + +/************************************************************************ +Check if string ends with given suffix. +@return true if string ends with given suffix. */ +static +bool +ends_with(const char *str, const char *suffix) +{ + size_t suffix_len = strlen(suffix); + size_t str_len = strlen(str); + return(str_len >= suffix_len + && strcmp(str + str_len - suffix_len, suffix) == 0); +} + +static bool starts_with(const char *str, const char *prefix) +{ + return strncmp(str, prefix, strlen(prefix)) == 0; +} + +/************************************************************************ +Create directories recursively. +@return 0 if directories created successfully. */ +static +int +mkdirp(const char *pathname, int Flags, myf MyFlags) +{ + char parent[PATH_MAX], *p; + + /* make a parent directory path */ + strncpy(parent, pathname, sizeof(parent)); + parent[sizeof(parent) - 1] = 0; + + for (p = parent + strlen(parent); + !is_path_separator(*p) && p != parent; p--); + + *p = 0; + + /* try to make parent directory */ + if (p != parent && mkdirp(parent, Flags, MyFlags) != 0) { + return(-1); + } + + /* make this one if parent has been made */ + if (my_mkdir(pathname, Flags, MyFlags) == 0) { + return(0); + } + + /* if it already exists that is fine */ + if (errno == EEXIST) { + return(0); + } + + return(-1); +} + +/************************************************************************ +Return true if first and second arguments are the same path. */ +bool +equal_paths(const char *first, const char *second) +{ +#ifdef HAVE_REALPATH + char real_first[PATH_MAX]; + char real_second[PATH_MAX]; + + if (realpath(first, real_first) == NULL) { + return false; + } + if (realpath(second, real_second) == NULL) { + return false; + } + + return (strcmp(real_first, real_second) == 0); +#else + return strcmp(first, second) == 0; +#endif +} + +/************************************************************************ +Check if directory exists. Optionally create directory if doesn't +exist. +@return true if directory exists and if it was created successfully. */ +bool +directory_exists(const char *dir, bool create) +{ + os_file_dir_t os_dir; + MY_STAT stat_arg; + char errbuf[MYSYS_STRERROR_SIZE]; + + if (my_stat(dir, &stat_arg, MYF(0)) == NULL) { + + if (!create) { + return(false); + } + + if (mkdirp(dir, 0777, MYF(0)) < 0) { + my_strerror(errbuf, sizeof(errbuf), my_errno); + msg("Can not create directory %s: %s\n", dir, errbuf); + return(false); + + } + } + + /* could be symlink */ + os_dir = os_file_opendir(dir, FALSE); + + if (os_dir == NULL) { + my_strerror(errbuf, sizeof(errbuf), my_errno); + msg("Can not open directory %s: %s\n", dir, + errbuf); + + return(false); + } + + os_file_closedir(os_dir); + + return(true); +} + +/************************************************************************ +Check that directory exists and it is empty. */ +static +bool +directory_exists_and_empty(const char *dir, const char *comment) +{ + os_file_dir_t os_dir; + dberr_t err; + os_file_stat_t info; + bool empty; + + if (!directory_exists(dir, true)) { + return(false); + } + + os_dir = os_file_opendir(dir, FALSE); + + if (os_dir == NULL) { + msg("%s can not open directory %s\n", comment, dir); + return(false); + } + + empty = (fil_file_readdir_next_file(&err, dir, os_dir, &info) != 0); + + os_file_closedir(os_dir); + + if (!empty) { + msg("%s directory %s is not empty!\n", comment, dir); + } + + return(empty); +} + + +/************************************************************************ +Check if file name ends with given set of suffixes. +@return true if it does. */ +static +bool +filename_matches(const char *filename, const char **ext_list) +{ + const char **ext; + + for (ext = ext_list; *ext; ext++) { + if (ends_with(filename, *ext)) { + return(true); + } + } + + return(false); +} + + +/************************************************************************ +Copy data file for backup. Also check if it is allowed to copy by +comparing its name to the list of known data file types and checking +if passes the rules for partial backup. +@return true if file backed up or skipped successfully. */ +static +bool +datafile_copy_backup(const char *filepath, uint thread_n) +{ + const char *ext_list[] = {"frm", "isl", "MYD", "MYI", "MAD", "MAI", + "MRG", "TRG", "TRN", "ARM", "ARZ", "CSM", "CSV", "opt", "par", + NULL}; + + /* Get the name and the path for the tablespace. node->name always + contains the path (which may be absolute for remote tablespaces in + 5.6+). space->name contains the tablespace name in the form + "./database/table.ibd" (in 5.5-) or "database/table" (in 5.6+). For a + multi-node shared tablespace, space->name contains the name of the first + node, but that's irrelevant, since we only need node_name to match them + against filters, and the shared tablespace is always copied regardless + of the filters value. */ + + if (check_if_skip_table(filepath)) { + msg_ts("[%02u] Skipping %s.\n", thread_n, filepath); + return(true); + } + + if (filename_matches(filepath, ext_list)) { + return copy_file(ds_data, filepath, filepath, thread_n); + } + + return(true); +} + + +/************************************************************************ +Same as datafile_copy_backup, but put file name into the list for +rsync command. */ +static +bool +datafile_rsync_backup(const char *filepath, bool save_to_list, FILE *f) +{ + const char *ext_list[] = {"frm", "isl", "MYD", "MYI", "MAD", "MAI", + "MRG", "TRG", "TRN", "ARM", "ARZ", "CSM", "CSV", "opt", "par", + NULL}; + + /* Get the name and the path for the tablespace. node->name always + contains the path (which may be absolute for remote tablespaces in + 5.6+). space->name contains the tablespace name in the form + "./database/table.ibd" (in 5.5-) or "database/table" (in 5.6+). For a + multi-node shared tablespace, space->name contains the name of the first + node, but that's irrelevant, since we only need node_name to match them + against filters, and the shared tablespace is always copied regardless + of the filters value. */ + + if (check_if_skip_table(filepath)) { + return(true); + } + + if (filename_matches(filepath, ext_list)) { + fprintf(f, "%s\n", filepath); + if (save_to_list) { + rsync_list.insert(filepath); + } + } + + return(true); +} + + +static +bool +backup_file_vprintf(const char *filename, const char *fmt, va_list ap) +{ + ds_file_t *dstfile = NULL; + MY_STAT stat; /* unused for now */ + char *buf = 0; + int buf_len; + const char *action; + + memset(&stat, 0, sizeof(stat)); + + buf_len = vasprintf(&buf, fmt, ap); + + stat.st_size = buf_len; + stat.st_mtime = my_time(0); + + dstfile = ds_open(ds_data, filename, &stat); + if (dstfile == NULL) { + msg("[%02u] error: " + "cannot open the destination stream for %s\n", + 0, filename); + goto error; + } + + action = xb_get_copy_action("Writing"); + msg_ts("[%02u] %s %s\n", 0, action, filename); + + if (buf_len == -1) { + goto error; + } + + if (ds_write(dstfile, buf, buf_len)) { + goto error; + } + + /* close */ + msg_ts("[%02u] ...done\n", 0); + free(buf); + + if (ds_close(dstfile)) { + goto error_close; + } + + return(true); + +error: + free(buf); + if (dstfile != NULL) { + ds_close(dstfile); + } + +error_close: + msg("[%02u] Error: backup file failed.\n", 0); + return(false); /*ERROR*/ +} + + +bool +backup_file_printf(const char *filename, const char *fmt, ...) +{ + bool result; + va_list ap; + + va_start(ap, fmt); + + result = backup_file_vprintf(filename, fmt, ap); + + va_end(ap); + + return(result); +} + +static +bool +run_data_threads(datadir_iter_t *it, os_thread_func_t func, uint n) +{ + datadir_thread_ctxt_t *data_threads; + uint i, count; + os_ib_mutex_t count_mutex; + bool ret; + + data_threads = (datadir_thread_ctxt_t*) + (ut_malloc(sizeof(datadir_thread_ctxt_t) * n)); + + count_mutex = os_mutex_create(); + count = n; + + for (i = 0; i < n; i++) { + data_threads[i].it = it; + data_threads[i].n_thread = i + 1; + data_threads[i].count = &count; + data_threads[i].count_mutex = count_mutex; + os_thread_create(func, data_threads + i, &data_threads[i].id); + } + + /* Wait for threads to exit */ + while (1) { + os_thread_sleep(100000); + os_mutex_enter(count_mutex); + if (count == 0) { + os_mutex_exit(count_mutex); + break; + } + os_mutex_exit(count_mutex); + } + + os_mutex_free(count_mutex); + + ret = true; + for (i = 0; i < n; i++) { + ret = data_threads[i].ret && ret; + if (!data_threads[i].ret) { + msg("Error: thread %u failed.\n", i); + } + } + + ut_free(data_threads); + + return(ret); +} + + +/************************************************************************ +Copy file for backup/restore. +@return true in case of success. */ +bool +copy_file(ds_ctxt_t *datasink, + const char *src_file_path, + const char *dst_file_path, + uint thread_n) +{ + char dst_name[FN_REFLEN]; + ds_file_t *dstfile = NULL; + datafile_cur_t cursor; + xb_fil_cur_result_t res; + const char *action; + + if (!datafile_open(src_file_path, &cursor, thread_n)) { + goto error_close; + } + + strncpy(dst_name, cursor.rel_path, sizeof(dst_name)); + + dstfile = ds_open(datasink, trim_dotslash(dst_file_path), + &cursor.statinfo); + if (dstfile == NULL) { + msg("[%02u] error: " + "cannot open the destination stream for %s\n", + thread_n, dst_name); + goto error; + } + + action = xb_get_copy_action(); + msg_ts("[%02u] %s %s to %s\n", + thread_n, action, src_file_path, dstfile->path); + + /* The main copy loop */ + while ((res = datafile_read(&cursor)) == XB_FIL_CUR_SUCCESS) { + + if (ds_write(dstfile, cursor.buf, cursor.buf_read)) { + goto error; + } + } + + if (res == XB_FIL_CUR_ERROR) { + goto error; + } + + /* close */ + msg_ts("[%02u] ...done\n", thread_n); + datafile_close(&cursor); + if (ds_close(dstfile)) { + goto error_close; + } + return(true); + +error: + datafile_close(&cursor); + if (dstfile != NULL) { + ds_close(dstfile); + } + +error_close: + msg("[%02u] Error: copy_file() failed.\n", thread_n); + return(false); /*ERROR*/ +} + + +/************************************************************************ +Try to move file by renaming it. If source and destination are on +different devices fall back to copy and unlink. +@return true in case of success. */ +static +bool +move_file(ds_ctxt_t *datasink, + const char *src_file_path, + const char *dst_file_path, + const char *dst_dir, uint thread_n) +{ + char errbuf[MYSYS_STRERROR_SIZE]; + char dst_file_path_abs[FN_REFLEN]; + char dst_dir_abs[FN_REFLEN]; + size_t dirname_length; + + ut_snprintf(dst_file_path_abs, sizeof(dst_file_path_abs), + "%s/%s", dst_dir, dst_file_path); + + dirname_part(dst_dir_abs, dst_file_path_abs, &dirname_length); + + if (!directory_exists(dst_dir_abs, true)) { + return(false); + } + + if (file_exists(dst_file_path_abs)) { + msg("Error: Move file %s to %s failed: Destination " + "file exists\n", + src_file_path, dst_file_path_abs); + return(false); + } + + msg_ts("[%02u] Moving %s to %s\n", + thread_n, src_file_path, dst_file_path_abs); + + if (my_rename(src_file_path, dst_file_path_abs, MYF(0)) != 0) { + if (my_errno == EXDEV) { + bool ret; + ret = copy_file(datasink, src_file_path, + dst_file_path, thread_n); + msg_ts("[%02u] Removing %s\n", thread_n, src_file_path); + if (unlink(src_file_path) != 0) { + my_strerror(errbuf, sizeof(errbuf), errno); + msg("Error: unlink %s failed: %s\n", + src_file_path, + errbuf); + } + return(ret); + } + my_strerror(errbuf, sizeof(errbuf), my_errno); + msg("Can not move file %s to %s: %s\n", + src_file_path, dst_file_path_abs, + errbuf); + return(false); + } + + msg_ts("[%02u] ...done\n", thread_n); + + return(true); +} + + +/************************************************************************ +Read link from .isl file if any and store it in the global map associated +with given tablespace. */ +static +void +read_link_file(const char *ibd_filepath, const char *link_filepath) +{ + char *filepath= NULL; + + FILE *file = fopen(link_filepath, "r+b"); + if (file) { + filepath = static_cast<char*>(malloc(OS_FILE_MAX_PATH)); + + os_file_read_string(file, filepath, OS_FILE_MAX_PATH); + fclose(file); + + if (strlen(filepath)) { + /* Trim whitespace from end of filepath */ + ulint lastch = strlen(filepath) - 1; + while (lastch > 4 && filepath[lastch] <= 0x20) { + filepath[lastch--] = 0x00; + } + srv_normalize_path_for_win(filepath); + } + + tablespace_locations[ibd_filepath] = filepath; + } + free(filepath); +} + + +/************************************************************************ +Return the location of given .ibd if it was previously read +from .isl file. +@return NULL or destination .ibd file path. */ +static +const char * +tablespace_filepath(const char *ibd_filepath) +{ + std::map<std::string, std::string>::iterator it; + + it = tablespace_locations.find(ibd_filepath); + + if (it != tablespace_locations.end()) { + return it->second.c_str(); + } + + return NULL; +} + + +/************************************************************************ +Copy or move file depending on current mode. +@return true in case of success. */ +static +bool +copy_or_move_file(const char *src_file_path, + const char *dst_file_path, + const char *dst_dir, + uint thread_n) +{ + ds_ctxt_t *datasink = ds_data; /* copy to datadir by default */ + char filedir[FN_REFLEN]; + size_t filedir_len; + bool ret; + + /* read the link from .isl file */ + if (ends_with(src_file_path, ".isl")) { + char *ibd_filepath; + + ibd_filepath = strdup(src_file_path); + strcpy(ibd_filepath + strlen(ibd_filepath) - 3, "ibd"); + + read_link_file(ibd_filepath, src_file_path); + + free(ibd_filepath); + } + + /* check if there is .isl file */ + if (ends_with(src_file_path, ".ibd")) { + char *link_filepath; + const char *filepath; + + link_filepath = strdup(src_file_path); + strcpy(link_filepath + strlen(link_filepath) - 3, "isl"); + + read_link_file(src_file_path, link_filepath); + + filepath = tablespace_filepath(src_file_path); + + if (filepath != NULL) { + dirname_part(filedir, filepath, &filedir_len); + + dst_file_path = filepath + filedir_len; + dst_dir = filedir; + + if (!directory_exists(dst_dir, true)) { + ret = false; + goto cleanup; + } + + datasink = ds_create(dst_dir, DS_TYPE_LOCAL); + } + + free(link_filepath); + } + + ret = (xtrabackup_copy_back ? + copy_file(datasink, src_file_path, dst_file_path, thread_n) : + move_file(datasink, src_file_path, dst_file_path, + dst_dir, thread_n)); + +cleanup: + + if (datasink != ds_data) { + ds_destroy(datasink); + } + + return(ret); +} + + + + +bool +backup_files(const char *from, bool prep_mode) +{ + char rsync_tmpfile_name[FN_REFLEN]; + FILE *rsync_tmpfile = NULL; + datadir_iter_t *it; + datadir_node_t node; + bool ret = true; + + if (prep_mode && !opt_rsync) { + return(true); + } + + if (opt_rsync) { + snprintf(rsync_tmpfile_name, sizeof(rsync_tmpfile_name), + "%s/%s%d", opt_mysql_tmpdir, + "xtrabackup_rsyncfiles_pass", + prep_mode ? 1 : 2); + rsync_tmpfile = fopen(rsync_tmpfile_name, "w"); + if (rsync_tmpfile == NULL) { + msg("Error: can't create file %s\n", + rsync_tmpfile_name); + return(false); + } + } + + msg_ts("Starting %s non-InnoDB tables and files\n", + prep_mode ? "prep copy of" : "to backup"); + + datadir_node_init(&node); + it = datadir_iter_new(from); + + while (datadir_iter_next(it, &node)) { + + if (!node.is_empty_dir) { + if (opt_rsync) { + ret = datafile_rsync_backup(node.filepath, + !prep_mode, rsync_tmpfile); + } else { + ret = datafile_copy_backup(node.filepath, 1); + } + if (!ret) { + msg("Failed to copy file %s\n", node.filepath); + goto out; + } + } else if (!prep_mode) { + /* backup fake file into empty directory */ + char path[FN_REFLEN]; + ut_snprintf(path, sizeof(path), + "%s/db.opt", node.filepath); + if (!(ret = backup_file_printf( + trim_dotslash(path), "%s", ""))) { + msg("Failed to create file %s\n", path); + goto out; + } + } + } + + if (opt_rsync) { + std::stringstream cmd; + int err; + + if (buffer_pool_filename && file_exists(buffer_pool_filename)) { + fprintf(rsync_tmpfile, "%s\n", buffer_pool_filename); + rsync_list.insert(buffer_pool_filename); + } + if (file_exists("ib_lru_dump")) { + fprintf(rsync_tmpfile, "%s\n", "ib_lru_dump"); + rsync_list.insert("ib_lru_dump"); + } + + fclose(rsync_tmpfile); + rsync_tmpfile = NULL; + + cmd << "rsync -t . --files-from=" << rsync_tmpfile_name + << " " << xtrabackup_target_dir; + + msg_ts("Starting rsync as: %s\n", cmd.str().c_str()); + if ((err = system(cmd.str().c_str()) && !prep_mode) != 0) { + msg_ts("Error: rsync failed with error code %d\n", err); + ret = false; + goto out; + } + msg_ts("rsync finished successfully.\n"); + + if (!prep_mode && !opt_no_lock) { + char path[FN_REFLEN]; + char dst_path[FN_REFLEN]; + char *newline; + + /* Remove files that have been removed between first and + second passes. Cannot use "rsync --delete" because it + does not work with --files-from. */ + snprintf(rsync_tmpfile_name, sizeof(rsync_tmpfile_name), + "%s/%s", opt_mysql_tmpdir, + "xtrabackup_rsyncfiles_pass1"); + + rsync_tmpfile = fopen(rsync_tmpfile_name, "r"); + if (rsync_tmpfile == NULL) { + msg("Error: can't open file %s\n", + rsync_tmpfile_name); + return(false); + } + + while (fgets(path, sizeof(path), rsync_tmpfile)) { + + newline = strchr(path, '\n'); + if (newline) { + *newline = 0; + } + if (rsync_list.count(path) < 1) { + snprintf(dst_path, sizeof(dst_path), + "%s/%s", xtrabackup_target_dir, + path); + msg_ts("Removing %s\n", dst_path); + unlink(dst_path); + } + } + + fclose(rsync_tmpfile); + rsync_tmpfile = NULL; + } + } + + msg_ts("Finished %s non-InnoDB tables and files\n", + prep_mode ? "a prep copy of" : "backing up"); + +out: + datadir_iter_free(it); + datadir_node_free(&node); + + if (rsync_tmpfile != NULL) { + fclose(rsync_tmpfile); + } + + return(ret); +} + +bool +backup_start() +{ + if (!opt_no_lock) { + if (opt_safe_slave_backup) { + if (!wait_for_safe_slave(mysql_connection)) { + return(false); + } + } + + if (!backup_files(fil_path_to_mysql_datadir, true)) { + return(false); + } + + history_lock_time = time(NULL); + + if (!lock_tables(mysql_connection)) { + return(false); + } + } + + if (!backup_files(fil_path_to_mysql_datadir, false)) { + return(false); + } + + if (!backup_files_from_datadir(fil_path_to_mysql_datadir)) { + return false; + } + + // There is no need to stop slave thread before coping non-Innodb data when + // --no-lock option is used because --no-lock option requires that no DDL or + // DML to non-transaction tables can occur. + if (opt_no_lock) { + if (opt_safe_slave_backup) { + if (!wait_for_safe_slave(mysql_connection)) { + return(false); + } + } + } + + if (opt_slave_info) { + lock_binlog_maybe(mysql_connection); + + if (!write_slave_info(mysql_connection)) { + return(false); + } + } + + /* The only reason why Galera/binlog info is written before + wait_for_ibbackup_log_copy_finish() is that after that call the xtrabackup + binary will start streamig a temporary copy of REDO log to stdout and + thus, any streaming from innobackupex would interfere. The only way to + avoid that is to have a single process, i.e. merge innobackupex and + xtrabackup. */ + if (opt_galera_info) { + if (!write_galera_info(mysql_connection)) { + return(false); + } + write_current_binlog_file(mysql_connection); + } + + if (opt_binlog_info == BINLOG_INFO_ON) { + + lock_binlog_maybe(mysql_connection); + write_binlog_info(mysql_connection); + } + + if (have_flush_engine_logs) { + msg_ts("Executing FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS...\n"); + xb_mysql_query(mysql_connection, + "FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS", false); + } + + return(true); +} + + +bool +backup_finish() +{ + /* release all locks */ + if (!opt_no_lock) { + unlock_all(mysql_connection); + history_lock_time = 0; + } else { + history_lock_time = time(NULL) - history_lock_time; + } + + if (opt_safe_slave_backup && sql_thread_started) { + msg("Starting slave SQL thread\n"); + xb_mysql_query(mysql_connection, + "START SLAVE SQL_THREAD", false); + } + + /* Copy buffer pool dump or LRU dump */ + if (!opt_rsync) { + if (buffer_pool_filename && file_exists(buffer_pool_filename)) { + const char *dst_name; + + dst_name = trim_dotslash(buffer_pool_filename); + copy_file(ds_data, buffer_pool_filename, dst_name, 0); + } + if (file_exists("ib_lru_dump")) { + copy_file(ds_data, "ib_lru_dump", "ib_lru_dump", 0); + } + } + + msg_ts("Backup created in directory '%s'\n", xtrabackup_target_dir); + if (mysql_binlog_position != NULL) { + msg("MySQL binlog position: %s\n", mysql_binlog_position); + } + if (mysql_slave_position && opt_slave_info) { + msg("MySQL slave binlog position: %s\n", + mysql_slave_position); + } + + if (!write_backup_config_file()) { + return(false); + } + + if (!write_xtrabackup_info(mysql_connection)) { + return(false); + } + + + + return(true); +} + +bool +ibx_copy_incremental_over_full() +{ + const char *ext_list[] = {"frm", "isl", "MYD", "MYI", "MAD", "MAI", + "MRG", "TRG", "TRN", "ARM", "ARZ", "CSM", "CSV", "opt", "par", + NULL}; + const char *sup_files[] = {"xtrabackup_binlog_info", + "xtrabackup_galera_info", + "xtrabackup_slave_info", + "xtrabackup_info", + "ib_lru_dump", + NULL}; + datadir_iter_t *it = NULL; + datadir_node_t node; + bool ret = true; + char path[FN_REFLEN]; + int i; + + datadir_node_init(&node); + + /* If we were applying an incremental change set, we need to make + sure non-InnoDB files and xtrabackup_* metainfo files are copied + to the full backup directory. */ + + if (xtrabackup_incremental) { + + ds_data = ds_create(xtrabackup_target_dir, DS_TYPE_LOCAL); + + it = datadir_iter_new(xtrabackup_incremental_dir); + + while (datadir_iter_next(it, &node)) { + + /* copy only non-innodb files */ + + if (node.is_empty_dir + || !filename_matches(node.filepath, ext_list)) { + continue; + } + + if (file_exists(node.filepath_rel)) { + unlink(node.filepath_rel); + } + + if (!(ret = copy_file(ds_data, node.filepath, + node.filepath_rel, 1))) { + msg("Failed to copy file %s\n", + node.filepath); + goto cleanup; + } + } + + /* copy buffer pool dump */ + if (innobase_buffer_pool_filename) { + const char *src_name; + + src_name = trim_dotslash(innobase_buffer_pool_filename); + + snprintf(path, sizeof(path), "%s/%s", + xtrabackup_incremental_dir, + src_name); + + if (file_exists(path)) { + copy_file(ds_data, path, + innobase_buffer_pool_filename, 0); + } + } + + /* copy supplementary files */ + + for (i = 0; sup_files[i]; i++) { + snprintf(path, sizeof(path), "%s/%s", + xtrabackup_incremental_dir, + sup_files[i]); + + if (file_exists(path)) + { + if (file_exists(sup_files[i])) { + unlink(sup_files[i]); + } + copy_file(ds_data, path, sup_files[i], 0); + } + } + + } + +cleanup: + if (it != NULL) { + datadir_iter_free(it); + } + + if (ds_data != NULL) { + ds_destroy(ds_data); + } + + datadir_node_free(&node); + + return(ret); +} + +bool +ibx_cleanup_full_backup() +{ + const char *ext_list[] = {"delta", "meta", "ibd", NULL}; + datadir_iter_t *it = NULL; + datadir_node_t node; + bool ret = true; + + datadir_node_init(&node); + + /* If we are applying an incremental change set, we need to make + sure non-InnoDB files are cleaned up from full backup dir before + we copy files from incremental dir. */ + + it = datadir_iter_new(xtrabackup_target_dir); + + while (datadir_iter_next(it, &node)) { + + if (node.is_empty_dir) { +#ifdef _WIN32 + DeleteFile(node.filepath); +#else + rmdir(node.filepath); +#endif + } + + if (xtrabackup_incremental && !node.is_empty_dir + && !filename_matches(node.filepath, ext_list)) { + unlink(node.filepath); + } + } + + datadir_iter_free(it); + + datadir_node_free(&node); + + return(ret); +} + +bool +apply_log_finish() +{ + if (!ibx_cleanup_full_backup() + || !ibx_copy_incremental_over_full()) { + return(false); + } + + return(true); +} + +extern void +os_io_init_simple(void); + +bool +copy_back() +{ + char *innobase_data_file_path_copy; + ulint i; + bool ret; + datadir_iter_t *it = NULL; + datadir_node_t node; + char *dst_dir; + + memset(&node, 0, sizeof(node)); + + if (!opt_force_non_empty_dirs) { + if (!directory_exists_and_empty(mysql_data_home, + "Original data")) { + return(false); + } + } else { + if (!directory_exists(mysql_data_home, true)) { + return(false); + } + } + if (srv_undo_dir && *srv_undo_dir + && !directory_exists(srv_undo_dir, true)) { + return(false); + } + if (innobase_data_home_dir && *innobase_data_home_dir + && !directory_exists(innobase_data_home_dir, true)) { + return(false); + } + if (srv_log_group_home_dir && *srv_log_group_home_dir + && !directory_exists(srv_log_group_home_dir, true)) { + return(false); + } + + /* cd to backup directory */ + if (my_setwd(xtrabackup_target_dir, MYF(MY_WME))) + { + msg("cannot my_setwd %s\n", xtrabackup_target_dir); + return(false); + } + + /* parse data file path */ + + if (!innobase_data_file_path) { + innobase_data_file_path = (char*) "ibdata1:10M:autoextend"; + } + innobase_data_file_path_copy = strdup(innobase_data_file_path); + + if (!(ret = srv_parse_data_file_paths_and_sizes( + innobase_data_file_path_copy))) { + msg("syntax error in innodb_data_file_path\n"); + return(false); + } + + srv_max_n_threads = 1000; + //os_sync_mutex = NULL; + ut_mem_init(); + /* temporally dummy value to avoid crash */ + srv_page_size_shift = 14; + srv_page_size = (1 << srv_page_size_shift); + os_sync_init(); + sync_init(); + os_io_init_simple(); + mem_init(srv_mem_pool_size); + ut_crc32_init(); + + /* copy undo tablespaces */ + if (srv_undo_tablespaces > 0) { + + dst_dir = (srv_undo_dir && *srv_undo_dir) + ? srv_undo_dir : mysql_data_home; + + ds_data = ds_create(dst_dir, DS_TYPE_LOCAL); + + for (i = 1; i <= srv_undo_tablespaces; i++) { + char filename[20]; + sprintf(filename, "undo%03u", (uint)i); + if (!(ret = copy_or_move_file(filename, filename, + dst_dir, 1))) { + goto cleanup; + } + } + + ds_destroy(ds_data); + ds_data = NULL; + } + + /* copy redo logs */ + + dst_dir = (srv_log_group_home_dir && *srv_log_group_home_dir) + ? srv_log_group_home_dir : mysql_data_home; + + ds_data = ds_create(dst_dir, DS_TYPE_LOCAL); + + for (i = 0; i < (ulong)innobase_log_files_in_group; i++) { + char filename[20]; + sprintf(filename, "ib_logfile%lu", i); + + if (!file_exists(filename)) { + continue; + } + + if (!(ret = copy_or_move_file(filename, filename, + dst_dir, 1))) { + goto cleanup; + } + } + + ds_destroy(ds_data); + ds_data = NULL; + + /* copy innodb system tablespace(s) */ + + dst_dir = (innobase_data_home_dir && *innobase_data_home_dir) + ? innobase_data_home_dir : mysql_data_home; + + ds_data = ds_create(dst_dir, DS_TYPE_LOCAL); + + for (i = 0; i < srv_n_data_files; i++) { + const char *filename = base_name(srv_data_file_names[i]); + + if (!(ret = copy_or_move_file(filename, srv_data_file_names[i], + dst_dir, 1))) { + goto cleanup; + } + } + + ds_destroy(ds_data); + ds_data = NULL; + + /* copy the rest of tablespaces */ + ds_data = ds_create(mysql_data_home, DS_TYPE_LOCAL); + + it = datadir_iter_new(".", false); + + datadir_node_init(&node); + + while (datadir_iter_next(it, &node)) { + const char *ext_list[] = {"backup-my.cnf", "xtrabackup_logfile", + "xtrabackup_binary", "xtrabackup_binlog_info", + "xtrabackup_checkpoints", ".qp", ".pmap", ".tmp", + ".xbcrypt", NULL}; + const char *filename; + char c_tmp; + int i_tmp; + bool is_ibdata_file; + + /* create empty directories */ + if (node.is_empty_dir) { + char path[FN_REFLEN]; + + snprintf(path, sizeof(path), "%s/%s", + mysql_data_home, node.filepath_rel); + + msg_ts("[%02u] Creating directory %s\n", 1, path); + + if (mkdirp(path, 0777, MYF(0)) < 0) { + char errbuf[MYSYS_STRERROR_SIZE]; + my_strerror(errbuf, sizeof(errbuf), my_errno); + msg("Can not create directory %s: %s\n", + path, errbuf); + ret = false; + + goto cleanup; + + } + + msg_ts("[%02u] ...done.", 1); + + continue; + } + + filename = base_name(node.filepath); + + /* skip .qp and .xbcrypt files */ + if (filename_matches(filename, ext_list)) { + continue; + } + + /* skip undo tablespaces */ + if (sscanf(filename, "undo%d%c", &i_tmp, &c_tmp) == 1) { + continue; + } + + /* skip redo logs */ + if (sscanf(filename, "ib_logfile%d%c", &i_tmp, &c_tmp) == 1) { + continue; + } + + /* skip innodb data files */ + is_ibdata_file = false; + for (i = 0; i < srv_n_data_files; i++) { + const char *ibfile; + + ibfile = base_name(srv_data_file_names[i]); + + if (strcmp(ibfile, filename) == 0) { + is_ibdata_file = true; + continue; + } + } + if (is_ibdata_file) { + continue; + } + + if (!(ret = copy_or_move_file(node.filepath, node.filepath_rel, + mysql_data_home, 1))) { + goto cleanup; + } + } + + /* copy buufer pool dump */ + + if (innobase_buffer_pool_filename) { + const char *src_name; + char path[FN_REFLEN]; + + src_name = trim_dotslash(innobase_buffer_pool_filename); + + snprintf(path, sizeof(path), "%s/%s", + mysql_data_home, + src_name); + + /* could be already copied with other files + from data directory */ + if (file_exists(src_name) && + !file_exists(innobase_buffer_pool_filename)) { + copy_or_move_file(src_name, + innobase_buffer_pool_filename, + mysql_data_home, 0); + } + } + +cleanup: + if (it != NULL) { + datadir_iter_free(it); + } + + datadir_node_free(&node); + + free(innobase_data_file_path_copy); + + if (ds_data != NULL) { + ds_destroy(ds_data); + } + + ds_data = NULL; + + //os_sync_free(); + mem_close(); + //os_sync_mutex = NULL; + ut_free_all_mem(); + sync_close(); + sync_initialized = FALSE; + return(ret); +} + +bool +decrypt_decompress_file(const char *filepath, uint thread_n) +{ + std::stringstream cmd, message; + char *dest_filepath = strdup(filepath); + bool needs_action = false; + + cmd << IF_WIN("type ","cat ") << filepath; + + if (ends_with(filepath, ".xbcrypt") && opt_decrypt) { + cmd << " | xbcrypt --decrypt --encrypt-algo=" + << xtrabackup_encrypt_algo_names[opt_decrypt_algo]; + if (xtrabackup_encrypt_key) { + cmd << " --encrypt-key=" << xtrabackup_encrypt_key; + } else { + cmd << " --encrypt-key-file=" + << xtrabackup_encrypt_key_file; + } + dest_filepath[strlen(dest_filepath) - 8] = 0; + message << "decrypting"; + needs_action = true; + } + + if (opt_decompress + && (ends_with(filepath, ".qp") + || (ends_with(filepath, ".qp.xbcrypt") + && opt_decrypt))) { + cmd << " | qpress -dio "; + dest_filepath[strlen(dest_filepath) - 3] = 0; + if (needs_action) { + message << " and "; + } + message << "decompressing"; + needs_action = true; + } + + cmd << " > " << dest_filepath; + message << " " << filepath; + + free(dest_filepath); + + if (needs_action) { + + msg_ts("[%02u] %s\n", thread_n, message.str().c_str()); + + if (system(cmd.str().c_str()) != 0) { + return(false); + } + + if (opt_remove_original) { + msg_ts("[%02u] removing %s\n", thread_n, filepath); + if (my_delete(filepath, MYF(MY_WME)) != 0) { + return(false); + } + } + } + + return(true); +} + +static +os_thread_ret_t STDCALL +decrypt_decompress_thread_func(void *arg) +{ + bool ret = true; + datadir_node_t node; + datadir_thread_ctxt_t *ctxt = (datadir_thread_ctxt_t *)(arg); + + datadir_node_init(&node); + + while (datadir_iter_next(ctxt->it, &node)) { + + /* skip empty directories in backup */ + if (node.is_empty_dir) { + continue; + } + + if (!ends_with(node.filepath, ".qp") + && !ends_with(node.filepath, ".xbcrypt")) { + continue; + } + + if (!(ret = decrypt_decompress_file(node.filepath, + ctxt->n_thread))) { + goto cleanup; + } + } + +cleanup: + + datadir_node_free(&node); + + os_mutex_enter(ctxt->count_mutex); + --(*ctxt->count); + os_mutex_exit(ctxt->count_mutex); + + ctxt->ret = ret; + + os_thread_exit(NULL); + OS_THREAD_DUMMY_RETURN; +} + +bool +decrypt_decompress() +{ + bool ret; + datadir_iter_t *it = NULL; + + srv_max_n_threads = 1000; + //os_sync_mutex = NULL; + ut_mem_init(); + os_sync_init(); + sync_init(); + + /* cd to backup directory */ + if (my_setwd(xtrabackup_target_dir, MYF(MY_WME))) + { + msg("cannot my_setwd %s\n", xtrabackup_target_dir); + return(false); + } + + /* copy the rest of tablespaces */ + ds_data = ds_create(".", DS_TYPE_LOCAL); + + it = datadir_iter_new(".", false); + + ut_a(xtrabackup_parallel >= 0); + + ret = run_data_threads(it, decrypt_decompress_thread_func, + xtrabackup_parallel ? xtrabackup_parallel : 1); + + if (it != NULL) { + datadir_iter_free(it); + } + + if (ds_data != NULL) { + ds_destroy(ds_data); + } + + ds_data = NULL; + + sync_close(); + sync_initialized = FALSE; + //os_sync_free(); + //os_sync_mutex = NULL; + ut_free_all_mem(); + + return(ret); +} + +/* + Copy some files from top level datadir. + Do not copy the Innodb files (ibdata1, redo log files), + as this is done in a separate step. +*/ +static bool backup_files_from_datadir(const char *dir_path) +{ + os_file_dir_t dir = os_file_opendir(dir_path, TRUE); + os_file_stat_t info; + bool ret = true; + while (os_file_readdir_next_file(dir_path, dir, &info) == 0) { + + if (info.type != OS_FILE_TYPE_FILE) + continue; + + const char *pname = strrchr(info.name, IF_WIN('\\', '/')); + if (!pname) + pname = info.name; + + /* Copy aria log files, and aws keys for encryption plugins.*/ + const char *prefixes[] = { "aria_log", "aws-kms-key" }; + for (size_t i = 0; i < array_elements(prefixes); i++) { + if (starts_with(pname, prefixes[i])) { + ret = copy_file(ds_data, info.name, info.name, 1); + if (!ret) { + break; + } + } + } + } + os_file_closedir(dir); + return ret; +} diff --git a/extra/mariabackup/backup_copy.h b/extra/mariabackup/backup_copy.h new file mode 100644 index 00000000000..4b829982764 --- /dev/null +++ b/extra/mariabackup/backup_copy.h @@ -0,0 +1,49 @@ + +#ifndef XTRABACKUP_BACKUP_COPY_H +#define XTRABACKUP_BACKUP_COPY_H + +#include <my_global.h> +#include "datasink.h" + +/* special files */ +#define XTRABACKUP_SLAVE_INFO "xtrabackup_slave_info" +#define XTRABACKUP_GALERA_INFO "xtrabackup_galera_info" +#define XTRABACKUP_BINLOG_INFO "xtrabackup_binlog_info" +#define XTRABACKUP_INFO "xtrabackup_info" + +extern bool binlog_locked; + +bool +backup_file_printf(const char *filename, const char *fmt, ...) + ATTRIBUTE_FORMAT(printf, 2, 0); + +/************************************************************************ +Return true if first and second arguments are the same path. */ +bool +equal_paths(const char *first, const char *second); + +/************************************************************************ +Copy file for backup/restore. +@return true in case of success. */ +bool +copy_file(ds_ctxt_t *datasink, + const char *src_file_path, + const char *dst_file_path, + uint thread_n); + +bool +backup_start(); +bool +backup_finish(); +bool +apply_log_finish(); +bool +copy_back(); +bool +decrypt_decompress(); +bool +is_path_separator(char); +bool +directory_exists(const char *dir, bool create); + +#endif diff --git a/extra/mariabackup/backup_mysql.cc b/extra/mariabackup/backup_mysql.cc new file mode 100644 index 00000000000..6299afffc6e --- /dev/null +++ b/extra/mariabackup/backup_mysql.cc @@ -0,0 +1,1648 @@ +/****************************************************** +hot backup tool for InnoDB +(c) 2009-2015 Percona LLC and/or its affiliates +Originally Created 3/3/2009 Yasufumi Kinoshita +Written by Alexey Kopytov, Aleksandr Kuzminsky, Stewart Smith, Vadim Tkachenko, +Yasufumi Kinoshita, Ignacio Nin and Baron Schwartz. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +******************************************************* + +This file incorporates work covered by the following copyright and +permission notice: + +Copyright (c) 2000, 2011, MySQL AB & Innobase Oy. All Rights Reserved. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 59 Temple +Place, Suite 330, Boston, MA 02111-1307 USA + +*******************************************************/ +#define MYSQL_CLIENT + +#include <my_global.h> +#include <mysql.h> +#include <mysqld.h> +#include <my_sys.h> +#include <string.h> +#include <limits> +#include "common.h" +#include "xtrabackup.h" +#include "mysql_version.h" +#include "backup_copy.h" +#include "backup_mysql.h" +#include "mysqld.h" +#include "encryption_plugin.h" +#include <sstream> + + +char *tool_name; +char tool_args[2048]; + +/* mysql flavor and version */ +mysql_flavor_t server_flavor = FLAVOR_UNKNOWN; +unsigned long mysql_server_version = 0; + +/* server capabilities */ +bool have_changed_page_bitmaps = false; +bool have_backup_locks = false; +bool have_backup_safe_binlog_info = false; +bool have_lock_wait_timeout = false; +bool have_galera_enabled = false; +bool have_flush_engine_logs = false; +bool have_multi_threaded_slave = false; +bool have_gtid_slave = false; + +/* Kill long selects */ +os_thread_id_t kill_query_thread_id; +os_event_t kill_query_thread_started; +os_event_t kill_query_thread_stopped; +os_event_t kill_query_thread_stop; + +bool sql_thread_started = false; +char *mysql_slave_position = NULL; +char *mysql_binlog_position = NULL; +char *buffer_pool_filename = NULL; + +/* History on server */ +time_t history_start_time; +time_t history_end_time; +time_t history_lock_time; + +MYSQL *mysql_connection; + +my_bool opt_ssl_verify_server_cert; + +MYSQL * +xb_mysql_connect() +{ + MYSQL *connection = mysql_init(NULL); + char mysql_port_str[std::numeric_limits<int>::digits10 + 3]; + + sprintf(mysql_port_str, "%d", opt_port); + + if (connection == NULL) { + msg("Failed to init MySQL struct: %s.\n", + mysql_error(connection)); + return(NULL); + } + + if (!opt_secure_auth) { + mysql_options(connection, MYSQL_SECURE_AUTH, + (char *) &opt_secure_auth); + } + + msg_ts("Connecting to MySQL server host: %s, user: %s, password: %s, " + "port: %s, socket: %s\n", opt_host ? opt_host : "localhost", + opt_user ? opt_user : "not set", + opt_password ? "set" : "not set", + opt_port != 0 ? mysql_port_str : "not set", + opt_socket ? opt_socket : "not set"); + +#ifdef HAVE_OPENSSL + if (opt_use_ssl) + { + mysql_ssl_set(connection, opt_ssl_key, opt_ssl_cert, + opt_ssl_ca, opt_ssl_capath, + opt_ssl_cipher); + mysql_options(connection, MYSQL_OPT_SSL_CRL, opt_ssl_crl); + mysql_options(connection, MYSQL_OPT_SSL_CRLPATH, + opt_ssl_crlpath); + } + mysql_options(connection,MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + (char*)&opt_ssl_verify_server_cert); +#endif + + if (!mysql_real_connect(connection, + opt_host ? opt_host : "localhost", + opt_user, + opt_password, + "" /*database*/, opt_port, + opt_socket, 0)) { + msg("Failed to connect to MySQL server: %s.\n", + mysql_error(connection)); + mysql_close(connection); + return(NULL); + } + + xb_mysql_query(connection, "SET SESSION wait_timeout=2147483", + false, true); + + return(connection); +} + +/*********************************************************************//** +Execute mysql query. */ +MYSQL_RES * +xb_mysql_query(MYSQL *connection, const char *query, bool use_result, + bool die_on_error) +{ + MYSQL_RES *mysql_result = NULL; + + if (mysql_query(connection, query)) { + msg("Error: failed to execute query %s: %s\n", query, + mysql_error(connection)); + if (die_on_error) { + exit(EXIT_FAILURE); + } + return(NULL); + } + + /* store result set on client if there is a result */ + if (mysql_field_count(connection) > 0) { + if ((mysql_result = mysql_store_result(connection)) == NULL) { + msg("Error: failed to fetch query result %s: %s\n", + query, mysql_error(connection)); + exit(EXIT_FAILURE); + } + + if (!use_result) { + mysql_free_result(mysql_result); + } + } + + return mysql_result; +} + + +struct mysql_variable { + const char *name; + char **value; +}; + + +static +void +read_mysql_variables(MYSQL *connection, const char *query, mysql_variable *vars, + bool vertical_result) +{ + MYSQL_RES *mysql_result; + MYSQL_ROW row; + mysql_variable *var; + + mysql_result = xb_mysql_query(connection, query, true); + + ut_ad(!vertical_result || mysql_num_fields(mysql_result) == 2); + + if (vertical_result) { + while ((row = mysql_fetch_row(mysql_result))) { + char *name = row[0]; + char *value = row[1]; + for (var = vars; var->name; var++) { + if (strcmp(var->name, name) == 0 + && value != NULL) { + *(var->value) = strdup(value); + } + } + } + } else { + MYSQL_FIELD *field; + + if ((row = mysql_fetch_row(mysql_result)) != NULL) { + int i = 0; + while ((field = mysql_fetch_field(mysql_result)) + != NULL) { + char *name = field->name; + char *value = row[i]; + for (var = vars; var->name; var++) { + if (strcmp(var->name, name) == 0 + && value != NULL) { + *(var->value) = strdup(value); + } + } + ++i; + } + } + } + + mysql_free_result(mysql_result); +} + + +static +void +free_mysql_variables(mysql_variable *vars) +{ + mysql_variable *var; + + for (var = vars; var->name; var++) { + free(*(var->value)); + } +} + + +static +char * +read_mysql_one_value(MYSQL *connection, const char *query) +{ + MYSQL_RES *mysql_result; + MYSQL_ROW row; + char *result = NULL; + + mysql_result = xb_mysql_query(connection, query, true); + + ut_ad(mysql_num_fields(mysql_result) == 1); + + if ((row = mysql_fetch_row(mysql_result))) { + result = strdup(row[0]); + } + + mysql_free_result(mysql_result); + + return(result); +} + +static +bool +check_server_version(unsigned long version_number, + const char *version_string, + const char *version_comment, + const char *innodb_version) +{ + bool version_supported = false; + bool mysql51 = false; + + mysql_server_version = version_number; + + server_flavor = FLAVOR_UNKNOWN; + if (strstr(version_comment, "Percona") != NULL) { + server_flavor = FLAVOR_PERCONA_SERVER; + } else if (strstr(version_comment, "MariaDB") != NULL || + strstr(version_string, "MariaDB") != NULL) { + server_flavor = FLAVOR_MARIADB; + } else if (strstr(version_comment, "MySQL") != NULL) { + server_flavor = FLAVOR_MYSQL; + } + + mysql51 = version_number > 50100 && version_number < 50500; + version_supported = version_supported + || (mysql51 && innodb_version != NULL); + version_supported = version_supported + || (version_number > 50500 && version_number < 50700); + version_supported = version_supported + || ((version_number > 100000) + && server_flavor == FLAVOR_MARIADB); + + if (mysql51 && innodb_version == NULL) { + msg("Error: Built-in InnoDB in MySQL 5.1 is not " + "supported in this release. You can either use " + "Percona XtraBackup 2.0, or upgrade to InnoDB " + "plugin.\n"); + } else if (!version_supported) { + msg("Error: Unsupported server version: '%s'. Please " + "report a bug at " + "https://bugs.launchpad.net/percona-xtrabackup\n", + version_string); + } + + return(version_supported); +} + +/*********************************************************************//** +Receive options important for XtraBackup from MySQL server. +@return true on success. */ +bool +get_mysql_vars(MYSQL *connection) +{ + char *gtid_mode_var = NULL; + char *version_var = NULL; + char *version_comment_var = NULL; + char *innodb_version_var = NULL; + char *have_backup_locks_var = NULL; + char *have_backup_safe_binlog_info_var = NULL; + char *log_bin_var = NULL; + char *lock_wait_timeout_var= NULL; + char *wsrep_on_var = NULL; + char *slave_parallel_workers_var = NULL; + char *gtid_slave_pos_var = NULL; + char *innodb_buffer_pool_filename_var = NULL; + char *datadir_var = NULL; + char *innodb_log_group_home_dir_var = NULL; + char *innodb_log_file_size_var = NULL; + char *innodb_log_files_in_group_var = NULL; + char *innodb_data_file_path_var = NULL; + char *innodb_data_home_dir_var = NULL; + char *innodb_undo_directory_var = NULL; + char *innodb_page_size_var = NULL; + + unsigned long server_version = mysql_get_server_version(connection); + + bool ret = true; + + mysql_variable mysql_vars[] = { + {"have_backup_locks", &have_backup_locks_var}, + {"have_backup_safe_binlog_info", + &have_backup_safe_binlog_info_var}, + {"log_bin", &log_bin_var}, + {"lock_wait_timeout", &lock_wait_timeout_var}, + {"gtid_mode", >id_mode_var}, + {"version", &version_var}, + {"version_comment", &version_comment_var}, + {"innodb_version", &innodb_version_var}, + {"wsrep_on", &wsrep_on_var}, + {"slave_parallel_workers", &slave_parallel_workers_var}, + {"gtid_slave_pos", >id_slave_pos_var}, + {"innodb_buffer_pool_filename", + &innodb_buffer_pool_filename_var}, + {"datadir", &datadir_var}, + {"innodb_log_group_home_dir", &innodb_log_group_home_dir_var}, + {"innodb_log_file_size", &innodb_log_file_size_var}, + {"innodb_log_files_in_group", &innodb_log_files_in_group_var}, + {"innodb_data_file_path", &innodb_data_file_path_var}, + {"innodb_data_home_dir", &innodb_data_home_dir_var}, + {"innodb_undo_directory", &innodb_undo_directory_var}, + {"innodb_page_size", &innodb_page_size_var}, + {NULL, NULL} + }; + + read_mysql_variables(connection, "SHOW VARIABLES", + mysql_vars, true); + + if (have_backup_locks_var != NULL && !opt_no_backup_locks) { + have_backup_locks = true; + } + + if (opt_binlog_info == BINLOG_INFO_AUTO) { + + if (have_backup_safe_binlog_info_var != NULL) + opt_binlog_info = BINLOG_INFO_LOCKLESS; + else if (log_bin_var != NULL && !strcmp(log_bin_var, "ON")) + opt_binlog_info = BINLOG_INFO_ON; + else + opt_binlog_info = BINLOG_INFO_OFF; + } + + if (have_backup_safe_binlog_info_var == NULL && + opt_binlog_info == BINLOG_INFO_LOCKLESS) { + + msg("Error: --binlog-info=LOCKLESS is not supported by the " + "server\n"); + return(false); + } + + if (lock_wait_timeout_var != NULL) { + have_lock_wait_timeout = true; + } + + if (wsrep_on_var != NULL) { + have_galera_enabled = true; + } + + /* Check server version compatibility and detect server flavor */ + + if (!(ret = check_server_version(server_version, version_var, + version_comment_var, + innodb_version_var))) { + goto out; + } + + if (server_version > 50500) { + have_flush_engine_logs = true; + } + + if (slave_parallel_workers_var != NULL + && atoi(slave_parallel_workers_var) > 0) { + have_multi_threaded_slave = true; + } + + if (innodb_buffer_pool_filename_var != NULL) { + buffer_pool_filename = strdup(innodb_buffer_pool_filename_var); + } + + if ((gtid_mode_var && strcmp(gtid_mode_var, "ON") == 0) || + (gtid_slave_pos_var && *gtid_slave_pos_var)) { + have_gtid_slave = true; + } + + msg("Using server version %s\n", version_var); + + if (!(ret = detect_mysql_capabilities_for_backup())) { + goto out; + } + + /* make sure datadir value is the same in configuration file */ + if (check_if_param_set("datadir")) { + if (!directory_exists(mysql_data_home, false)) { + msg("Warning: option 'datadir' points to " + "nonexistent directory '%s'\n", mysql_data_home); + } + if (!directory_exists(datadir_var, false)) { + msg("Warning: MySQL variable 'datadir' points to " + "nonexistent directory '%s'\n", datadir_var); + } + if (!equal_paths(mysql_data_home, datadir_var)) { + msg("Warning: option 'datadir' has different " + "values:\n" + " '%s' in defaults file\n" + " '%s' in SHOW VARIABLES\n", + mysql_data_home, datadir_var); + } + } + + /* get some default values is they are missing from my.cnf */ + if (!check_if_param_set("datadir") && datadir_var && *datadir_var) { + strmake(mysql_real_data_home, datadir_var, FN_REFLEN - 1); + mysql_data_home= mysql_real_data_home; + } + + if (!check_if_param_set("innodb_data_file_path") + && innodb_data_file_path_var && *innodb_data_file_path_var) { + innobase_data_file_path = my_strdup( + innodb_data_file_path_var, MYF(MY_FAE)); + } + + if (!check_if_param_set("innodb_data_home_dir") + && innodb_data_home_dir_var && *innodb_data_home_dir_var) { + innobase_data_home_dir = my_strdup( + innodb_data_home_dir_var, MYF(MY_FAE)); + } + + if (!check_if_param_set("innodb_log_group_home_dir") + && innodb_log_group_home_dir_var + && *innodb_log_group_home_dir_var) { + srv_log_group_home_dir = my_strdup( + innodb_log_group_home_dir_var, MYF(MY_FAE)); + } + + if (!check_if_param_set("innodb_undo_directory") + && innodb_undo_directory_var && *innodb_undo_directory_var) { + srv_undo_dir = my_strdup( + innodb_undo_directory_var, MYF(MY_FAE)); + } + + if (!check_if_param_set("innodb_log_files_in_group") + && innodb_log_files_in_group_var) { + char *endptr; + + innobase_log_files_in_group = strtol( + innodb_log_files_in_group_var, &endptr, 10); + ut_ad(*endptr == 0); + } + + if (!check_if_param_set("innodb_log_file_size") + && innodb_log_file_size_var) { + char *endptr; + + innobase_log_file_size = strtoll( + innodb_log_file_size_var, &endptr, 10); + ut_ad(*endptr == 0); + } + + if (!check_if_param_set("innodb_page_size") && innodb_page_size_var) { + char *endptr; + + innobase_page_size = strtoll( + innodb_page_size_var, &endptr, 10); + ut_ad(*endptr == 0); + } + +out: + free_mysql_variables(mysql_vars); + + return(ret); +} + +/*********************************************************************//** +Query the server to find out what backup capabilities it supports. +@return true on success. */ +bool +detect_mysql_capabilities_for_backup() +{ + const char *query = "SELECT 'INNODB_CHANGED_PAGES', COUNT(*) FROM " + "INFORMATION_SCHEMA.PLUGINS " + "WHERE PLUGIN_NAME LIKE 'INNODB_CHANGED_PAGES'"; + char *innodb_changed_pages = NULL; + mysql_variable vars[] = { + {"INNODB_CHANGED_PAGES", &innodb_changed_pages}, {NULL, NULL}}; + + if (xtrabackup_incremental) { + + read_mysql_variables(mysql_connection, query, vars, true); + + ut_ad(innodb_changed_pages != NULL); + + have_changed_page_bitmaps = (atoi(innodb_changed_pages) == 1); + + /* INNODB_CHANGED_PAGES are listed in + INFORMATION_SCHEMA.PLUGINS in MariaDB, but + FLUSH NO_WRITE_TO_BINLOG CHANGED_PAGE_BITMAPS + is not supported for versions below 10.1.6 + (see MDEV-7472) */ + if (server_flavor == FLAVOR_MARIADB && + mysql_server_version < 100106) { + have_changed_page_bitmaps = false; + } + + free_mysql_variables(vars); + } + + /* do some sanity checks */ + if (opt_galera_info && !have_galera_enabled) { + msg("--galera-info is specified on the command " + "line, but the server does not support Galera " + "replication. Ignoring the option.\n"); + opt_galera_info = false; + } + + if (opt_slave_info && have_multi_threaded_slave && + !have_gtid_slave) { + msg("The --slave-info option requires GTID enabled for a " + "multi-threaded slave.\n"); + return(false); + } + + return(true); +} + +static +bool +select_incremental_lsn_from_history(lsn_t *incremental_lsn) +{ + MYSQL_RES *mysql_result; + MYSQL_ROW row; + char query[1000]; + char buf[100]; + + if (opt_incremental_history_name) { + mysql_real_escape_string(mysql_connection, buf, + opt_incremental_history_name, + (unsigned long)strlen(opt_incremental_history_name)); + ut_snprintf(query, sizeof(query), + "SELECT innodb_to_lsn " + "FROM PERCONA_SCHEMA.xtrabackup_history " + "WHERE name = '%s' " + "AND innodb_to_lsn IS NOT NULL " + "ORDER BY innodb_to_lsn DESC LIMIT 1", + buf); + } + + if (opt_incremental_history_uuid) { + mysql_real_escape_string(mysql_connection, buf, + opt_incremental_history_uuid, + (unsigned long)strlen(opt_incremental_history_uuid)); + ut_snprintf(query, sizeof(query), + "SELECT innodb_to_lsn " + "FROM PERCONA_SCHEMA.xtrabackup_history " + "WHERE uuid = '%s' " + "AND innodb_to_lsn IS NOT NULL " + "ORDER BY innodb_to_lsn DESC LIMIT 1", + buf); + } + + mysql_result = xb_mysql_query(mysql_connection, query, true); + + ut_ad(mysql_num_fields(mysql_result) == 1); + if (!(row = mysql_fetch_row(mysql_result))) { + msg("Error while attempting to find history record " + "for %s %s\n", + opt_incremental_history_uuid ? "uuid" : "name", + opt_incremental_history_uuid ? + opt_incremental_history_uuid : + opt_incremental_history_name); + return(false); + } + + *incremental_lsn = strtoull(row[0], NULL, 10); + + mysql_free_result(mysql_result); + + msg("Found and using lsn: " LSN_PF " for %s %s\n", *incremental_lsn, + opt_incremental_history_uuid ? "uuid" : "name", + opt_incremental_history_uuid ? + opt_incremental_history_uuid : + opt_incremental_history_name); + + return(true); +} + +static +const char * +eat_sql_whitespace(const char *query) +{ + bool comment = false; + + while (*query) { + if (comment) { + if (query[0] == '*' && query[1] == '/') { + query += 2; + comment = false; + continue; + } + ++query; + continue; + } + if (query[0] == '/' && query[1] == '*') { + query += 2; + comment = true; + continue; + } + if (strchr("\t\n\r (", query[0])) { + ++query; + continue; + } + break; + } + + return(query); +} + +static +bool +is_query_from_list(const char *query, const char **list) +{ + const char **item; + + query = eat_sql_whitespace(query); + + item = list; + while (*item) { + if (strncasecmp(query, *item, strlen(*item)) == 0) { + return(true); + } + ++item; + } + + return(false); +} + +static +bool +is_query(const char *query) +{ + const char *query_list[] = {"insert", "update", "delete", "replace", + "alter", "load", "select", "do", "handler", "call", "execute", + "begin", NULL}; + + return is_query_from_list(query, query_list); +} + +static +bool +is_select_query(const char *query) +{ + const char *query_list[] = {"select", NULL}; + + return is_query_from_list(query, query_list); +} + +static +bool +is_update_query(const char *query) +{ + const char *query_list[] = {"insert", "update", "delete", "replace", + "alter", "load", NULL}; + + return is_query_from_list(query, query_list); +} + +static +bool +have_queries_to_wait_for(MYSQL *connection, uint threshold) +{ + MYSQL_RES *result; + MYSQL_ROW row; + bool all_queries; + + result = xb_mysql_query(connection, "SHOW FULL PROCESSLIST", true); + + all_queries = (opt_lock_wait_query_type == QUERY_TYPE_ALL); + while ((row = mysql_fetch_row(result)) != NULL) { + const char *info = row[7]; + int duration = atoi(row[5]); + char *id = row[0]; + + if (info != NULL + && duration >= (int)threshold + && ((all_queries && is_query(info)) + || is_update_query(info))) { + msg_ts("Waiting for query %s (duration %d sec): %s", + id, duration, info); + return(true); + } + } + + return(false); +} + +static +void +kill_long_queries(MYSQL *connection, time_t timeout) +{ + MYSQL_RES *result; + MYSQL_ROW row; + bool all_queries; + char kill_stmt[100]; + + result = xb_mysql_query(connection, "SHOW FULL PROCESSLIST", true); + + all_queries = (opt_kill_long_query_type == QUERY_TYPE_ALL); + while ((row = mysql_fetch_row(result)) != NULL) { + const char *info = row[7]; + long long duration = atoll(row[5]); + char *id = row[0]; + + if (info != NULL && + (time_t)duration >= timeout && + ((all_queries && is_query(info)) || + is_select_query(info))) { + msg_ts("Killing query %s (duration %d sec): %s\n", + id, (int)duration, info); + ut_snprintf(kill_stmt, sizeof(kill_stmt), + "KILL %s", id); + xb_mysql_query(connection, kill_stmt, false, false); + } + } +} + +static +bool +wait_for_no_updates(MYSQL *connection, uint timeout, uint threshold) +{ + time_t start_time; + + start_time = time(NULL); + + msg_ts("Waiting %u seconds for queries running longer than %u seconds " + "to finish\n", timeout, threshold); + + while (time(NULL) <= (time_t)(start_time + timeout)) { + if (!have_queries_to_wait_for(connection, threshold)) { + return(true); + } + os_thread_sleep(1000000); + } + + msg_ts("Unable to obtain lock. Please try again later."); + + return(false); +} + +static +os_thread_ret_t +kill_query_thread( +/*===============*/ + void *arg __attribute__((unused))) +{ + MYSQL *mysql; + time_t start_time; + + start_time = time(NULL); + + os_event_set(kill_query_thread_started); + + msg_ts("Kill query timeout %d seconds.\n", + opt_kill_long_queries_timeout); + + while (time(NULL) - start_time < + (time_t)opt_kill_long_queries_timeout) { + if (os_event_wait_time(kill_query_thread_stop, 1000) != + OS_SYNC_TIME_EXCEEDED) { + goto stop_thread; + } + } + + if ((mysql = xb_mysql_connect()) == NULL) { + msg("Error: kill query thread failed\n"); + goto stop_thread; + } + + while (true) { + kill_long_queries(mysql, time(NULL) - start_time); + if (os_event_wait_time(kill_query_thread_stop, 1000) != + OS_SYNC_TIME_EXCEEDED) { + break; + } + } + + mysql_close(mysql); + +stop_thread: + msg_ts("Kill query thread stopped\n"); + + os_event_set(kill_query_thread_stopped); + + os_thread_exit(NULL); + OS_THREAD_DUMMY_RETURN; +} + + +static +void +start_query_killer() +{ + kill_query_thread_stop = os_event_create(); + kill_query_thread_started = os_event_create(); + kill_query_thread_stopped = os_event_create(); + + os_thread_create(kill_query_thread, NULL, &kill_query_thread_id); + + os_event_wait(kill_query_thread_started); +} + +static +void +stop_query_killer() +{ + os_event_set(kill_query_thread_stop); + os_event_wait_time(kill_query_thread_stopped, 60000); +} + +/*********************************************************************//** +Function acquires either a backup tables lock, if supported +by the server, or a global read lock (FLUSH TABLES WITH READ LOCK) +otherwise. +@returns true if lock acquired */ +bool +lock_tables(MYSQL *connection) +{ + if (have_lock_wait_timeout) { + /* Set the maximum supported session value for + lock_wait_timeout to prevent unnecessary timeouts when the + global value is changed from the default */ + xb_mysql_query(connection, + "SET SESSION lock_wait_timeout=31536000", false); + } + + if (have_backup_locks) { + msg_ts("Executing LOCK TABLES FOR BACKUP...\n"); + xb_mysql_query(connection, "LOCK TABLES FOR BACKUP", false); + return(true); + } + + if (!opt_lock_wait_timeout && !opt_kill_long_queries_timeout) { + + /* We do first a FLUSH TABLES. If a long update is running, the + FLUSH TABLES will wait but will not stall the whole mysqld, and + when the long update is done the FLUSH TABLES WITH READ LOCK + will start and succeed quickly. So, FLUSH TABLES is to lower + the probability of a stage where both mysqldump and most client + connections are stalled. Of course, if a second long update + starts between the two FLUSHes, we have that bad stall. + + Option lock_wait_timeout serve the same purpose and is not + compatible with this trick. + */ + + msg_ts("Executing FLUSH NO_WRITE_TO_BINLOG TABLES...\n"); + + xb_mysql_query(connection, + "FLUSH NO_WRITE_TO_BINLOG TABLES", false); + } + + if (opt_lock_wait_timeout) { + if (!wait_for_no_updates(connection, opt_lock_wait_timeout, + opt_lock_wait_threshold)) { + return(false); + } + } + + msg_ts("Executing FLUSH TABLES WITH READ LOCK...\n"); + + if (opt_kill_long_queries_timeout) { + start_query_killer(); + } + + if (have_galera_enabled) { + xb_mysql_query(connection, + "SET SESSION wsrep_causal_reads=0", false); + } + + xb_mysql_query(connection, "FLUSH TABLES WITH READ LOCK", false); + + if (opt_kill_long_queries_timeout) { + stop_query_killer(); + } + + return(true); +} + + +/*********************************************************************//** +If backup locks are used, execute LOCK BINLOG FOR BACKUP provided that we are +not in the --no-lock mode and the lock has not been acquired already. +@returns true if lock acquired */ +bool +lock_binlog_maybe(MYSQL *connection) +{ + if (have_backup_locks && !opt_no_lock && !binlog_locked) { + msg_ts("Executing LOCK BINLOG FOR BACKUP...\n"); + xb_mysql_query(connection, "LOCK BINLOG FOR BACKUP", false); + binlog_locked = true; + + return(true); + } + + return(false); +} + + +/*********************************************************************//** +Releases either global read lock acquired with FTWRL and the binlog +lock acquired with LOCK BINLOG FOR BACKUP, depending on +the locking strategy being used */ +void +unlock_all(MYSQL *connection) +{ + if (opt_debug_sleep_before_unlock) { + msg_ts("Debug sleep for %u seconds\n", + opt_debug_sleep_before_unlock); + os_thread_sleep(opt_debug_sleep_before_unlock * 1000); + } + + if (binlog_locked) { + msg_ts("Executing UNLOCK BINLOG\n"); + xb_mysql_query(connection, "UNLOCK BINLOG", false); + } + + msg_ts("Executing UNLOCK TABLES\n"); + xb_mysql_query(connection, "UNLOCK TABLES", false); + + msg_ts("All tables unlocked\n"); +} + + +static +int +get_open_temp_tables(MYSQL *connection) +{ + char *slave_open_temp_tables = NULL; + mysql_variable status[] = { + {"Slave_open_temp_tables", &slave_open_temp_tables}, + {NULL, NULL} + }; + int result = false; + + read_mysql_variables(connection, + "SHOW STATUS LIKE 'slave_open_temp_tables'", status, true); + + result = slave_open_temp_tables ? atoi(slave_open_temp_tables) : 0; + + free_mysql_variables(status); + + return(result); +} + +/*********************************************************************//** +Wait until it's safe to backup a slave. Returns immediately if +the host isn't a slave. Currently there's only one check: +Slave_open_temp_tables has to be zero. Dies on timeout. */ +bool +wait_for_safe_slave(MYSQL *connection) +{ + char *read_master_log_pos = NULL; + char *slave_sql_running = NULL; + int n_attempts = 1; + const int sleep_time = 3; + int open_temp_tables = 0; + bool result = true; + + mysql_variable status[] = { + {"Read_Master_Log_Pos", &read_master_log_pos}, + {"Slave_SQL_Running", &slave_sql_running}, + {NULL, NULL} + }; + + sql_thread_started = false; + + read_mysql_variables(connection, "SHOW SLAVE STATUS", status, false); + + if (!(read_master_log_pos && slave_sql_running)) { + msg("Not checking slave open temp tables for " + "--safe-slave-backup because host is not a slave\n"); + goto cleanup; + } + + if (strcmp(slave_sql_running, "Yes") == 0) { + sql_thread_started = true; + xb_mysql_query(connection, "STOP SLAVE SQL_THREAD", false); + } + + if (opt_safe_slave_backup_timeout > 0) { + n_attempts = opt_safe_slave_backup_timeout / sleep_time; + } + + open_temp_tables = get_open_temp_tables(connection); + msg_ts("Slave open temp tables: %d\n", open_temp_tables); + + while (open_temp_tables && n_attempts--) { + msg_ts("Starting slave SQL thread, waiting %d seconds, then " + "checking Slave_open_temp_tables again (%d attempts " + "remaining)...\n", sleep_time, n_attempts); + + xb_mysql_query(connection, "START SLAVE SQL_THREAD", false); + os_thread_sleep(sleep_time * 1000000); + xb_mysql_query(connection, "STOP SLAVE SQL_THREAD", false); + + open_temp_tables = get_open_temp_tables(connection); + msg_ts("Slave open temp tables: %d\n", open_temp_tables); + } + + /* Restart the slave if it was running at start */ + if (open_temp_tables == 0) { + msg_ts("Slave is safe to backup\n"); + goto cleanup; + } + + result = false; + + if (sql_thread_started) { + msg_ts("Restarting slave SQL thread.\n"); + xb_mysql_query(connection, "START SLAVE SQL_THREAD", false); + } + + msg_ts("Slave_open_temp_tables did not become zero after " + "%d seconds\n", opt_safe_slave_backup_timeout); + +cleanup: + free_mysql_variables(status); + + return(result); +} + + +/*********************************************************************//** +Retrieves MySQL binlog position of the master server in a replication +setup and saves it in a file. It also saves it in mysql_slave_position +variable. */ +bool +write_slave_info(MYSQL *connection) +{ + char *master = NULL; + char *filename = NULL; + char *gtid_executed = NULL; + char *position = NULL; + char *gtid_slave_pos = NULL; + char *ptr; + bool result = false; + + mysql_variable status[] = { + {"Master_Host", &master}, + {"Relay_Master_Log_File", &filename}, + {"Exec_Master_Log_Pos", &position}, + {"Executed_Gtid_Set", >id_executed}, + {NULL, NULL} + }; + + mysql_variable variables[] = { + {"gtid_slave_pos", >id_slave_pos}, + {NULL, NULL} + }; + + read_mysql_variables(connection, "SHOW SLAVE STATUS", status, false); + read_mysql_variables(connection, "SHOW VARIABLES", variables, true); + + if (master == NULL || filename == NULL || position == NULL) { + msg("Failed to get master binlog coordinates " + "from SHOW SLAVE STATUS\n"); + msg("This means that the server is not a " + "replication slave. Ignoring the --slave-info " + "option\n"); + /* we still want to continue the backup */ + result = true; + goto cleanup; + } + + /* Print slave status to a file. + If GTID mode is used, construct a CHANGE MASTER statement with + MASTER_AUTO_POSITION and correct a gtid_purged value. */ + if (gtid_executed != NULL && *gtid_executed) { + /* MySQL >= 5.6 with GTID enabled */ + + for (ptr = strchr(gtid_executed, '\n'); + ptr; + ptr = strchr(ptr, '\n')) { + *ptr = ' '; + } + + result = backup_file_printf(XTRABACKUP_SLAVE_INFO, + "SET GLOBAL gtid_purged='%s';\n" + "CHANGE MASTER TO MASTER_AUTO_POSITION=1\n", + gtid_executed); + + ut_a(asprintf(&mysql_slave_position, + "master host '%s', purge list '%s'", + master, gtid_executed) != -1); + } else if (gtid_slave_pos && *gtid_slave_pos) { + /* MariaDB >= 10.0 with GTID enabled */ + result = backup_file_printf(XTRABACKUP_SLAVE_INFO, + "SET GLOBAL gtid_slave_pos = '%s';\n" + "CHANGE MASTER TO master_use_gtid = slave_pos\n", + gtid_slave_pos); + ut_a(asprintf(&mysql_slave_position, + "master host '%s', gtid_slave_pos %s", + master, gtid_slave_pos) != -1); + } else { + result = backup_file_printf(XTRABACKUP_SLAVE_INFO, + "CHANGE MASTER TO MASTER_LOG_FILE='%s', " + "MASTER_LOG_POS=%s\n", filename, position); + ut_a(asprintf(&mysql_slave_position, + "master host '%s', filename '%s', position '%s'", + master, filename, position) != -1); + } + +cleanup: + free_mysql_variables(status); + free_mysql_variables(variables); + + return(result); +} + + +/*********************************************************************//** +Retrieves MySQL Galera and +saves it in a file. It also prints it to stdout. */ +bool +write_galera_info(MYSQL *connection) +{ + char *state_uuid = NULL, *state_uuid55 = NULL; + char *last_committed = NULL, *last_committed55 = NULL; + bool result; + + mysql_variable status[] = { + {"Wsrep_local_state_uuid", &state_uuid}, + {"wsrep_local_state_uuid", &state_uuid55}, + {"Wsrep_last_committed", &last_committed}, + {"wsrep_last_committed", &last_committed55}, + {NULL, NULL} + }; + + /* When backup locks are supported by the server, we should skip + creating xtrabackup_galera_info file on the backup stage, because + wsrep_local_state_uuid and wsrep_last_committed will be inconsistent + without blocking commits. The state file will be created on the prepare + stage using the WSREP recovery procedure. */ + if (have_backup_locks) { + return(true); + } + + read_mysql_variables(connection, "SHOW STATUS", status, true); + + if ((state_uuid == NULL && state_uuid55 == NULL) + || (last_committed == NULL && last_committed55 == NULL)) { + msg("Failed to get master wsrep state from SHOW STATUS.\n"); + result = false; + goto cleanup; + } + + result = backup_file_printf(XTRABACKUP_GALERA_INFO, + "%s:%s\n", state_uuid ? state_uuid : state_uuid55, + last_committed ? last_committed : last_committed55); + +cleanup: + free_mysql_variables(status); + + return(result); +} + + +/*********************************************************************//** +Flush and copy the current binary log file into the backup, +if GTID is enabled */ +bool +write_current_binlog_file(MYSQL *connection) +{ + char *executed_gtid_set = NULL; + char *gtid_binlog_state = NULL; + char *log_bin_file = NULL; + char *log_bin_dir = NULL; + bool gtid_exists; + bool result = true; + char filepath[FN_REFLEN]; + + mysql_variable status[] = { + {"Executed_Gtid_Set", &executed_gtid_set}, + {NULL, NULL} + }; + + mysql_variable status_after_flush[] = { + {"File", &log_bin_file}, + {NULL, NULL} + }; + + mysql_variable vars[] = { + {"gtid_binlog_state", >id_binlog_state}, + {"log_bin_basename", &log_bin_dir}, + {NULL, NULL} + }; + + read_mysql_variables(connection, "SHOW MASTER STATUS", status, false); + read_mysql_variables(connection, "SHOW VARIABLES", vars, true); + + gtid_exists = (executed_gtid_set && *executed_gtid_set) + || (gtid_binlog_state && *gtid_binlog_state); + + if (gtid_exists) { + size_t log_bin_dir_length; + + lock_binlog_maybe(connection); + + xb_mysql_query(connection, "FLUSH BINARY LOGS", false); + + read_mysql_variables(connection, "SHOW MASTER STATUS", + status_after_flush, false); + + if (opt_log_bin != NULL && strchr(opt_log_bin, FN_LIBCHAR)) { + /* If log_bin is set, it has priority */ + if (log_bin_dir) { + free(log_bin_dir); + } + log_bin_dir = strdup(opt_log_bin); + } else if (log_bin_dir == NULL) { + /* Default location is MySQL datadir */ + log_bin_dir = strdup("./"); + } + + dirname_part(log_bin_dir, log_bin_dir, &log_bin_dir_length); + + /* strip final slash if it is not the only path component */ + if (log_bin_dir_length > 1 && + log_bin_dir[log_bin_dir_length - 1] == FN_LIBCHAR) { + log_bin_dir[log_bin_dir_length - 1] = 0; + } + + if (log_bin_dir == NULL || log_bin_file == NULL) { + msg("Failed to get master binlog coordinates from " + "SHOW MASTER STATUS"); + result = false; + goto cleanup; + } + + ut_snprintf(filepath, sizeof(filepath), "%s%c%s", + log_bin_dir, FN_LIBCHAR, log_bin_file); + result = copy_file(ds_data, filepath, log_bin_file, 0); + } + +cleanup: + free_mysql_variables(status_after_flush); + free_mysql_variables(status); + free_mysql_variables(vars); + + return(result); +} + + +/*********************************************************************//** +Retrieves MySQL binlog position and +saves it in a file. It also prints it to stdout. */ +bool +write_binlog_info(MYSQL *connection) +{ + char *filename = NULL; + char *position = NULL; + char *gtid_mode = NULL; + char *gtid_current_pos = NULL; + char *gtid_executed = NULL; + char *gtid = NULL; + bool result; + bool mysql_gtid; + bool mariadb_gtid; + + mysql_variable status[] = { + {"File", &filename}, + {"Position", &position}, + {"Executed_Gtid_Set", >id_executed}, + {NULL, NULL} + }; + + mysql_variable vars[] = { + {"gtid_mode", >id_mode}, + {"gtid_current_pos", >id_current_pos}, + {NULL, NULL} + }; + + read_mysql_variables(connection, "SHOW MASTER STATUS", status, false); + read_mysql_variables(connection, "SHOW VARIABLES", vars, true); + + if (filename == NULL || position == NULL) { + /* Do not create xtrabackup_binlog_info if binary + log is disabled */ + result = true; + goto cleanup; + } + + mysql_gtid = ((gtid_mode != NULL) && (strcmp(gtid_mode, "ON") == 0)); + mariadb_gtid = (gtid_current_pos != NULL); + + gtid = (gtid_executed != NULL ? gtid_executed : gtid_current_pos); + + if (mariadb_gtid || mysql_gtid) { + ut_a(asprintf(&mysql_binlog_position, + "filename '%s', position '%s', " + "GTID of the last change '%s'", + filename, position, gtid) != -1); + result = backup_file_printf(XTRABACKUP_BINLOG_INFO, + "%s\t%s\t%s\n", filename, position, + gtid); + } else { + ut_a(asprintf(&mysql_binlog_position, + "filename '%s', position '%s'", + filename, position) != -1); + result = backup_file_printf(XTRABACKUP_BINLOG_INFO, + "%s\t%s\n", filename, position); + } + +cleanup: + free_mysql_variables(status); + free_mysql_variables(vars); + + return(result); +} + +static string escape_and_quote(MYSQL *mysql,const char *str) +{ + if (!str) + return "NULL"; + size_t len = strlen(str); + char* escaped = (char *)alloca(2 * len + 3); + escaped[0] = '\''; + size_t new_len = mysql_real_escape_string(mysql, escaped+1, str, len); + escaped[new_len + 1] = '\''; + escaped[new_len + 2] = 0; + return string(escaped); +} + +/*********************************************************************//** +Writes xtrabackup_info file and if backup_history is enable creates +PERCONA_SCHEMA.xtrabackup_history and writes a new history record to the +table containing all the history info particular to the just completed +backup. */ +bool +write_xtrabackup_info(MYSQL *connection) +{ + + char *uuid = NULL; + char *server_version = NULL; + char buf_start_time[100]; + char buf_end_time[100]; + tm tm; + ostringstream oss; + const char *xb_stream_name[] = {"file", "tar", "xbstream"}; + + + ut_ad(xtrabackup_stream_fmt < 3); + + uuid = read_mysql_one_value(connection, "SELECT UUID()"); + server_version = read_mysql_one_value(connection, "SELECT VERSION()"); + localtime_r(&history_start_time, &tm); + strftime(buf_start_time, sizeof(buf_start_time), + "%Y-%m-%d %H:%M:%S", &tm); + history_end_time = time(NULL); + localtime_r(&history_end_time, &tm); + strftime(buf_end_time, sizeof(buf_end_time), + "%Y-%m-%d %H:%M:%S", &tm); + bool is_partial = (xtrabackup_tables + || xtrabackup_tables_file + || xtrabackup_databases + || xtrabackup_databases_file + || xtrabackup_tables_exclude + || xtrabackup_databases_exclude + ); + + backup_file_printf(XTRABACKUP_INFO, + "uuid = %s\n" + "name = %s\n" + "tool_name = %s\n" + "tool_command = %s\n" + "tool_version = %s\n" + "ibbackup_version = %s\n" + "server_version = %s\n" + "start_time = %s\n" + "end_time = %s\n" + "lock_time = %d\n" + "binlog_pos = %s\n" + "innodb_from_lsn = %llu\n" + "innodb_to_lsn = %llu\n" + "partial = %s\n" + "incremental = %s\n" + "format = %s\n" + "compact = %s\n" + "compressed = %s\n" + "encrypted = %s\n", + uuid, /* uuid */ + opt_history ? opt_history : "", /* name */ + tool_name, /* tool_name */ + tool_args, /* tool_command */ + MYSQL_SERVER_VERSION, /* tool_version */ + MYSQL_SERVER_VERSION, /* ibbackup_version */ + server_version, /* server_version */ + buf_start_time, /* start_time */ + buf_end_time, /* end_time */ + (int)history_lock_time, /* lock_time */ + mysql_binlog_position ? + mysql_binlog_position : "", /* binlog_pos */ + incremental_lsn, /* innodb_from_lsn */ + metadata_to_lsn, /* innodb_to_lsn */ + is_partial? "Y" : "N", + xtrabackup_incremental ? "Y" : "N", /* incremental */ + xb_stream_name[xtrabackup_stream_fmt], /* format */ + "N", /* compact */ + xtrabackup_compress ? "compressed" : "N", /* compressed */ + xtrabackup_encrypt ? "Y" : "N"); /* encrypted */ + + if (!opt_history) { + goto cleanup; + } + + xb_mysql_query(connection, + "CREATE DATABASE IF NOT EXISTS PERCONA_SCHEMA", false); + xb_mysql_query(connection, + "CREATE TABLE IF NOT EXISTS PERCONA_SCHEMA.xtrabackup_history(" + "uuid VARCHAR(40) NOT NULL PRIMARY KEY," + "name VARCHAR(255) DEFAULT NULL," + "tool_name VARCHAR(255) DEFAULT NULL," + "tool_command TEXT DEFAULT NULL," + "tool_version VARCHAR(255) DEFAULT NULL," + "ibbackup_version VARCHAR(255) DEFAULT NULL," + "server_version VARCHAR(255) DEFAULT NULL," + "start_time TIMESTAMP NULL DEFAULT NULL," + "end_time TIMESTAMP NULL DEFAULT NULL," + "lock_time BIGINT UNSIGNED DEFAULT NULL," + "binlog_pos VARCHAR(128) DEFAULT NULL," + "innodb_from_lsn BIGINT UNSIGNED DEFAULT NULL," + "innodb_to_lsn BIGINT UNSIGNED DEFAULT NULL," + "partial ENUM('Y', 'N') DEFAULT NULL," + "incremental ENUM('Y', 'N') DEFAULT NULL," + "format ENUM('file', 'tar', 'xbstream') DEFAULT NULL," + "compact ENUM('Y', 'N') DEFAULT NULL," + "compressed ENUM('Y', 'N') DEFAULT NULL," + "encrypted ENUM('Y', 'N') DEFAULT NULL" + ") CHARACTER SET utf8 ENGINE=innodb", false); + + +#define ESCAPE_BOOL(expr) ((expr)?"'Y'":"'N'") + + oss << "insert into PERCONA_SCHEMA.xtrabackup_history(" + << "uuid, name, tool_name, tool_command, tool_version," + << "ibbackup_version, server_version, start_time, end_time," + << "lock_time, binlog_pos, innodb_from_lsn, innodb_to_lsn," + << "partial, incremental, format, compact, compressed, " + << "encrypted) values(" + << escape_and_quote(connection, uuid) << "," + << escape_and_quote(connection, opt_history) << "," + << escape_and_quote(connection, tool_name) << "," + << escape_and_quote(connection, tool_args) << "," + << escape_and_quote(connection, MYSQL_SERVER_VERSION) << "," + << escape_and_quote(connection, MYSQL_SERVER_VERSION) << "," + << escape_and_quote(connection, server_version) << "," + << "from_unixtime(" << history_start_time << ")," + << "from_unixtime(" << history_end_time << ")," + << history_lock_time << "," + << escape_and_quote(connection, mysql_binlog_position) << "," + << incremental_lsn << "," + << metadata_to_lsn << "," + << ESCAPE_BOOL(is_partial) << "," + << ESCAPE_BOOL(xtrabackup_incremental)<< "," + << escape_and_quote(connection,xb_stream_name[xtrabackup_stream_fmt]) <<"," + << ESCAPE_BOOL(false) << "," + << ESCAPE_BOOL(xtrabackup_compress) << "," + << ESCAPE_BOOL(xtrabackup_encrypt) <<")"; + + xb_mysql_query(mysql_connection, oss.str().c_str(), false); + +cleanup: + + free(uuid); + free(server_version); + + return(true); +} + +extern const char *innodb_checksum_algorithm_names[]; + +bool write_backup_config_file() +{ + int rc= backup_file_printf("backup-my.cnf", + "# This MySQL options file was generated by innobackupex.\n\n" + "# The MySQL server\n" + "[mysqld]\n" + "innodb_checksum_algorithm=%s\n" + "innodb_log_checksum_algorithm=%s\n" + "innodb_data_file_path=%s\n" + "innodb_log_files_in_group=%lu\n" + "innodb_log_file_size=%lld\n" + "innodb_page_size=%lu\n" + "innodb_log_block_size=%lu\n" + "innodb_undo_directory=%s\n" + "innodb_undo_tablespaces=%lu\n" + "%s%s\n" + "%s%s\n" + "%s\n", + innodb_checksum_algorithm_names[srv_checksum_algorithm], + innodb_checksum_algorithm_names[srv_log_checksum_algorithm], + innobase_data_file_path, + srv_n_log_files, + innobase_log_file_size, + srv_page_size, + srv_log_block_size, + srv_undo_dir, + srv_undo_tablespaces, + innobase_doublewrite_file ? "innodb_doublewrite_file=" : "", + innobase_doublewrite_file ? innobase_doublewrite_file : "", + innobase_buffer_pool_filename ? + "innodb_buffer_pool_filename=" : "", + innobase_buffer_pool_filename ? + innobase_buffer_pool_filename : "", + encryption_plugin_get_config()); + return rc; +} + + +static +char *make_argv(char *buf, size_t len, int argc, char **argv) +{ + size_t left= len; + const char *arg; + + buf[0]= 0; + ++argv; --argc; + while (argc > 0 && left > 0) + { + arg = *argv; + if (strncmp(*argv, "--password", strlen("--password")) == 0) { + arg = "--password=..."; + } + if (strncmp(*argv, "--encrypt-key", + strlen("--encrypt-key")) == 0) { + arg = "--encrypt-key=..."; + } + if (strncmp(*argv, "--encrypt_key", + strlen("--encrypt_key")) == 0) { + arg = "--encrypt_key=..."; + } + left-= ut_snprintf(buf + len - left, left, + "%s%c", arg, argc > 1 ? ' ' : 0); + ++argv; --argc; + } + + return buf; +} + +void +capture_tool_command(int argc, char **argv) +{ + /* capture tool name tool args */ + tool_name = strrchr(argv[0], '/'); + tool_name = tool_name ? tool_name + 1 : argv[0]; + + make_argv(tool_args, sizeof(tool_args), argc, argv); +} + + +bool +select_history() +{ + if (opt_incremental_history_name || opt_incremental_history_uuid) { + if (!select_incremental_lsn_from_history( + &incremental_lsn)) { + return(false); + } + } + return(true); +} + +bool +flush_changed_page_bitmaps() +{ + if (xtrabackup_incremental && have_changed_page_bitmaps && + !xtrabackup_incremental_force_scan) { + xb_mysql_query(mysql_connection, + "FLUSH NO_WRITE_TO_BINLOG CHANGED_PAGE_BITMAPS", false); + } + return(true); +} + + +/*********************************************************************//** +Deallocate memory, disconnect from MySQL server, etc. +@return true on success. */ +void +backup_cleanup() +{ + free(mysql_slave_position); + free(mysql_binlog_position); + free(buffer_pool_filename); + + if (mysql_connection) { + mysql_close(mysql_connection); + } +} diff --git a/extra/mariabackup/backup_mysql.h b/extra/mariabackup/backup_mysql.h new file mode 100644 index 00000000000..3ccd7bdb613 --- /dev/null +++ b/extra/mariabackup/backup_mysql.h @@ -0,0 +1,92 @@ +#ifndef XTRABACKUP_BACKUP_MYSQL_H +#define XTRABACKUP_BACKUP_MYSQL_H + +#include <mysql.h> + +/* mysql flavor and version */ +enum mysql_flavor_t { FLAVOR_UNKNOWN, FLAVOR_MYSQL, + FLAVOR_PERCONA_SERVER, FLAVOR_MARIADB }; +extern mysql_flavor_t server_flavor; +extern unsigned long mysql_server_version; + +/* server capabilities */ +extern bool have_changed_page_bitmaps; +extern bool have_backup_locks; +extern bool have_lock_wait_timeout; +extern bool have_galera_enabled; +extern bool have_flush_engine_logs; +extern bool have_multi_threaded_slave; +extern bool have_gtid_slave; + + +/* History on server */ +extern time_t history_start_time; +extern time_t history_end_time; +extern time_t history_lock_time; + + +extern bool sql_thread_started; +extern char *mysql_slave_position; +extern char *mysql_binlog_position; +extern char *buffer_pool_filename; + +/** connection to mysql server */ +extern MYSQL *mysql_connection; + +void +capture_tool_command(int argc, char **argv); + +bool +select_history(); + +bool +flush_changed_page_bitmaps(); + +void +backup_cleanup(); + +bool +get_mysql_vars(MYSQL *connection); + +bool +detect_mysql_capabilities_for_backup(); + +MYSQL * +xb_mysql_connect(); + +MYSQL_RES * +xb_mysql_query(MYSQL *connection, const char *query, bool use_result, + bool die_on_error = true); + +void +unlock_all(MYSQL *connection); + +bool +write_current_binlog_file(MYSQL *connection); + +bool +write_binlog_info(MYSQL *connection); + +bool +write_xtrabackup_info(MYSQL *connection); + +bool +write_backup_config_file(); + +bool +lock_binlog_maybe(MYSQL *connection); + +bool +lock_tables(MYSQL *connection); + +bool +wait_for_safe_slave(MYSQL *connection); + +bool +write_galera_info(MYSQL *connection); + +bool +write_slave_info(MYSQL *connection); + + +#endif diff --git a/extra/mariabackup/changed_page_bitmap.cc b/extra/mariabackup/changed_page_bitmap.cc new file mode 100644 index 00000000000..435b7fb6172 --- /dev/null +++ b/extra/mariabackup/changed_page_bitmap.cc @@ -0,0 +1,1018 @@ +/****************************************************** +XtraBackup: hot backup tool for InnoDB +(c) 2009-2012 Percona Inc. +Originally Created 3/3/2009 Yasufumi Kinoshita +Written by Alexey Kopytov, Aleksandr Kuzminsky, Stewart Smith, Vadim Tkachenko, +Yasufumi Kinoshita, Ignacio Nin and Baron Schwartz. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +/* Changed page bitmap implementation */ + +#include "changed_page_bitmap.h" + +#include "common.h" +#include "xtrabackup.h" + +/* TODO: copy-pasted shared definitions from the XtraDB bitmap write code. +Remove these on the first opportunity, i.e. single-binary XtraBackup. */ + +/* log0online.h */ + +/** Single bitmap file information */ +struct log_online_bitmap_file_t { + char name[FN_REFLEN]; /*!< Name with full path */ + os_file_t file; /*!< Handle to opened file */ + ib_uint64_t size; /*!< Size of the file */ + ib_uint64_t offset; /*!< Offset of the next read, + or count of already-read bytes + */ +}; + +/** A set of bitmap files containing some LSN range */ +struct log_online_bitmap_file_range_t { + size_t count; /*!< Number of files */ + /*!< Dynamically-allocated array of info about individual files */ + struct files_t { + char name[FN_REFLEN];/*!< Name of a file */ + lsn_t start_lsn; /*!< Starting LSN of data in this + file */ + ulong seq_num; /*!< Sequence number of this file */ + } *files; +}; + +/* log0online.c */ + +/** File name stem for bitmap files. */ +static const char* bmp_file_name_stem = "ib_modified_log_"; + +/** The bitmap file block size in bytes. All writes will be multiples of this. + */ +enum { + MODIFIED_PAGE_BLOCK_SIZE = 4096 +}; + +/** Offsets in a file bitmap block */ +enum { + MODIFIED_PAGE_IS_LAST_BLOCK = 0,/* 1 if last block in the current + write, 0 otherwise. */ + MODIFIED_PAGE_START_LSN = 4, /* The starting tracked LSN of this and + other blocks in the same write */ + MODIFIED_PAGE_END_LSN = 12, /* The ending tracked LSN of this and + other blocks in the same write */ + MODIFIED_PAGE_SPACE_ID = 20, /* The space ID of tracked pages in + this block */ + MODIFIED_PAGE_1ST_PAGE_ID = 24, /* The page ID of the first tracked + page in this block */ + MODIFIED_PAGE_BLOCK_UNUSED_1 = 28,/* Unused in order to align the start + of bitmap at 8 byte boundary */ + MODIFIED_PAGE_BLOCK_BITMAP = 32,/* Start of the bitmap itself */ + MODIFIED_PAGE_BLOCK_UNUSED_2 = MODIFIED_PAGE_BLOCK_SIZE - 8, + /* Unused in order to align the end of + bitmap at 8 byte boundary */ + MODIFIED_PAGE_BLOCK_CHECKSUM = MODIFIED_PAGE_BLOCK_SIZE - 4 + /* The checksum of the current block */ +}; + +/** Length of the bitmap data in a block */ +enum { MODIFIED_PAGE_BLOCK_BITMAP_LEN + = MODIFIED_PAGE_BLOCK_UNUSED_2 - MODIFIED_PAGE_BLOCK_BITMAP }; + +/** Length of the bitmap data in a block in page ids */ +enum { MODIFIED_PAGE_BLOCK_ID_COUNT = MODIFIED_PAGE_BLOCK_BITMAP_LEN * 8 }; + +typedef ib_uint64_t bitmap_word_t; + +/****************************************************************//** +Calculate a bitmap block checksum. Algorithm borrowed from +log_block_calc_checksum. +@return checksum */ +UNIV_INLINE +ulint +log_online_calc_checksum( +/*=====================*/ + const byte* block); /*!<in: bitmap block */ + +/****************************************************************//** +Provide a comparisson function for the RB-tree tree (space, +block_start_page) pairs. Actual implementation does not matter as +long as the ordering is full. +@return -1 if p1 < p2, 0 if p1 == p2, 1 if p1 > p2 +*/ +static +int +log_online_compare_bmp_keys( +/*========================*/ + const void* p1, /*!<in: 1st key to compare */ + const void* p2) /*!<in: 2nd key to compare */ +{ + const byte *k1 = (const byte *)p1; + const byte *k2 = (const byte *)p2; + + ulint k1_space = mach_read_from_4(k1 + MODIFIED_PAGE_SPACE_ID); + ulint k2_space = mach_read_from_4(k2 + MODIFIED_PAGE_SPACE_ID); + if (k1_space == k2_space) { + + ulint k1_start_page + = mach_read_from_4(k1 + MODIFIED_PAGE_1ST_PAGE_ID); + ulint k2_start_page + = mach_read_from_4(k2 + MODIFIED_PAGE_1ST_PAGE_ID); + return k1_start_page < k2_start_page + ? -1 : k1_start_page > k2_start_page ? 1 : 0; + } + return k1_space < k2_space ? -1 : 1; +} + +/****************************************************************//** +Calculate a bitmap block checksum. Algorithm borrowed from +log_block_calc_checksum. +@return checksum */ +UNIV_INLINE +ulint +log_online_calc_checksum( +/*=====================*/ + const byte* block) /*!<in: bitmap block */ +{ + ulint sum; + ulint sh; + ulint i; + + sum = 1; + sh = 0; + + for (i = 0; i < MODIFIED_PAGE_BLOCK_CHECKSUM; i++) { + + ulint b = block[i]; + sum &= 0x7FFFFFFFUL; + sum += b; + sum += b << sh; + sh++; + if (sh > 24) { + + sh = 0; + } + } + + return sum; +} + +/****************************************************************//** +Read one bitmap data page and check it for corruption. + +@return TRUE if page read OK, FALSE if I/O error */ +static +ibool +log_online_read_bitmap_page( +/*========================*/ + log_online_bitmap_file_t *bitmap_file, /*!<in/out: bitmap + file */ + byte *page, /*!<out: read page. Must be at + least MODIFIED_PAGE_BLOCK_SIZE + bytes long */ + ibool *checksum_ok) /*!<out: TRUE if page + checksum OK */ +{ + ulint checksum; + ulint actual_checksum; + ibool success; + + ut_a(bitmap_file->size >= MODIFIED_PAGE_BLOCK_SIZE); + ut_a(bitmap_file->offset + <= bitmap_file->size - MODIFIED_PAGE_BLOCK_SIZE); + ut_a(bitmap_file->offset % MODIFIED_PAGE_BLOCK_SIZE == 0); + + success = os_file_read(bitmap_file->file, page, bitmap_file->offset, + MODIFIED_PAGE_BLOCK_SIZE); + + if (UNIV_UNLIKELY(!success)) { + + /* The following call prints an error message */ + os_file_get_last_error(TRUE); + msg("InnoDB: Warning: failed reading changed page bitmap " + "file \'%s\'\n", bitmap_file->name); + return FALSE; + } + + bitmap_file->offset += MODIFIED_PAGE_BLOCK_SIZE; + ut_ad(bitmap_file->offset <= bitmap_file->size); + + checksum = mach_read_from_4(page + MODIFIED_PAGE_BLOCK_CHECKSUM); + actual_checksum = log_online_calc_checksum(page); + *checksum_ok = (checksum == actual_checksum); + + return TRUE; +} + +/*********************************************************************//** +Check the name of a given file if it's a changed page bitmap file and +return file sequence and start LSN name components if it is. If is not, +the values of output parameters are undefined. + +@return TRUE if a given file is a changed page bitmap file. */ +static +ibool +log_online_is_bitmap_file( +/*======================*/ + const os_file_stat_t* file_info, /*!<in: file to + check */ + ulong* bitmap_file_seq_num, /*!<out: bitmap file + sequence number */ + lsn_t* bitmap_file_start_lsn) /*!<out: bitmap file + start LSN */ +{ + char stem[FN_REFLEN]; + + ut_ad (strlen(file_info->name) < OS_FILE_MAX_PATH); + + return ((file_info->type == OS_FILE_TYPE_FILE + || file_info->type == OS_FILE_TYPE_LINK) + && (sscanf(file_info->name, "%[a-z_]%lu_" LSN_PF ".xdb", stem, + bitmap_file_seq_num, bitmap_file_start_lsn) == 3) + && (!strcmp(stem, bmp_file_name_stem))); +} + +/*********************************************************************//** +List the bitmap files in srv_data_home and setup their range that contains the +specified LSN interval. This range, if non-empty, will start with a file that +has the greatest LSN equal to or less than the start LSN and will include all +the files up to the one with the greatest LSN less than the end LSN. Caller +must free bitmap_files->files when done if bitmap_files set to non-NULL and +this function returned TRUE. Field bitmap_files->count might be set to a +larger value than the actual count of the files, and space for the unused array +slots will be allocated but cleared to zeroes. + +@return TRUE if succeeded +*/ +static +ibool +log_online_setup_bitmap_file_range( +/*===============================*/ + log_online_bitmap_file_range_t *bitmap_files, /*!<in/out: bitmap file + range */ + lsn_t range_start, /*!<in: start LSN */ + lsn_t range_end) /*!<in: end LSN */ +{ + os_file_dir_t bitmap_dir; + os_file_stat_t bitmap_dir_file_info; + ulong first_file_seq_num = ULONG_MAX; + ulong last_file_seq_num = 0; + lsn_t first_file_start_lsn = LSN_MAX; + + xb_ad(range_end >= range_start); + + bitmap_files->count = 0; + bitmap_files->files = NULL; + + /* 1st pass: size the info array */ + + bitmap_dir = os_file_opendir(srv_data_home, FALSE); + if (UNIV_UNLIKELY(!bitmap_dir)) { + + msg("InnoDB: Error: failed to open bitmap directory \'%s\'\n", + srv_data_home); + return FALSE; + } + + while (!os_file_readdir_next_file(srv_data_home, bitmap_dir, + &bitmap_dir_file_info)) { + + ulong file_seq_num; + lsn_t file_start_lsn; + + if (!log_online_is_bitmap_file(&bitmap_dir_file_info, + &file_seq_num, + &file_start_lsn) + || file_start_lsn >= range_end) { + + continue; + } + + if (file_seq_num > last_file_seq_num) { + + last_file_seq_num = file_seq_num; + } + + if (file_start_lsn >= range_start + || file_start_lsn == first_file_start_lsn + || first_file_start_lsn > range_start) { + + /* A file that falls into the range */ + + if (file_start_lsn < first_file_start_lsn) { + + first_file_start_lsn = file_start_lsn; + } + if (file_seq_num < first_file_seq_num) { + + first_file_seq_num = file_seq_num; + } + } else if (file_start_lsn > first_file_start_lsn) { + + /* A file that has LSN closer to the range start + but smaller than it, replacing another such file */ + first_file_start_lsn = file_start_lsn; + first_file_seq_num = file_seq_num; + } + } + + if (UNIV_UNLIKELY(os_file_closedir(bitmap_dir))) { + + os_file_get_last_error(TRUE); + msg("InnoDB: Error: cannot close \'%s\'\n",srv_data_home); + return FALSE; + } + + if (first_file_seq_num == ULONG_MAX && last_file_seq_num == 0) { + + bitmap_files->count = 0; + return TRUE; + } + + bitmap_files->count = last_file_seq_num - first_file_seq_num + 1; + + /* 2nd pass: get the file names in the file_seq_num order */ + + bitmap_dir = os_file_opendir(srv_data_home, FALSE); + if (UNIV_UNLIKELY(!bitmap_dir)) { + + msg("InnoDB: Error: failed to open bitmap directory \'%s\'\n", + srv_data_home); + return FALSE; + } + + bitmap_files->files = + static_cast<log_online_bitmap_file_range_t::files_t *> + (ut_malloc(bitmap_files->count + * sizeof(bitmap_files->files[0]))); + memset(bitmap_files->files, 0, + bitmap_files->count * sizeof(bitmap_files->files[0])); + + while (!os_file_readdir_next_file(srv_data_home, bitmap_dir, + &bitmap_dir_file_info)) { + + ulong file_seq_num; + lsn_t file_start_lsn; + size_t array_pos; + + if (!log_online_is_bitmap_file(&bitmap_dir_file_info, + &file_seq_num, + &file_start_lsn) + || file_start_lsn >= range_end + || file_start_lsn < first_file_start_lsn) { + + continue; + } + + array_pos = file_seq_num - first_file_seq_num; + if (UNIV_UNLIKELY(array_pos >= bitmap_files->count)) { + + msg("InnoDB: Error: inconsistent bitmap file " + "directory\n"); + free(bitmap_files->files); + return FALSE; + } + + if (file_seq_num > bitmap_files->files[array_pos].seq_num) { + + bitmap_files->files[array_pos].seq_num = file_seq_num; + strncpy(bitmap_files->files[array_pos].name, + bitmap_dir_file_info.name, FN_REFLEN); + bitmap_files->files[array_pos].name[FN_REFLEN - 1] + = '\0'; + bitmap_files->files[array_pos].start_lsn + = file_start_lsn; + } + } + + if (UNIV_UNLIKELY(os_file_closedir(bitmap_dir))) { + + os_file_get_last_error(TRUE); + msg("InnoDB: Error: cannot close \'%s\'\n", srv_data_home); + free(bitmap_files->files); + return FALSE; + } + +#ifdef UNIV_DEBUG + ut_ad(bitmap_files->files[0].seq_num == first_file_seq_num); + + for (size_t i = 1; i < bitmap_files->count; i++) { + if (!bitmap_files->files[i].seq_num) { + + break; + } + ut_ad(bitmap_files->files[i].seq_num + > bitmap_files->files[i - 1].seq_num); + ut_ad(bitmap_files->files[i].start_lsn + >= bitmap_files->files[i - 1].start_lsn); + } +#endif + + return TRUE; +} + +/****************************************************************//** +Open a bitmap file for reading. + +@return TRUE if opened successfully */ +static +ibool +log_online_open_bitmap_file_read_only( +/*==================================*/ + const char* name, /*!<in: bitmap file + name without directory, + which is assumed to be + srv_data_home */ + log_online_bitmap_file_t* bitmap_file) /*!<out: opened bitmap + file */ +{ + ibool success = FALSE; + + xb_ad(name[0] != '\0'); + + ut_snprintf(bitmap_file->name, FN_REFLEN, "%s%s", srv_data_home, name); + bitmap_file->file + = os_file_create_simple_no_error_handling(0, bitmap_file->name, + OS_FILE_OPEN, + OS_FILE_READ_ONLY, + &success,0); + if (UNIV_UNLIKELY(!success)) { + + /* Here and below assume that bitmap file names do not + contain apostrophes, thus no need for ut_print_filename(). */ + msg("InnoDB: Warning: error opening the changed page " + "bitmap \'%s\'\n", bitmap_file->name); + return FALSE; + } + + bitmap_file->size = os_file_get_size(bitmap_file->file); + bitmap_file->offset = 0; + +#ifdef UNIV_LINUX + posix_fadvise(bitmap_file->file, 0, 0, POSIX_FADV_SEQUENTIAL); + posix_fadvise(bitmap_file->file, 0, 0, POSIX_FADV_NOREUSE); +#endif + + return TRUE; +} + +/****************************************************************//** +Diagnose one or both of the following situations if we read close to +the end of bitmap file: +1) Warn if the remainder of the file is less than one page. +2) Error if we cannot read any more full pages but the last read page +did not have the last-in-run flag set. + +@return FALSE for the error */ +static +ibool +log_online_diagnose_bitmap_eof( +/*===========================*/ + const log_online_bitmap_file_t* bitmap_file, /*!< in: bitmap file */ + ibool last_page_in_run)/*!< in: "last page in + run" flag value in the + last read page */ +{ + /* Check if we are too close to EOF to read a full page */ + if ((bitmap_file->size < MODIFIED_PAGE_BLOCK_SIZE) + || (bitmap_file->offset + > bitmap_file->size - MODIFIED_PAGE_BLOCK_SIZE)) { + + if (UNIV_UNLIKELY(bitmap_file->offset != bitmap_file->size)) { + + /* If we are not at EOF and we have less than one page + to read, it's junk. This error is not fatal in + itself. */ + + msg("InnoDB: Warning: junk at the end of changed " + "page bitmap file \'%s\'.\n", bitmap_file->name); + } + + if (UNIV_UNLIKELY(!last_page_in_run)) { + + /* We are at EOF but the last read page did not finish + a run */ + /* It's a "Warning" here because it's not a fatal error + for the whole server */ + msg("InnoDB: Warning: changed page bitmap " + "file \'%s\' does not contain a complete run " + "at the end.\n", bitmap_file->name); + return FALSE; + } + } + return TRUE; +} + +/* End of copy-pasted definitions */ + +/** Iterator structure over changed page bitmap */ +struct xb_page_bitmap_range_struct { + const xb_page_bitmap *bitmap; /* Bitmap with data */ + ulint space_id; /* Space id for this + iterator */ + ulint bit_i; /* Bit index of the iterator + position in the current page */ + const ib_rbt_node_t *bitmap_node; /* Current bitmap tree node */ + const byte *bitmap_page; /* Current bitmap page */ + ulint current_page_id;/* Current page id */ +}; + +/****************************************************************//** +Print a diagnostic message on missing bitmap data for an LSN range. */ +static +void +xb_msg_missing_lsn_data( +/*====================*/ + lsn_t missing_interval_start, /*!<in: interval start */ + lsn_t missing_interval_end) /*!<in: interval end */ +{ + msg("xtrabackup: warning: changed page data missing for LSNs between " + LSN_PF " and " LSN_PF "\n", missing_interval_start, + missing_interval_end); +} + +/****************************************************************//** +Scan a bitmap file until data for a desired LSN or EOF is found and check that +the page before the starting one is not corrupted to ensure that the found page +indeed contains the very start of the desired LSN data. The caller must check +the page LSN values to determine if the bitmap file was scanned until the data +was found or until EOF. Page must be at least MODIFIED_PAGE_BLOCK_SIZE big. + +@return TRUE if the scan successful without corruption detected +*/ +static +ibool +xb_find_lsn_in_bitmap_file( +/*=======================*/ + log_online_bitmap_file_t *bitmap_file, /*!<in/out: bitmap + file */ + byte *page, /*!<in/out: last read + bitmap page */ + lsn_t *page_end_lsn, /*!<out: end LSN of the + last read page */ + lsn_t lsn) /*!<in: LSN to find */ +{ + ibool last_page_ok = TRUE; + ibool next_to_last_page_ok = TRUE; + + xb_ad (bitmap_file->size >= MODIFIED_PAGE_BLOCK_SIZE); + + *page_end_lsn = 0; + + while ((*page_end_lsn <= lsn) + && (bitmap_file->offset + <= bitmap_file->size - MODIFIED_PAGE_BLOCK_SIZE)) { + + next_to_last_page_ok = last_page_ok; + if (!log_online_read_bitmap_page(bitmap_file, page, + &last_page_ok)) { + + return FALSE; + } + + *page_end_lsn = mach_read_from_8(page + MODIFIED_PAGE_END_LSN); + } + + /* We check two pages here because the last read page already contains + the required LSN data. If the next to the last one page is corrupted, + then we have no way of telling if that page contained the required LSN + range data too */ + return last_page_ok && next_to_last_page_ok; +} + +/****************************************************************//** +Read the disk bitmap and build the changed page bitmap tree for the +LSN interval incremental_lsn to checkpoint_lsn_start. + +@return the built bitmap tree or NULL if unable to read the full interval for +any reason. */ +xb_page_bitmap* +xb_page_bitmap_init(void) +/*=====================*/ +{ + log_online_bitmap_file_t bitmap_file; + lsn_t bmp_start_lsn = incremental_lsn; + lsn_t bmp_end_lsn = checkpoint_lsn_start; + byte page[MODIFIED_PAGE_BLOCK_SIZE]; + lsn_t current_page_end_lsn; + xb_page_bitmap *result; + ibool last_page_in_run= FALSE; + log_online_bitmap_file_range_t bitmap_files; + size_t bmp_i; + ibool last_page_ok = TRUE; + + if (UNIV_UNLIKELY(bmp_start_lsn > bmp_end_lsn)) { + + msg("xtrabackup: incremental backup LSN " LSN_PF + " is larger than than the last checkpoint LSN " LSN_PF + "\n", bmp_start_lsn, bmp_end_lsn); + return NULL; + } + + if (!log_online_setup_bitmap_file_range(&bitmap_files, bmp_start_lsn, + bmp_end_lsn)) { + + return NULL; + } + + /* Only accept no bitmap files returned if start LSN == end LSN */ + if (bitmap_files.count == 0 && bmp_end_lsn != bmp_start_lsn) { + + return NULL; + } + + result = rbt_create(MODIFIED_PAGE_BLOCK_SIZE, + log_online_compare_bmp_keys); + + if (bmp_start_lsn == bmp_end_lsn) { + + /* Empty range - empty bitmap */ + return result; + } + + bmp_i = 0; + + if (UNIV_UNLIKELY(bitmap_files.files[bmp_i].start_lsn + > bmp_start_lsn)) { + + /* The 1st file does not have the starting LSN data */ + xb_msg_missing_lsn_data(bmp_start_lsn, + bitmap_files.files[bmp_i].start_lsn); + rbt_free(result); + free(bitmap_files.files); + return NULL; + } + + /* Skip any zero-sized files at the start */ + while ((bmp_i < bitmap_files.count - 1) + && (bitmap_files.files[bmp_i].start_lsn + == bitmap_files.files[bmp_i + 1].start_lsn)) { + + bmp_i++; + } + + /* Is the 1st bitmap file missing? */ + if (UNIV_UNLIKELY(bitmap_files.files[bmp_i].name[0] == '\0')) { + + /* TODO: this is not the exact missing range */ + xb_msg_missing_lsn_data(bmp_start_lsn, bmp_end_lsn); + rbt_free(result); + free(bitmap_files.files); + return NULL; + } + + /* Open the 1st bitmap file */ + if (UNIV_UNLIKELY(!log_online_open_bitmap_file_read_only( + bitmap_files.files[bmp_i].name, + &bitmap_file))) { + + rbt_free(result); + free(bitmap_files.files); + return NULL; + } + + /* If the 1st file is truncated, no data. Not merged with the case + below because zero-length file indicates not a corruption but missing + subsequent files instead. */ + if (UNIV_UNLIKELY(bitmap_file.size < MODIFIED_PAGE_BLOCK_SIZE)) { + + xb_msg_missing_lsn_data(bmp_start_lsn, bmp_end_lsn); + rbt_free(result); + free(bitmap_files.files); + os_file_close(bitmap_file.file); + return NULL; + } + + /* Find the start of the required LSN range in the file */ + if (UNIV_UNLIKELY(!xb_find_lsn_in_bitmap_file(&bitmap_file, page, + ¤t_page_end_lsn, + bmp_start_lsn))) { + + msg("xtrabackup: Warning: changed page bitmap file " + "\'%s\' corrupted\n", bitmap_file.name); + rbt_free(result); + free(bitmap_files.files); + os_file_close(bitmap_file.file); + return NULL; + } + + last_page_in_run + = mach_read_from_4(page + MODIFIED_PAGE_IS_LAST_BLOCK); + + if (UNIV_UNLIKELY(!log_online_diagnose_bitmap_eof(&bitmap_file, + last_page_in_run))) { + + rbt_free(result); + free(bitmap_files.files); + os_file_close(bitmap_file.file); + return NULL; + } + + if (UNIV_UNLIKELY(current_page_end_lsn < bmp_start_lsn)) { + + xb_msg_missing_lsn_data(current_page_end_lsn, bmp_start_lsn); + rbt_free(result); + free(bitmap_files.files); + os_file_close(bitmap_file.file); + return NULL; + } + + /* 1st bitmap page found, add it to the tree. */ + rbt_insert(result, page, page); + + /* Read next pages/files until all required data is read */ + while (last_page_ok + && (current_page_end_lsn < bmp_end_lsn + || (current_page_end_lsn == bmp_end_lsn + && !last_page_in_run))) { + + ib_rbt_bound_t tree_search_pos; + + /* If EOF, advance the file skipping over any empty files */ + while (bitmap_file.size < MODIFIED_PAGE_BLOCK_SIZE + || (bitmap_file.offset + > bitmap_file.size - MODIFIED_PAGE_BLOCK_SIZE)) { + + os_file_close(bitmap_file.file); + + if (UNIV_UNLIKELY( + !log_online_diagnose_bitmap_eof( + &bitmap_file, last_page_in_run))) { + + rbt_free(result); + free(bitmap_files.files); + return NULL; + } + + bmp_i++; + + if (UNIV_UNLIKELY(bmp_i == bitmap_files.count + || (bitmap_files.files[bmp_i].seq_num + == 0))) { + + xb_msg_missing_lsn_data(current_page_end_lsn, + bmp_end_lsn); + rbt_free(result); + free(bitmap_files.files); + return NULL; + } + + /* Is the next file missing? */ + if (UNIV_UNLIKELY(bitmap_files.files[bmp_i].name[0] + == '\0')) { + + /* TODO: this is not the exact missing range */ + xb_msg_missing_lsn_data(bitmap_files.files + [bmp_i - 1].start_lsn, + bmp_end_lsn); + rbt_free(result); + free(bitmap_files.files); + return NULL; + } + + if (UNIV_UNLIKELY( + !log_online_open_bitmap_file_read_only( + bitmap_files.files[bmp_i].name, + &bitmap_file))) { + + rbt_free(result); + free(bitmap_files.files); + return NULL; + } + } + + if (UNIV_UNLIKELY( + !log_online_read_bitmap_page(&bitmap_file, page, + &last_page_ok))) { + + rbt_free(result); + free(bitmap_files.files); + os_file_close(bitmap_file.file); + return NULL; + } + + if (UNIV_UNLIKELY(!last_page_ok)) { + + msg("xtrabackup: warning: changed page bitmap file " + "\'%s\' corrupted.\n", bitmap_file.name); + rbt_free(result); + free(bitmap_files.files); + os_file_close(bitmap_file.file); + return NULL; + } + + /* Merge the current page with an existing page or insert a new + page into the tree */ + + if (!rbt_search(result, &tree_search_pos, page)) { + + /* Merge the bitmap pages */ + byte *existing_page + = rbt_value(byte, tree_search_pos.last); + bitmap_word_t *bmp_word_1 = (bitmap_word_t *) + (existing_page + MODIFIED_PAGE_BLOCK_BITMAP); + bitmap_word_t *bmp_end = (bitmap_word_t *) + (existing_page + MODIFIED_PAGE_BLOCK_UNUSED_2); + bitmap_word_t *bmp_word_2 = (bitmap_word_t *) + (page + MODIFIED_PAGE_BLOCK_BITMAP); + while (bmp_word_1 < bmp_end) { + + *bmp_word_1++ |= *bmp_word_2++; + } + xb_a (bmp_word_1 == bmp_end); + } else { + + /* Add a new page */ + rbt_add_node(result, &tree_search_pos, page); + } + + current_page_end_lsn + = mach_read_from_8(page + MODIFIED_PAGE_END_LSN); + last_page_in_run + = mach_read_from_4(page + MODIFIED_PAGE_IS_LAST_BLOCK); + } + + xb_a (current_page_end_lsn >= bmp_end_lsn); + + free(bitmap_files.files); + os_file_close(bitmap_file.file); + + return result; +} + +/****************************************************************//** +Free the bitmap tree. */ +void +xb_page_bitmap_deinit( +/*==================*/ + xb_page_bitmap* bitmap) /*!<in/out: bitmap tree */ +{ + if (bitmap) { + + rbt_free(bitmap); + } +} + +/****************************************************************//** +Advance to the next bitmap page or setup the first bitmap page for the +given bitmap range. Assumes that bitmap_range->bitmap_page has been +already found/bumped by rbt_search()/rbt_next(). + +@return FALSE if no more bitmap data for the range space ID */ +static +ibool +xb_page_bitmap_setup_next_page( +/*===========================*/ + xb_page_bitmap_range* bitmap_range) /*!<in/out: the bitmap range */ +{ + ulint new_space_id; + ulint new_1st_page_id; + + if (bitmap_range->bitmap_node == NULL) { + + bitmap_range->current_page_id = ULINT_UNDEFINED; + return FALSE; + } + + bitmap_range->bitmap_page = rbt_value(byte, bitmap_range->bitmap_node); + + new_space_id = mach_read_from_4(bitmap_range->bitmap_page + + MODIFIED_PAGE_SPACE_ID); + if (new_space_id != bitmap_range->space_id) { + + /* No more data for the current page id. */ + xb_a(new_space_id > bitmap_range->space_id); + bitmap_range->current_page_id = ULINT_UNDEFINED; + return FALSE; + } + + new_1st_page_id = mach_read_from_4(bitmap_range->bitmap_page + + MODIFIED_PAGE_1ST_PAGE_ID); + xb_a (new_1st_page_id >= bitmap_range->current_page_id + || bitmap_range->current_page_id == ULINT_UNDEFINED); + + bitmap_range->current_page_id = new_1st_page_id; + bitmap_range->bit_i = 0; + + return TRUE; +} + +/****************************************************************//** +Set up a new bitmap range iterator over a given space id changed +pages in a given bitmap. + +@return bitmap range iterator */ +xb_page_bitmap_range* +xb_page_bitmap_range_init( +/*======================*/ + xb_page_bitmap* bitmap, /*!< in: bitmap to iterate over */ + ulint space_id) /*!< in: space id */ +{ + byte search_page[MODIFIED_PAGE_BLOCK_SIZE]; + xb_page_bitmap_range *result + = static_cast<xb_page_bitmap_range *> + (ut_malloc(sizeof(*result))); + + memset(result, 0, sizeof(*result)); + result->bitmap = bitmap; + result->space_id = space_id; + result->current_page_id = ULINT_UNDEFINED; + + /* Search for the 1st page for the given space id */ + /* This also sets MODIFIED_PAGE_1ST_PAGE_ID to 0, which is what we + want. */ + memset(search_page, 0, MODIFIED_PAGE_BLOCK_SIZE); + mach_write_to_4(search_page + MODIFIED_PAGE_SPACE_ID, space_id); + + result->bitmap_node = rbt_lower_bound(result->bitmap, search_page); + + xb_page_bitmap_setup_next_page(result); + + return result; +} + +/****************************************************************//** +Get the value of the bitmap->range->bit_i bitmap bit + +@return the current bit value */ +static inline +ibool +is_bit_set( +/*=======*/ + const xb_page_bitmap_range* bitmap_range) /*!< in: bitmap + range */ +{ + return ((*(((bitmap_word_t *)(bitmap_range->bitmap_page + + MODIFIED_PAGE_BLOCK_BITMAP)) + + (bitmap_range->bit_i >> 6))) + & (1ULL << (bitmap_range->bit_i & 0x3F))) ? TRUE : FALSE; +} + +/****************************************************************//** +Get the next page id that has its bit set or cleared, i.e. equal to +bit_value. + +@return page id */ +ulint +xb_page_bitmap_range_get_next_bit( +/*==============================*/ + xb_page_bitmap_range* bitmap_range, /*!< in/out: bitmap range */ + ibool bit_value) /*!< in: bit value */ +{ + if (UNIV_UNLIKELY(bitmap_range->current_page_id + == ULINT_UNDEFINED)) { + + return ULINT_UNDEFINED; + } + + do { + while (bitmap_range->bit_i < MODIFIED_PAGE_BLOCK_ID_COUNT) { + + while (is_bit_set(bitmap_range) != bit_value + && (bitmap_range->bit_i + < MODIFIED_PAGE_BLOCK_ID_COUNT)) { + + bitmap_range->current_page_id++; + bitmap_range->bit_i++; + } + + if (bitmap_range->bit_i + < MODIFIED_PAGE_BLOCK_ID_COUNT) { + + ulint result = bitmap_range->current_page_id; + bitmap_range->current_page_id++; + bitmap_range->bit_i++; + return result; + } + } + + bitmap_range->bitmap_node + = rbt_next(bitmap_range->bitmap, + bitmap_range->bitmap_node); + + } while (xb_page_bitmap_setup_next_page(bitmap_range)); + + return ULINT_UNDEFINED; +} + +/****************************************************************//** +Free the bitmap range iterator. */ +void +xb_page_bitmap_range_deinit( +/*========================*/ + xb_page_bitmap_range* bitmap_range) /*! in/out: bitmap range */ +{ + ut_free(bitmap_range); +} diff --git a/extra/mariabackup/changed_page_bitmap.h b/extra/mariabackup/changed_page_bitmap.h new file mode 100644 index 00000000000..6f549f47400 --- /dev/null +++ b/extra/mariabackup/changed_page_bitmap.h @@ -0,0 +1,85 @@ +/****************************************************** +XtraBackup: hot backup tool for InnoDB +(c) 2009-2012 Percona Inc. +Originally Created 3/3/2009 Yasufumi Kinoshita +Written by Alexey Kopytov, Aleksandr Kuzminsky, Stewart Smith, Vadim Tkachenko, +Yasufumi Kinoshita, Ignacio Nin and Baron Schwartz. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +/* Changed page bitmap interface */ + +#ifndef XB_CHANGED_PAGE_BITMAP_H +#define XB_CHANGED_PAGE_BITMAP_H + +#include <ut0rbt.h> +#include <fil0fil.h> + +/* The changed page bitmap structure */ +typedef ib_rbt_t xb_page_bitmap; + +struct xb_page_bitmap_range_struct; + +/* The bitmap range iterator over one space id */ +typedef struct xb_page_bitmap_range_struct xb_page_bitmap_range; + +/****************************************************************//** +Read the disk bitmap and build the changed page bitmap tree for the +LSN interval incremental_lsn to checkpoint_lsn_start. + +@return the built bitmap tree */ +xb_page_bitmap* +xb_page_bitmap_init(void); +/*=====================*/ + +/****************************************************************//** +Free the bitmap tree. */ +void +xb_page_bitmap_deinit( +/*==================*/ + xb_page_bitmap* bitmap); /*!<in/out: bitmap tree */ + + +/****************************************************************//** +Set up a new bitmap range iterator over a given space id changed +pages in a given bitmap. + +@return bitmap range iterator */ +xb_page_bitmap_range* +xb_page_bitmap_range_init( +/*======================*/ + xb_page_bitmap* bitmap, /*!< in: bitmap to iterate over */ + ulint space_id); /*!< in: space id */ + +/****************************************************************//** +Get the next page id that has its bit set or cleared, i.e. equal to +bit_value. + +@return page id */ +ulint +xb_page_bitmap_range_get_next_bit( +/*==============================*/ + xb_page_bitmap_range* bitmap_range, /*!< in/out: bitmap range */ + ibool bit_value); /*!< in: bit value */ + +/****************************************************************//** +Free the bitmap range iterator. */ +void +xb_page_bitmap_range_deinit( +/*========================*/ + xb_page_bitmap_range* bitmap_range); /*! in/out: bitmap range */ + +#endif diff --git a/extra/mariabackup/common.h b/extra/mariabackup/common.h new file mode 100644 index 00000000000..7b1dfd7a0db --- /dev/null +++ b/extra/mariabackup/common.h @@ -0,0 +1,174 @@ +/****************************************************** +Copyright (c) 2011-2013 Percona LLC and/or its affiliates. + +Common declarations for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#ifndef XB_COMMON_H +#define XB_COMMON_H + +#include <my_global.h> +#include <mysql_version.h> +#include <fcntl.h> +#include <stdarg.h> + + +# define fil_is_user_tablespace_id(i) ((i) > srv_undo_tablespaces_open) + +#ifdef _MSC_VER +#define stat _stati64 +#define PATH_MAX MAX_PATH +#endif + +#ifndef HAVE_VASPRINTF +static inline int vasprintf(char **strp, const char *fmt, va_list args) +{ + int len; +#ifdef _MSC_VER + len = _vscprintf(fmt, args); +#else + len = vsnprintf(NULL, 0, fmt, args); +#endif + if (len < 0) + { + return -1; + } + *strp = (char *)malloc(len + 1); + if (!*strp) + { + return -1; + } + vsprintf(*strp, fmt, args); + return len; +} + +static inline int asprintf(char **strp, const char *fmt,...) +{ + va_list args; + va_start(args, fmt); + int len = vasprintf(strp, fmt, args); + va_end(args); + return len; +} +#endif + +#define xb_a(expr) \ + do { \ + if (!(expr)) { \ + msg("Assertion \"%s\" failed at %s:%lu\n", \ + #expr, __FILE__, (ulong) __LINE__); \ + abort(); \ + } \ + } while (0); + +#ifdef XB_DEBUG +#define xb_ad(expr) xb_a(expr) +#else +#define xb_ad(expr) +#endif + +#define XB_DELTA_INFO_SUFFIX ".meta" + +static inline int msg(const char *fmt, ...) ATTRIBUTE_FORMAT(printf, 1, 2); +static inline int msg(const char *fmt, ...) +{ + int result; + va_list args; + + va_start(args, fmt); + result = vfprintf(stderr, fmt, args); + va_end(args); + + return result; +} + +static inline int msg_ts(const char *fmt, ...) ATTRIBUTE_FORMAT(printf, 1, 2); +static inline int msg_ts(const char *fmt, ...) +{ + int result; + time_t t = time(NULL); + char date[100]; + char *line; + va_list args; + + strftime(date, sizeof(date), "%y%m%d %H:%M:%S", localtime(&t)); + + va_start(args, fmt); + result = vasprintf(&line, fmt, args); + va_end(args); + + if (result != -1) { + result = fprintf(stderr, "%s %s", date, line); + free(line); + } + + return result; +} + +/* Use POSIX_FADV_NORMAL when available */ + +#ifdef POSIX_FADV_NORMAL +# define USE_POSIX_FADVISE +#else +# define POSIX_FADV_NORMAL +# define POSIX_FADV_SEQUENTIAL +# define POSIX_FADV_DONTNEED +# define posix_fadvise(a,b,c,d) do {} while(0) +#endif + +/*********************************************************************** +Computes bit shift for a given value. If the argument is not a power +of 2, returns 0.*/ +static inline size_t +get_bit_shift(size_t value) +{ + size_t shift; + + if (value == 0) + return 0; + + for (shift = 0; !(value & 1); shift++) { + value >>= 1; + } + return (value >> 1) ? 0 : shift; +} + +/**************************************************************************** +Read 'len' bytes from 'fd'. It is identical to my_read(..., MYF(MY_FULL_IO)), +i.e. tries to combine partial reads into a single block of size 'len', except +that it bails out on EOF or error, and returns the number of successfully read +bytes instead. */ +static inline size_t +xb_read_full(File fd, uchar *buf, size_t len) +{ + size_t tlen = 0; + size_t tbytes; + + while (tlen < len) { + tbytes = my_read(fd, buf, len - tlen, MYF(MY_WME)); + if (tbytes == 0 || tbytes == MY_FILE_ERROR) { + break; + } + + buf += tbytes; + tlen += tbytes; + } + + return tlen; +} + +#endif diff --git a/extra/mariabackup/crc/CMakeLists.txt b/extra/mariabackup/crc/CMakeLists.txt new file mode 100644 index 00000000000..91758cdf520 --- /dev/null +++ b/extra/mariabackup/crc/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright (c) 2017 Percona LLC and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. + +# This program 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 General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +PROJECT(crc C) + +IF(NOT CMAKE_CROSSCOMPILING AND NOT MSVC) + STRING(TOLOWER ${CMAKE_SYSTEM_PROCESSOR} processor) + IF(processor MATCHES "86" OR processor MATCHES "amd64" OR processor MATCHES "x64") + # Check for PCLMUL instruction + CHECK_C_SOURCE_RUNS(" + int main() + { + asm volatile (\"pclmulqdq \\$0x00, %%xmm1, %%xmm0\":::\"cc\"); + return 0; + }" HAVE_CLMUL_INSTRUCTION) + ENDIF() +ENDIF() +IF(HAVE_CLMUL_INSTRUCTION) + ADD_DEFINITIONS(-DHAVE_CLMUL_INSTRUCTION) +ENDIF() +ADD_LIBRARY(crc STATIC crc_glue.c crc-intel-pclmul.c) diff --git a/extra/mariabackup/crc/config.h.cmake b/extra/mariabackup/crc/config.h.cmake new file mode 100644 index 00000000000..fe81c1859ae --- /dev/null +++ b/extra/mariabackup/crc/config.h.cmake @@ -0,0 +1,21 @@ +/****************************************************** +Copyright (c) 2017 Percona LLC and/or its affiliates. + +Zlib compatible CRC-32 implementation. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#cmakedefine HAVE_CLMUL_INSTRUCTION 1 diff --git a/extra/mariabackup/crc/crc-intel-pclmul.c b/extra/mariabackup/crc/crc-intel-pclmul.c new file mode 100644 index 00000000000..d470c2bee43 --- /dev/null +++ b/extra/mariabackup/crc/crc-intel-pclmul.c @@ -0,0 +1,511 @@ +/****************************************************** +Copyright (c) 2017 Percona LLC and/or its affiliates. + +CRC32 using Intel's PCLMUL instruction. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +/* crc-intel-pclmul.c - Intel PCLMUL accelerated CRC implementation + * Copyright (C) 2016 Jussi Kivilinna <jussi.kivilinna@iki.fi> + * + * This file is part of Libgcrypt. + * + * Libgcrypt 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. + * + * Libgcrypt 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> + +# define U64_C(c) (c ## UL) + +typedef uint32_t u32; +typedef uint16_t u16; +typedef uint64_t u64; +#ifndef byte +typedef uint8_t byte; +#endif + +# define _gcry_bswap32 __builtin_bswap32 + +#if __GNUC__ >= 4 && defined(__x86_64__) && defined(HAVE_CLMUL_INSTRUCTION) + +#if _GCRY_GCC_VERSION >= 40400 /* 4.4 */ +/* Prevent compiler from issuing SSE instructions between asm blocks. */ +# pragma GCC target("no-sse") +#endif + + +#define ALIGNED_16 __attribute__ ((aligned (16))) + + +struct u16_unaligned_s +{ + u16 a; +} __attribute__((packed, aligned (1), may_alias)); + + +/* Constants structure for generic reflected/non-reflected CRC32 CLMUL + * functions. */ +struct crc32_consts_s +{ + /* k: { x^(32*17), x^(32*15), x^(32*5), x^(32*3), x^(32*2), 0 } mod P(x) */ + u64 k[6]; + /* my_p: { floor(x^64 / P(x)), P(x) } */ + u64 my_p[2]; +}; + + +/* CLMUL constants for CRC32 and CRC32RFC1510. */ +static const struct crc32_consts_s crc32_consts ALIGNED_16 = +{ + { /* k[6] = reverse_33bits( x^(32*y) mod P(x) ) */ + U64_C(0x154442bd4), U64_C(0x1c6e41596), /* y = { 17, 15 } */ + U64_C(0x1751997d0), U64_C(0x0ccaa009e), /* y = { 5, 3 } */ + U64_C(0x163cd6124), 0 /* y = 2 */ + }, + { /* my_p[2] = reverse_33bits ( { floor(x^64 / P(x)), P(x) } ) */ + U64_C(0x1f7011641), U64_C(0x1db710641) + } +}; + +/* Common constants for CRC32 algorithms. */ +static const byte crc32_refl_shuf_shift[3 * 16] ALIGNED_16 = + { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }; +static const byte crc32_partial_fold_input_mask[16 + 16] ALIGNED_16 = + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }; +static const u64 crc32_merge9to15_shuf[15 - 9 + 1][2] ALIGNED_16 = + { + { U64_C(0x0706050403020100), U64_C(0xffffffffffffff0f) }, /* 9 */ + { U64_C(0x0706050403020100), U64_C(0xffffffffffff0f0e) }, + { U64_C(0x0706050403020100), U64_C(0xffffffffff0f0e0d) }, + { U64_C(0x0706050403020100), U64_C(0xffffffff0f0e0d0c) }, + { U64_C(0x0706050403020100), U64_C(0xffffff0f0e0d0c0b) }, + { U64_C(0x0706050403020100), U64_C(0xffff0f0e0d0c0b0a) }, + { U64_C(0x0706050403020100), U64_C(0xff0f0e0d0c0b0a09) }, /* 15 */ + }; +static const u64 crc32_merge5to7_shuf[7 - 5 + 1][2] ALIGNED_16 = + { + { U64_C(0xffffff0703020100), U64_C(0xffffffffffffffff) }, /* 5 */ + { U64_C(0xffff070603020100), U64_C(0xffffffffffffffff) }, + { U64_C(0xff07060503020100), U64_C(0xffffffffffffffff) }, /* 7 */ + }; + +/* PCLMUL functions for reflected CRC32. */ +static inline void +crc32_reflected_bulk (u32 *pcrc, const byte *inbuf, size_t inlen, + const struct crc32_consts_s *consts) +{ + if (inlen >= 8 * 16) + { + asm volatile ("movd %[crc], %%xmm4\n\t" + "movdqu %[inbuf_0], %%xmm0\n\t" + "movdqu %[inbuf_1], %%xmm1\n\t" + "movdqu %[inbuf_2], %%xmm2\n\t" + "movdqu %[inbuf_3], %%xmm3\n\t" + "pxor %%xmm4, %%xmm0\n\t" + : + : [inbuf_0] "m" (inbuf[0 * 16]), + [inbuf_1] "m" (inbuf[1 * 16]), + [inbuf_2] "m" (inbuf[2 * 16]), + [inbuf_3] "m" (inbuf[3 * 16]), + [crc] "m" (*pcrc) + ); + + inbuf += 4 * 16; + inlen -= 4 * 16; + + asm volatile ("movdqa %[k1k2], %%xmm4\n\t" + : + : [k1k2] "m" (consts->k[1 - 1]) + ); + + /* Fold by 4. */ + while (inlen >= 4 * 16) + { + asm volatile ("movdqu %[inbuf_0], %%xmm5\n\t" + "movdqa %%xmm0, %%xmm6\n\t" + "pclmulqdq $0x00, %%xmm4, %%xmm0\n\t" + "pclmulqdq $0x11, %%xmm4, %%xmm6\n\t" + "pxor %%xmm5, %%xmm0\n\t" + "pxor %%xmm6, %%xmm0\n\t" + + "movdqu %[inbuf_1], %%xmm5\n\t" + "movdqa %%xmm1, %%xmm6\n\t" + "pclmulqdq $0x00, %%xmm4, %%xmm1\n\t" + "pclmulqdq $0x11, %%xmm4, %%xmm6\n\t" + "pxor %%xmm5, %%xmm1\n\t" + "pxor %%xmm6, %%xmm1\n\t" + + "movdqu %[inbuf_2], %%xmm5\n\t" + "movdqa %%xmm2, %%xmm6\n\t" + "pclmulqdq $0x00, %%xmm4, %%xmm2\n\t" + "pclmulqdq $0x11, %%xmm4, %%xmm6\n\t" + "pxor %%xmm5, %%xmm2\n\t" + "pxor %%xmm6, %%xmm2\n\t" + + "movdqu %[inbuf_3], %%xmm5\n\t" + "movdqa %%xmm3, %%xmm6\n\t" + "pclmulqdq $0x00, %%xmm4, %%xmm3\n\t" + "pclmulqdq $0x11, %%xmm4, %%xmm6\n\t" + "pxor %%xmm5, %%xmm3\n\t" + "pxor %%xmm6, %%xmm3\n\t" + : + : [inbuf_0] "m" (inbuf[0 * 16]), + [inbuf_1] "m" (inbuf[1 * 16]), + [inbuf_2] "m" (inbuf[2 * 16]), + [inbuf_3] "m" (inbuf[3 * 16]) + ); + + inbuf += 4 * 16; + inlen -= 4 * 16; + } + + asm volatile ("movdqa %[k3k4], %%xmm6\n\t" + "movdqa %[my_p], %%xmm5\n\t" + : + : [k3k4] "m" (consts->k[3 - 1]), + [my_p] "m" (consts->my_p[0]) + ); + + /* Fold 4 to 1. */ + + asm volatile ("movdqa %%xmm0, %%xmm4\n\t" + "pclmulqdq $0x00, %%xmm6, %%xmm0\n\t" + "pclmulqdq $0x11, %%xmm6, %%xmm4\n\t" + "pxor %%xmm1, %%xmm0\n\t" + "pxor %%xmm4, %%xmm0\n\t" + + "movdqa %%xmm0, %%xmm4\n\t" + "pclmulqdq $0x00, %%xmm6, %%xmm0\n\t" + "pclmulqdq $0x11, %%xmm6, %%xmm4\n\t" + "pxor %%xmm2, %%xmm0\n\t" + "pxor %%xmm4, %%xmm0\n\t" + + "movdqa %%xmm0, %%xmm4\n\t" + "pclmulqdq $0x00, %%xmm6, %%xmm0\n\t" + "pclmulqdq $0x11, %%xmm6, %%xmm4\n\t" + "pxor %%xmm3, %%xmm0\n\t" + "pxor %%xmm4, %%xmm0\n\t" + : + : + ); + } + else + { + asm volatile ("movd %[crc], %%xmm1\n\t" + "movdqu %[inbuf], %%xmm0\n\t" + "movdqa %[k3k4], %%xmm6\n\t" + "pxor %%xmm1, %%xmm0\n\t" + "movdqa %[my_p], %%xmm5\n\t" + : + : [inbuf] "m" (*inbuf), + [crc] "m" (*pcrc), + [k3k4] "m" (consts->k[3 - 1]), + [my_p] "m" (consts->my_p[0]) + ); + + inbuf += 16; + inlen -= 16; + } + + /* Fold by 1. */ + if (inlen >= 16) + { + while (inlen >= 16) + { + /* Load next block to XMM2. Fold XMM0 to XMM0:XMM1. */ + asm volatile ("movdqu %[inbuf], %%xmm2\n\t" + "movdqa %%xmm0, %%xmm1\n\t" + "pclmulqdq $0x00, %%xmm6, %%xmm0\n\t" + "pclmulqdq $0x11, %%xmm6, %%xmm1\n\t" + "pxor %%xmm2, %%xmm0\n\t" + "pxor %%xmm1, %%xmm0\n\t" + : + : [inbuf] "m" (*inbuf) + ); + + inbuf += 16; + inlen -= 16; + } + } + + /* Partial fold. */ + if (inlen) + { + /* Load last input and add padding zeros. */ + asm volatile ("movdqu %[shr_shuf], %%xmm3\n\t" + "movdqu %[shl_shuf], %%xmm4\n\t" + "movdqu %[mask], %%xmm2\n\t" + + "movdqa %%xmm0, %%xmm1\n\t" + "pshufb %%xmm4, %%xmm0\n\t" + "movdqu %[inbuf], %%xmm4\n\t" + "pshufb %%xmm3, %%xmm1\n\t" + "pand %%xmm4, %%xmm2\n\t" + "por %%xmm1, %%xmm2\n\t" + + "movdqa %%xmm0, %%xmm1\n\t" + "pclmulqdq $0x00, %%xmm6, %%xmm0\n\t" + "pclmulqdq $0x11, %%xmm6, %%xmm1\n\t" + "pxor %%xmm2, %%xmm0\n\t" + "pxor %%xmm1, %%xmm0\n\t" + : + : [inbuf] "m" (*(inbuf - 16 + inlen)), + [mask] "m" (crc32_partial_fold_input_mask[inlen]), + [shl_shuf] "m" (crc32_refl_shuf_shift[inlen]), + [shr_shuf] "m" (crc32_refl_shuf_shift[inlen + 16]) + ); + + inbuf += inlen; + inlen -= inlen; + } + + /* Final fold. */ + asm volatile (/* reduce 128-bits to 96-bits */ + "movdqa %%xmm0, %%xmm1\n\t" + "pclmulqdq $0x10, %%xmm6, %%xmm0\n\t" + "psrldq $8, %%xmm1\n\t" + "pxor %%xmm1, %%xmm0\n\t" + + /* reduce 96-bits to 64-bits */ + "pshufd $0xfc, %%xmm0, %%xmm1\n\t" /* [00][00][00][x] */ + "pshufd $0xf9, %%xmm0, %%xmm0\n\t" /* [00][00][x>>64][x>>32] */ + "pclmulqdq $0x00, %[k5], %%xmm1\n\t" /* [00][00][xx][xx] */ + "pxor %%xmm1, %%xmm0\n\t" /* top 64-bit are zero */ + + /* barrett reduction */ + "pshufd $0xf3, %%xmm0, %%xmm1\n\t" /* [00][00][x>>32][00] */ + "pslldq $4, %%xmm0\n\t" /* [??][x>>32][??][??] */ + "pclmulqdq $0x00, %%xmm5, %%xmm1\n\t" /* [00][xx][xx][00] */ + "pclmulqdq $0x10, %%xmm5, %%xmm1\n\t" /* [00][xx][xx][00] */ + "pxor %%xmm1, %%xmm0\n\t" + + /* store CRC */ + "pextrd $2, %%xmm0, %[out]\n\t" + : [out] "=m" (*pcrc) + : [k5] "m" (consts->k[5 - 1]) + ); +} + +static inline void +crc32_reflected_less_than_16 (u32 *pcrc, const byte *inbuf, size_t inlen, + const struct crc32_consts_s *consts) +{ + if (inlen < 4) + { + u32 crc = *pcrc; + u32 data; + + asm volatile ("movdqa %[my_p], %%xmm5\n\t" + : + : [my_p] "m" (consts->my_p[0]) + ); + + if (inlen == 1) + { + data = inbuf[0]; + data ^= crc; + data <<= 24; + crc >>= 8; + } + else if (inlen == 2) + { + data = ((const struct u16_unaligned_s *)inbuf)->a; + data ^= crc; + data <<= 16; + crc >>= 16; + } + else + { + data = ((const struct u16_unaligned_s *)inbuf)->a; + data |= inbuf[2] << 16; + data ^= crc; + data <<= 8; + crc >>= 24; + } + + /* Barrett reduction */ + asm volatile ("movd %[in], %%xmm0\n\t" + "movd %[crc], %%xmm1\n\t" + + "pclmulqdq $0x00, %%xmm5, %%xmm0\n\t" /* [00][00][xx][xx] */ + "psllq $32, %%xmm1\n\t" + "pshufd $0xfc, %%xmm0, %%xmm0\n\t" /* [00][00][00][x] */ + "pclmulqdq $0x10, %%xmm5, %%xmm0\n\t" /* [00][00][xx][xx] */ + "pxor %%xmm1, %%xmm0\n\t" + + "pextrd $1, %%xmm0, %[out]\n\t" + : [out] "=m" (*pcrc) + : [in] "rm" (data), + [crc] "rm" (crc) + ); + } + else if (inlen == 4) + { + /* Barrett reduction */ + asm volatile ("movd %[crc], %%xmm1\n\t" + "movd %[in], %%xmm0\n\t" + "movdqa %[my_p], %%xmm5\n\t" + "pxor %%xmm1, %%xmm0\n\t" + + "pclmulqdq $0x00, %%xmm5, %%xmm0\n\t" /* [00][00][xx][xx] */ + "pshufd $0xfc, %%xmm0, %%xmm0\n\t" /* [00][00][00][x] */ + "pclmulqdq $0x10, %%xmm5, %%xmm0\n\t" /* [00][00][xx][xx] */ + + "pextrd $1, %%xmm0, %[out]\n\t" + : [out] "=m" (*pcrc) + : [in] "m" (*inbuf), + [crc] "m" (*pcrc), + [my_p] "m" (consts->my_p[0]) + ); + } + else + { + asm volatile ("movdqu %[shuf], %%xmm4\n\t" + "movd %[crc], %%xmm1\n\t" + "movdqa %[my_p], %%xmm5\n\t" + "movdqa %[k3k4], %%xmm6\n\t" + : + : [shuf] "m" (crc32_refl_shuf_shift[inlen]), + [crc] "m" (*pcrc), + [my_p] "m" (consts->my_p[0]), + [k3k4] "m" (consts->k[3 - 1]) + ); + + if (inlen >= 8) + { + asm volatile ("movq %[inbuf], %%xmm0\n\t" + : + : [inbuf] "m" (*inbuf) + ); + if (inlen > 8) + { + asm volatile (/*"pinsrq $1, %[inbuf_tail], %%xmm0\n\t"*/ + "movq %[inbuf_tail], %%xmm2\n\t" + "punpcklqdq %%xmm2, %%xmm0\n\t" + "pshufb %[merge_shuf], %%xmm0\n\t" + : + : [inbuf_tail] "m" (inbuf[inlen - 8]), + [merge_shuf] "m" + (*crc32_merge9to15_shuf[inlen - 9]) + ); + } + } + else + { + asm volatile ("movd %[inbuf], %%xmm0\n\t" + "pinsrd $1, %[inbuf_tail], %%xmm0\n\t" + "pshufb %[merge_shuf], %%xmm0\n\t" + : + : [inbuf] "m" (*inbuf), + [inbuf_tail] "m" (inbuf[inlen - 4]), + [merge_shuf] "m" + (*crc32_merge5to7_shuf[inlen - 5]) + ); + } + + /* Final fold. */ + asm volatile ("pxor %%xmm1, %%xmm0\n\t" + "pshufb %%xmm4, %%xmm0\n\t" + + /* reduce 128-bits to 96-bits */ + "movdqa %%xmm0, %%xmm1\n\t" + "pclmulqdq $0x10, %%xmm6, %%xmm0\n\t" + "psrldq $8, %%xmm1\n\t" + "pxor %%xmm1, %%xmm0\n\t" /* top 32-bit are zero */ + + /* reduce 96-bits to 64-bits */ + "pshufd $0xfc, %%xmm0, %%xmm1\n\t" /* [00][00][00][x] */ + "pshufd $0xf9, %%xmm0, %%xmm0\n\t" /* [00][00][x>>64][x>>32] */ + "pclmulqdq $0x00, %[k5], %%xmm1\n\t" /* [00][00][xx][xx] */ + "pxor %%xmm1, %%xmm0\n\t" /* top 64-bit are zero */ + + /* barrett reduction */ + "pshufd $0xf3, %%xmm0, %%xmm1\n\t" /* [00][00][x>>32][00] */ + "pslldq $4, %%xmm0\n\t" /* [??][x>>32][??][??] */ + "pclmulqdq $0x00, %%xmm5, %%xmm1\n\t" /* [00][xx][xx][00] */ + "pclmulqdq $0x10, %%xmm5, %%xmm1\n\t" /* [00][xx][xx][00] */ + "pxor %%xmm1, %%xmm0\n\t" + + /* store CRC */ + "pextrd $2, %%xmm0, %[out]\n\t" + : [out] "=m" (*pcrc) + : [k5] "m" (consts->k[5 - 1]) + ); + } +} + +void +crc32_intel_pclmul (u32 *pcrc, const byte *inbuf, size_t inlen) +{ + const struct crc32_consts_s *consts = &crc32_consts; +#if defined(__x86_64__) && defined(__WIN64__) + char win64tmp[2 * 16]; + + /* XMM6-XMM7 need to be restored after use. */ + asm volatile ("movdqu %%xmm6, 0*16(%0)\n\t" + "movdqu %%xmm7, 1*16(%0)\n\t" + : + : "r" (win64tmp) + : "memory"); +#endif + + if (!inlen) + return; + + if (inlen >= 16) + crc32_reflected_bulk(pcrc, inbuf, inlen, consts); + else + crc32_reflected_less_than_16(pcrc, inbuf, inlen, consts); + +#if defined(__x86_64__) && defined(__WIN64__) + /* Restore used registers. */ + asm volatile("movdqu 0*16(%0), %%xmm6\n\t" + "movdqu 1*16(%0), %%xmm7\n\t" + : + : "r" (win64tmp) + : "memory"); +#endif +} + +#endif diff --git a/extra/mariabackup/crc/crc-intel-pclmul.h b/extra/mariabackup/crc/crc-intel-pclmul.h new file mode 100644 index 00000000000..120058165a0 --- /dev/null +++ b/extra/mariabackup/crc/crc-intel-pclmul.h @@ -0,0 +1,25 @@ +/****************************************************** +Copyright (c) 2017 Percona LLC and/or its affiliates. + +CRC32 using Intel's PCLMUL instruction. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#include <stdint.h> +#include <stddef.h> + +void +crc32_intel_pclmul(uint32_t *pcrc, const uint8_t *inbuf, size_t inlen); diff --git a/extra/mariabackup/crc/crc_glue.c b/extra/mariabackup/crc/crc_glue.c new file mode 100644 index 00000000000..ae3fa91c1b0 --- /dev/null +++ b/extra/mariabackup/crc/crc_glue.c @@ -0,0 +1,72 @@ +/****************************************************** +Copyright (c) 2017 Percona LLC and/or its affiliates. + +Zlib compatible CRC-32 implementation. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#include "crc_glue.h" +#include "crc-intel-pclmul.h" +#include <stdint.h> +#include <string.h> +#include <zlib.h> + +#if __GNUC__ >= 4 && defined(__x86_64__) +static int pclmul_enabled = 0; +#endif + +#if defined(__GNUC__) && defined(__x86_64__) +static +uint32_t +cpuid(uint32_t* ecx, uint32_t* edx) +{ + uint32_t level; + + asm("cpuid" : "=a" (level) : "a" (0) : "ebx", "ecx", "edx"); + + if (level < 1) { + return level; + } + + asm("cpuid" : "=c" (*ecx), "=d" (*edx) + : "a" (1) + : "ebx"); + + return level; +} +#endif + +void crc_init() { +#if defined(__GNUC__) && defined(__x86_64__) + uint32_t ecx, edx; + + if (cpuid(&ecx, &edx) > 0) { + pclmul_enabled = ((ecx >> 19) & 1) && ((ecx >> 1) & 1); + } +#endif +} + +unsigned long crc32_iso3309(unsigned long crc, const unsigned char *buf, unsigned int len) +{ +#if __GNUC__ >= 4 && defined(__x86_64__) && defined(HAVE_CLMUL_INSTRUCTION) + if (pclmul_enabled) { + uint32_t crc_accum = crc ^ 0xffffffffL; + crc32_intel_pclmul(&crc_accum, buf, len); + return crc_accum ^ 0xffffffffL; + } +#endif + return crc32(crc, buf, len); +} diff --git a/extra/mariabackup/crc/crc_glue.h b/extra/mariabackup/crc/crc_glue.h new file mode 100644 index 00000000000..e287fa4a7aa --- /dev/null +++ b/extra/mariabackup/crc/crc_glue.h @@ -0,0 +1,31 @@ +/****************************************************** +Copyright (c) 2017 Percona LLC and/or its affiliates. + +Zlib compatible CRC-32 implementation. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + + +#ifdef __cplusplus +extern "C" { +#endif + +void crc_init(); +unsigned long crc32_iso3309(unsigned long crc, const unsigned char *buf, unsigned int len); + +#ifdef __cplusplus +} +#endif diff --git a/extra/mariabackup/datasink.c b/extra/mariabackup/datasink.c new file mode 100644 index 00000000000..460e0e8ca19 --- /dev/null +++ b/extra/mariabackup/datasink.c @@ -0,0 +1,137 @@ +/****************************************************** +Copyright (c) 2011-2013 Percona LLC and/or its affiliates. + +Data sink interface. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#include <my_base.h> +#include "common.h" +#include "datasink.h" +#include "ds_compress.h" +#include "ds_archive.h" +#include "ds_xbstream.h" +#include "ds_local.h" +#include "ds_stdout.h" +#include "ds_tmpfile.h" +#include "ds_buffer.h" + +/************************************************************************ +Create a datasink of the specified type */ +ds_ctxt_t * +ds_create(const char *root, ds_type_t type) +{ + datasink_t *ds; + ds_ctxt_t *ctxt; + + switch (type) { + case DS_TYPE_STDOUT: + ds = &datasink_stdout; + break; + case DS_TYPE_LOCAL: + ds = &datasink_local; + break; + case DS_TYPE_ARCHIVE: +#ifdef HAVE_LIBARCHIVE + ds = &datasink_archive; +#else + msg("Error : mariabackup was built without libarchive support"); + exit(EXIT_FAILURE); +#endif + break; + case DS_TYPE_XBSTREAM: + ds = &datasink_xbstream; + break; + case DS_TYPE_COMPRESS: + ds = &datasink_compress; + break; + case DS_TYPE_ENCRYPT: + case DS_TYPE_DECRYPT: + msg("Error : mariabackup does not support encrypted backups."); + exit(EXIT_FAILURE); + break; + + case DS_TYPE_TMPFILE: + ds = &datasink_tmpfile; + break; + case DS_TYPE_BUFFER: + ds = &datasink_buffer; + break; + default: + msg("Unknown datasink type: %d\n", type); + xb_ad(0); + return NULL; + } + + ctxt = ds->init(root); + if (ctxt != NULL) { + ctxt->datasink = ds; + } else { + msg("Error: failed to initialize datasink.\n"); + exit(EXIT_FAILURE); + } + + return ctxt; +} + +/************************************************************************ +Open a datasink file */ +ds_file_t * +ds_open(ds_ctxt_t *ctxt, const char *path, MY_STAT *stat) +{ + ds_file_t *file; + + file = ctxt->datasink->open(ctxt, path, stat); + if (file != NULL) { + file->datasink = ctxt->datasink; + } + + return file; +} + +/************************************************************************ +Write to a datasink file. +@return 0 on success, 1 on error. */ +int +ds_write(ds_file_t *file, const void *buf, size_t len) +{ + return file->datasink->write(file, buf, len); +} + +/************************************************************************ +Close a datasink file. +@return 0 on success, 1, on error. */ +int +ds_close(ds_file_t *file) +{ + return file->datasink->close(file); +} + +/************************************************************************ +Destroy a datasink handle */ +void +ds_destroy(ds_ctxt_t *ctxt) +{ + ctxt->datasink->deinit(ctxt); +} + +/************************************************************************ +Set the destination pipe for a datasink (only makes sense for compress and +tmpfile). */ +void ds_set_pipe(ds_ctxt_t *ctxt, ds_ctxt_t *pipe_ctxt) +{ + ctxt->pipe_ctxt = pipe_ctxt; +} diff --git a/extra/mariabackup/datasink.h b/extra/mariabackup/datasink.h new file mode 100644 index 00000000000..8bf1321aad1 --- /dev/null +++ b/extra/mariabackup/datasink.h @@ -0,0 +1,100 @@ +/****************************************************** +Copyright (c) 2011-2013 Percona LLC and/or its affiliates. + +Data sink interface. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#ifndef XB_DATASINK_H +#define XB_DATASINK_H + +#include <my_global.h> +#include <my_dir.h> + +#ifdef __cplusplus +extern "C" { +#endif + +extern char *xtrabackup_tmpdir; +struct datasink_struct; +typedef struct datasink_struct datasink_t; + +typedef struct ds_ctxt { + datasink_t *datasink; + char *root; + void *ptr; + struct ds_ctxt *pipe_ctxt; +} ds_ctxt_t; + +typedef struct { + void *ptr; + char *path; + datasink_t *datasink; +} ds_file_t; + +struct datasink_struct { + ds_ctxt_t *(*init)(const char *root); + ds_file_t *(*open)(ds_ctxt_t *ctxt, const char *path, MY_STAT *stat); + int (*write)(ds_file_t *file, const void *buf, size_t len); + int (*close)(ds_file_t *file); + void (*deinit)(ds_ctxt_t *ctxt); +}; + +/* Supported datasink types */ +typedef enum { + DS_TYPE_STDOUT, + DS_TYPE_LOCAL, + DS_TYPE_ARCHIVE, + DS_TYPE_XBSTREAM, + DS_TYPE_COMPRESS, + DS_TYPE_ENCRYPT, + DS_TYPE_DECRYPT, + DS_TYPE_TMPFILE, + DS_TYPE_BUFFER +} ds_type_t; + +/************************************************************************ +Create a datasink of the specified type */ +ds_ctxt_t *ds_create(const char *root, ds_type_t type); + +/************************************************************************ +Open a datasink file */ +ds_file_t *ds_open(ds_ctxt_t *ctxt, const char *path, MY_STAT *stat); + +/************************************************************************ +Write to a datasink file. +@return 0 on success, 1 on error. */ +int ds_write(ds_file_t *file, const void *buf, size_t len); + +/************************************************************************ +Close a datasink file. +@return 0 on success, 1, on error. */ +int ds_close(ds_file_t *file); + +/************************************************************************ +Destroy a datasink handle */ +void ds_destroy(ds_ctxt_t *ctxt); + +/************************************************************************ +Set the destination pipe for a datasink (only makes sense for compress and +tmpfile). */ +void ds_set_pipe(ds_ctxt_t *ctxt, ds_ctxt_t *pipe_ctxt); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* XB_DATASINK_H */ diff --git a/extra/mariabackup/ds_archive.c b/extra/mariabackup/ds_archive.c new file mode 100644 index 00000000000..50afcce4bc7 --- /dev/null +++ b/extra/mariabackup/ds_archive.c @@ -0,0 +1,280 @@ +/****************************************************** +Copyright (c) 2013 Percona LLC and/or its affiliates. + +Streaming implementation for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#include <my_base.h> +#include <archive.h> +#include <archive_entry.h> +#include "common.h" +#include "datasink.h" + +#if ARCHIVE_VERSION_NUMBER < 3000000 +#define archive_write_add_filter_none(X) archive_write_set_compression_none(X) +#define archive_write_free(X) archive_write_finish(X) +#endif + +typedef struct { + struct archive *archive; + ds_file_t *dest_file; + pthread_mutex_t mutex; +} ds_archive_ctxt_t; + +typedef struct { + struct archive_entry *entry; + ds_archive_ctxt_t *archive_ctxt; +} ds_archive_file_t; + + +/*********************************************************************** +General archive interface */ + +static ds_ctxt_t *archive_init(const char *root); +static ds_file_t *archive_open(ds_ctxt_t *ctxt, const char *path, + MY_STAT *mystat); +static int archive_write(ds_file_t *file, const void *buf, size_t len); +static int archive_close(ds_file_t *file); +static void archive_deinit(ds_ctxt_t *ctxt); + +datasink_t datasink_archive = { + &archive_init, + &archive_open, + &archive_write, + &archive_close, + &archive_deinit +}; + +static +int +my_archive_open_callback(struct archive *a __attribute__((unused)), + void *data __attribute__((unused))) +{ + return ARCHIVE_OK; +} + +static +ssize_t +my_archive_write_callback(struct archive *a __attribute__((unused)), + void *data, const void *buffer, size_t length) +{ + ds_archive_ctxt_t *archive_ctxt; + + archive_ctxt = (ds_archive_ctxt_t *) data; + + xb_ad(archive_ctxt != NULL); + xb_ad(archive_ctxt->dest_file != NULL); + + if (!ds_write(archive_ctxt->dest_file, buffer, length)) { + return length; + } + return -1; +} + +static +int +my_archive_close_callback(struct archive *a __attribute__((unused)), + void *data __attribute__((unused))) +{ + return ARCHIVE_OK; +} + +static +ds_ctxt_t * +archive_init(const char *root __attribute__((unused))) +{ + ds_ctxt_t *ctxt; + ds_archive_ctxt_t *archive_ctxt; + struct archive *a; + + ctxt = my_malloc(sizeof(ds_ctxt_t) + sizeof(ds_archive_ctxt_t), + MYF(MY_FAE)); + archive_ctxt = (ds_archive_ctxt_t *)(ctxt + 1); + + if (pthread_mutex_init(&archive_ctxt->mutex, NULL)) { + msg("archive_init: pthread_mutex_init() failed.\n"); + goto err; + } + + a = archive_write_new(); + if (a == NULL) { + msg("archive_write_new() failed.\n"); + goto err; + } + + archive_ctxt->archive = a; + archive_ctxt->dest_file = NULL; + + if(archive_write_add_filter_none(a) != ARCHIVE_OK || + archive_write_set_format_pax_restricted(a) != ARCHIVE_OK || + /* disable internal buffering so we don't have to flush the + output in xtrabackup */ + archive_write_set_bytes_per_block(a, 0) != ARCHIVE_OK) { + msg("failed to set libarchive archive options: %s\n", + archive_error_string(a)); + archive_write_free(a); + goto err; + } + + if (archive_write_open(a, archive_ctxt, my_archive_open_callback, + my_archive_write_callback, + my_archive_close_callback) != ARCHIVE_OK) { + msg("cannot open output archive.\n"); + return NULL; + } + + ctxt->ptr = archive_ctxt; + + return ctxt; + +err: + my_free(ctxt); + return NULL; +} + +static +ds_file_t * +archive_open(ds_ctxt_t *ctxt, const char *path, MY_STAT *mystat) +{ + ds_archive_ctxt_t *archive_ctxt; + ds_ctxt_t *dest_ctxt; + ds_file_t *file; + ds_archive_file_t *archive_file; + + struct archive *a; + struct archive_entry *entry; + + xb_ad(ctxt->pipe_ctxt != NULL); + dest_ctxt = ctxt->pipe_ctxt; + + archive_ctxt = (ds_archive_ctxt_t *) ctxt->ptr; + + pthread_mutex_lock(&archive_ctxt->mutex); + if (archive_ctxt->dest_file == NULL) { + archive_ctxt->dest_file = ds_open(dest_ctxt, path, mystat); + if (archive_ctxt->dest_file == NULL) { + return NULL; + } + } + pthread_mutex_unlock(&archive_ctxt->mutex); + + file = (ds_file_t *) my_malloc(sizeof(ds_file_t) + + sizeof(ds_archive_file_t), + MYF(MY_FAE)); + + archive_file = (ds_archive_file_t *) (file + 1); + + a = archive_ctxt->archive; + + entry = archive_entry_new(); + if (entry == NULL) { + msg("archive_entry_new() failed.\n"); + goto err; + } + + archive_entry_set_size(entry, mystat->st_size); + archive_entry_set_mode(entry, 0660); + archive_entry_set_filetype(entry, AE_IFREG); + archive_entry_set_pathname(entry, path); + archive_entry_set_mtime(entry, mystat->st_mtime, 0); + + archive_file->entry = entry; + archive_file->archive_ctxt = archive_ctxt; + + if (archive_write_header(a, entry) != ARCHIVE_OK) { + msg("archive_write_header() failed.\n"); + archive_entry_free(entry); + goto err; + } + + file->ptr = archive_file; + file->path = archive_ctxt->dest_file->path; + + return file; + +err: + if (archive_ctxt->dest_file) { + ds_close(archive_ctxt->dest_file); + archive_ctxt->dest_file = NULL; + } + my_free(file); + + return NULL; +} + +static +int +archive_write(ds_file_t *file, const void *buf, size_t len) +{ + ds_archive_file_t *archive_file; + struct archive *a; + + archive_file = (ds_archive_file_t *) file->ptr; + + a = archive_file->archive_ctxt->archive; + + xb_ad(archive_file->archive_ctxt->dest_file != NULL); + if (archive_write_data(a, buf, len) < 0) { + msg("archive_write_data() failed: %s (errno = %d)\n", + archive_error_string(a), archive_errno(a)); + return 1; + } + + return 0; +} + +static +int +archive_close(ds_file_t *file) +{ + ds_archive_file_t *archive_file; + int rc = 0; + + archive_file = (ds_archive_file_t *)file->ptr; + + archive_entry_free(archive_file->entry); + + my_free(file); + + return rc; +} + +static +void +archive_deinit(ds_ctxt_t *ctxt) +{ + struct archive *a; + ds_archive_ctxt_t *archive_ctxt; + + archive_ctxt = (ds_archive_ctxt_t *) ctxt->ptr; + + a = archive_ctxt->archive; + + if (archive_write_close(a) != ARCHIVE_OK) { + msg("archive_write_close() failed.\n"); + } + archive_write_free(a); + + if (archive_ctxt->dest_file) { + ds_close(archive_ctxt->dest_file); + archive_ctxt->dest_file = NULL; + } + + pthread_mutex_destroy(&archive_ctxt->mutex); + + my_free(ctxt); +} diff --git a/extra/mariabackup/ds_archive.h b/extra/mariabackup/ds_archive.h new file mode 100644 index 00000000000..3f4e4463c58 --- /dev/null +++ b/extra/mariabackup/ds_archive.h @@ -0,0 +1,28 @@ +/****************************************************** +Copyright (c) 2013 Percona LLC and/or its affiliates. + +Streaming interface for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#ifndef DS_ARCHIVE_H +#define DS_ARCHIVE_H + +#include "datasink.h" + +extern datasink_t datasink_archive; + +#endif diff --git a/extra/mariabackup/ds_buffer.c b/extra/mariabackup/ds_buffer.c new file mode 100644 index 00000000000..4bb314c0f50 --- /dev/null +++ b/extra/mariabackup/ds_buffer.c @@ -0,0 +1,189 @@ +/****************************************************** +Copyright (c) 2012-2013 Percona LLC and/or its affiliates. + +buffer datasink for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +/* Does buffered output to a destination datasink set with ds_set_pipe(). +Writes to the destination datasink are guaranteed to not be smaller than a +specified buffer size (DS_DEFAULT_BUFFER_SIZE by default), with the only +exception for the last write for a file. */ + +#include <mysql_version.h> +#include <my_base.h> +#include "ds_buffer.h" +#include "common.h" +#include "datasink.h" + +#define DS_DEFAULT_BUFFER_SIZE (64 * 1024) + +typedef struct { + ds_file_t *dst_file; + char *buf; + size_t pos; + size_t size; +} ds_buffer_file_t; + +typedef struct { + size_t buffer_size; +} ds_buffer_ctxt_t; + +static ds_ctxt_t *buffer_init(const char *root); +static ds_file_t *buffer_open(ds_ctxt_t *ctxt, const char *path, + MY_STAT *mystat); +static int buffer_write(ds_file_t *file, const void *buf, size_t len); +static int buffer_close(ds_file_t *file); +static void buffer_deinit(ds_ctxt_t *ctxt); + +datasink_t datasink_buffer = { + &buffer_init, + &buffer_open, + &buffer_write, + &buffer_close, + &buffer_deinit +}; + +/* Change the default buffer size */ +void ds_buffer_set_size(ds_ctxt_t *ctxt, size_t size) +{ + ds_buffer_ctxt_t *buffer_ctxt = (ds_buffer_ctxt_t *) ctxt->ptr; + + buffer_ctxt->buffer_size = size; +} + +static ds_ctxt_t * +buffer_init(const char *root) +{ + ds_ctxt_t *ctxt; + ds_buffer_ctxt_t *buffer_ctxt; + + ctxt = my_malloc(sizeof(ds_ctxt_t) + sizeof(ds_buffer_ctxt_t), + MYF(MY_FAE)); + buffer_ctxt = (ds_buffer_ctxt_t *) (ctxt + 1); + buffer_ctxt->buffer_size = DS_DEFAULT_BUFFER_SIZE; + + ctxt->ptr = buffer_ctxt; + ctxt->root = my_strdup(root, MYF(MY_FAE)); + + return ctxt; +} + +static ds_file_t * +buffer_open(ds_ctxt_t *ctxt, const char *path, MY_STAT *mystat) +{ + ds_buffer_ctxt_t *buffer_ctxt; + ds_ctxt_t *pipe_ctxt; + ds_file_t *dst_file; + ds_file_t *file; + ds_buffer_file_t *buffer_file; + + pipe_ctxt = ctxt->pipe_ctxt; + xb_a(pipe_ctxt != NULL); + + dst_file = ds_open(pipe_ctxt, path, mystat); + if (dst_file == NULL) { + exit(EXIT_FAILURE); + } + + buffer_ctxt = (ds_buffer_ctxt_t *) ctxt->ptr; + + file = (ds_file_t *) my_malloc(sizeof(ds_file_t) + + sizeof(ds_buffer_file_t) + + buffer_ctxt->buffer_size, + MYF(MY_FAE)); + + buffer_file = (ds_buffer_file_t *) (file + 1); + buffer_file->dst_file = dst_file; + buffer_file->buf = (char *) (buffer_file + 1); + buffer_file->size = buffer_ctxt->buffer_size; + buffer_file->pos = 0; + + file->path = dst_file->path; + file->ptr = buffer_file; + + return file; +} + +static int +buffer_write(ds_file_t *file, const void *buf, size_t len) +{ + ds_buffer_file_t *buffer_file; + + buffer_file = (ds_buffer_file_t *) file->ptr; + + while (len > 0) { + if (buffer_file->pos + len > buffer_file->size) { + if (buffer_file->pos > 0) { + size_t bytes; + + bytes = buffer_file->size - buffer_file->pos; + memcpy(buffer_file->buf + buffer_file->pos, buf, + bytes); + + if (ds_write(buffer_file->dst_file, + buffer_file->buf, + buffer_file->size)) { + return 1; + } + + buffer_file->pos = 0; + + buf = (const char *) buf + bytes; + len -= bytes; + } else { + /* We don't have any buffered bytes, just write + the entire source buffer */ + if (ds_write(buffer_file->dst_file, buf, len)) { + return 1; + } + break; + } + } else { + memcpy(buffer_file->buf + buffer_file->pos, buf, len); + buffer_file->pos += len; + break; + } + } + + return 0; +} + +static int +buffer_close(ds_file_t *file) +{ + ds_buffer_file_t *buffer_file; + int ret; + + buffer_file = (ds_buffer_file_t *) file->ptr; + if (buffer_file->pos > 0) { + ds_write(buffer_file->dst_file, buffer_file->buf, + buffer_file->pos); + } + + ret = ds_close(buffer_file->dst_file); + + my_free(file); + + return ret; +} + +static void +buffer_deinit(ds_ctxt_t *ctxt) +{ + my_free(ctxt->root); + my_free(ctxt); +} diff --git a/extra/mariabackup/ds_buffer.h b/extra/mariabackup/ds_buffer.h new file mode 100644 index 00000000000..f8d2d63267d --- /dev/null +++ b/extra/mariabackup/ds_buffer.h @@ -0,0 +1,39 @@ +/****************************************************** +Copyright (c) 2012-2013 Percona LLC and/or its affiliates. + +buffer datasink for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#ifndef DS_BUFFER_H +#define DS_BUFFER_H + +#include "datasink.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern datasink_t datasink_buffer; + +/* Change the default buffer size */ +void ds_buffer_set_size(ds_ctxt_t *ctxt, size_t size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/extra/mariabackup/ds_compress.c b/extra/mariabackup/ds_compress.c new file mode 100644 index 00000000000..15801c8abd4 --- /dev/null +++ b/extra/mariabackup/ds_compress.c @@ -0,0 +1,462 @@ +/****************************************************** +Copyright (c) 2011-2013 Percona LLC and/or its affiliates. + +Compressing datasink implementation for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#include <mysql_version.h> +#include <my_base.h> +#include <quicklz.h> +#include <zlib.h> +#include "common.h" +#include "datasink.h" + +#define COMPRESS_CHUNK_SIZE ((size_t) (xtrabackup_compress_chunk_size)) +#define MY_QLZ_COMPRESS_OVERHEAD 400 + +typedef struct { + pthread_t id; + uint num; + pthread_mutex_t ctrl_mutex; + pthread_cond_t ctrl_cond; + pthread_mutex_t data_mutex; + pthread_cond_t data_cond; + my_bool started; + my_bool data_avail; + my_bool cancelled; + const char *from; + size_t from_len; + char *to; + size_t to_len; + qlz_state_compress state; + ulong adler; +} comp_thread_ctxt_t; + +typedef struct { + comp_thread_ctxt_t *threads; + uint nthreads; +} ds_compress_ctxt_t; + +typedef struct { + ds_file_t *dest_file; + ds_compress_ctxt_t *comp_ctxt; + size_t bytes_processed; +} ds_compress_file_t; + +/* Compression options */ +extern char *xtrabackup_compress_alg; +extern uint xtrabackup_compress_threads; +extern ulonglong xtrabackup_compress_chunk_size; + +static ds_ctxt_t *compress_init(const char *root); +static ds_file_t *compress_open(ds_ctxt_t *ctxt, const char *path, + MY_STAT *mystat); +static int compress_write(ds_file_t *file, const void *buf, size_t len); +static int compress_close(ds_file_t *file); +static void compress_deinit(ds_ctxt_t *ctxt); + +datasink_t datasink_compress = { + &compress_init, + &compress_open, + &compress_write, + &compress_close, + &compress_deinit +}; + +static inline int write_uint32_le(ds_file_t *file, ulong n); +static inline int write_uint64_le(ds_file_t *file, ulonglong n); + +static comp_thread_ctxt_t *create_worker_threads(uint n); +static void destroy_worker_threads(comp_thread_ctxt_t *threads, uint n); +static void *compress_worker_thread_func(void *arg); + +static +ds_ctxt_t * +compress_init(const char *root) +{ + ds_ctxt_t *ctxt; + ds_compress_ctxt_t *compress_ctxt; + comp_thread_ctxt_t *threads; + + /* Create and initialize the worker threads */ + threads = create_worker_threads(xtrabackup_compress_threads); + if (threads == NULL) { + msg("compress: failed to create worker threads.\n"); + return NULL; + } + + ctxt = (ds_ctxt_t *) my_malloc(sizeof(ds_ctxt_t) + + sizeof(ds_compress_ctxt_t), + MYF(MY_FAE)); + + compress_ctxt = (ds_compress_ctxt_t *) (ctxt + 1); + compress_ctxt->threads = threads; + compress_ctxt->nthreads = xtrabackup_compress_threads; + + ctxt->ptr = compress_ctxt; + ctxt->root = my_strdup(root, MYF(MY_FAE)); + + return ctxt; +} + +static +ds_file_t * +compress_open(ds_ctxt_t *ctxt, const char *path, MY_STAT *mystat) +{ + ds_compress_ctxt_t *comp_ctxt; + ds_ctxt_t *dest_ctxt; + ds_file_t *dest_file; + char new_name[FN_REFLEN]; + size_t name_len; + ds_file_t *file; + ds_compress_file_t *comp_file; + + xb_ad(ctxt->pipe_ctxt != NULL); + dest_ctxt = ctxt->pipe_ctxt; + + comp_ctxt = (ds_compress_ctxt_t *) ctxt->ptr; + + /* Append the .qp extension to the filename */ + fn_format(new_name, path, "", ".qp", MYF(MY_APPEND_EXT)); + + dest_file = ds_open(dest_ctxt, new_name, mystat); + if (dest_file == NULL) { + return NULL; + } + + /* Write the qpress archive header */ + if (ds_write(dest_file, "qpress10", 8) || + write_uint64_le(dest_file, COMPRESS_CHUNK_SIZE)) { + goto err; + } + + /* We are going to create a one-file "flat" (i.e. with no + subdirectories) archive. So strip the directory part from the path and + remove the '.qp' suffix. */ + fn_format(new_name, path, "", "", MYF(MY_REPLACE_DIR)); + + /* Write the qpress file header */ + name_len = strlen(new_name); + if (ds_write(dest_file, "F", 1) || + write_uint32_le(dest_file, (uint)name_len) || + /* we want to write the terminating \0 as well */ + ds_write(dest_file, new_name, name_len + 1)) { + goto err; + } + + file = (ds_file_t *) my_malloc(sizeof(ds_file_t) + + sizeof(ds_compress_file_t), + MYF(MY_FAE)); + comp_file = (ds_compress_file_t *) (file + 1); + comp_file->dest_file = dest_file; + comp_file->comp_ctxt = comp_ctxt; + comp_file->bytes_processed = 0; + + file->ptr = comp_file; + file->path = dest_file->path; + + return file; + +err: + ds_close(dest_file); + return NULL; +} + +static +int +compress_write(ds_file_t *file, const void *buf, size_t len) +{ + ds_compress_file_t *comp_file; + ds_compress_ctxt_t *comp_ctxt; + comp_thread_ctxt_t *threads; + comp_thread_ctxt_t *thd; + uint nthreads; + uint i; + const char *ptr; + ds_file_t *dest_file; + + comp_file = (ds_compress_file_t *) file->ptr; + comp_ctxt = comp_file->comp_ctxt; + dest_file = comp_file->dest_file; + + threads = comp_ctxt->threads; + nthreads = comp_ctxt->nthreads; + + ptr = (const char *) buf; + while (len > 0) { + uint max_thread; + + /* Send data to worker threads for compression */ + for (i = 0; i < nthreads; i++) { + size_t chunk_len; + + thd = threads + i; + + pthread_mutex_lock(&thd->ctrl_mutex); + + chunk_len = (len > COMPRESS_CHUNK_SIZE) ? + COMPRESS_CHUNK_SIZE : len; + thd->from = ptr; + thd->from_len = chunk_len; + + pthread_mutex_lock(&thd->data_mutex); + thd->data_avail = TRUE; + pthread_cond_signal(&thd->data_cond); + pthread_mutex_unlock(&thd->data_mutex); + + len -= chunk_len; + if (len == 0) { + break; + } + ptr += chunk_len; + } + + max_thread = (i < nthreads) ? i : nthreads - 1; + + /* Reap and stream the compressed data */ + for (i = 0; i <= max_thread; i++) { + thd = threads + i; + + pthread_mutex_lock(&thd->data_mutex); + while (thd->data_avail == TRUE) { + pthread_cond_wait(&thd->data_cond, + &thd->data_mutex); + } + + xb_a(threads[i].to_len > 0); + + if (ds_write(dest_file, "NEWBNEWB", 8) || + write_uint64_le(dest_file, + comp_file->bytes_processed)) { + msg("compress: write to the destination stream " + "failed.\n"); + return 1; + } + + comp_file->bytes_processed += threads[i].from_len; + + if (write_uint32_le(dest_file, threads[i].adler) || + ds_write(dest_file, threads[i].to, + threads[i].to_len)) { + msg("compress: write to the destination stream " + "failed.\n"); + return 1; + } + + pthread_mutex_unlock(&threads[i].data_mutex); + pthread_mutex_unlock(&threads[i].ctrl_mutex); + } + } + + return 0; +} + +static +int +compress_close(ds_file_t *file) +{ + ds_compress_file_t *comp_file; + ds_file_t *dest_file; + int rc; + + comp_file = (ds_compress_file_t *) file->ptr; + dest_file = comp_file->dest_file; + + /* Write the qpress file trailer */ + ds_write(dest_file, "ENDSENDS", 8); + + /* Supposedly the number of written bytes should be written as a + "recovery information" in the file trailer, but in reality qpress + always writes 8 zeros here. Let's do the same */ + + write_uint64_le(dest_file, 0); + + rc = ds_close(dest_file); + + my_free(file); + + return rc; +} + +static +void +compress_deinit(ds_ctxt_t *ctxt) +{ + ds_compress_ctxt_t *comp_ctxt; + + xb_ad(ctxt->pipe_ctxt != NULL); + + comp_ctxt = (ds_compress_ctxt_t *) ctxt->ptr;; + + destroy_worker_threads(comp_ctxt->threads, comp_ctxt->nthreads); + + my_free(ctxt->root); + my_free(ctxt); +} + +static inline +int +write_uint32_le(ds_file_t *file, ulong n) +{ + char tmp[4]; + + int4store(tmp, n); + return ds_write(file, tmp, sizeof(tmp)); +} + +static inline +int +write_uint64_le(ds_file_t *file, ulonglong n) +{ + char tmp[8]; + + int8store(tmp, n); + return ds_write(file, tmp, sizeof(tmp)); +} + +static +comp_thread_ctxt_t * +create_worker_threads(uint n) +{ + comp_thread_ctxt_t *threads; + uint i; + + threads = (comp_thread_ctxt_t *) + my_malloc(sizeof(comp_thread_ctxt_t) * n, MYF(MY_FAE)); + + for (i = 0; i < n; i++) { + comp_thread_ctxt_t *thd = threads + i; + + thd->num = i + 1; + thd->started = FALSE; + thd->cancelled = FALSE; + thd->data_avail = FALSE; + + thd->to = (char *) my_malloc(COMPRESS_CHUNK_SIZE + + MY_QLZ_COMPRESS_OVERHEAD, + MYF(MY_FAE)); + + /* Initialize the control mutex and condition var */ + if (pthread_mutex_init(&thd->ctrl_mutex, NULL) || + pthread_cond_init(&thd->ctrl_cond, NULL)) { + goto err; + } + + /* Initialize and data mutex and condition var */ + if (pthread_mutex_init(&thd->data_mutex, NULL) || + pthread_cond_init(&thd->data_cond, NULL)) { + goto err; + } + + pthread_mutex_lock(&thd->ctrl_mutex); + + if (pthread_create(&thd->id, NULL, compress_worker_thread_func, + thd)) { + msg("compress: pthread_create() failed: " + "errno = %d\n", errno); + goto err; + } + } + + /* Wait for the threads to start */ + for (i = 0; i < n; i++) { + comp_thread_ctxt_t *thd = threads + i; + + while (thd->started == FALSE) + pthread_cond_wait(&thd->ctrl_cond, &thd->ctrl_mutex); + pthread_mutex_unlock(&thd->ctrl_mutex); + } + + return threads; + +err: + return NULL; +} + +static +void +destroy_worker_threads(comp_thread_ctxt_t *threads, uint n) +{ + uint i; + + for (i = 0; i < n; i++) { + comp_thread_ctxt_t *thd = threads + i; + + pthread_mutex_lock(&thd->data_mutex); + threads[i].cancelled = TRUE; + pthread_cond_signal(&thd->data_cond); + pthread_mutex_unlock(&thd->data_mutex); + + pthread_join(thd->id, NULL); + + pthread_cond_destroy(&thd->data_cond); + pthread_mutex_destroy(&thd->data_mutex); + pthread_cond_destroy(&thd->ctrl_cond); + pthread_mutex_destroy(&thd->ctrl_mutex); + + my_free(thd->to); + } + + my_free(threads); +} + +static +void * +compress_worker_thread_func(void *arg) +{ + comp_thread_ctxt_t *thd = (comp_thread_ctxt_t *) arg; + + pthread_mutex_lock(&thd->ctrl_mutex); + + pthread_mutex_lock(&thd->data_mutex); + + thd->started = TRUE; + pthread_cond_signal(&thd->ctrl_cond); + + pthread_mutex_unlock(&thd->ctrl_mutex); + + while (1) { + thd->data_avail = FALSE; + pthread_cond_signal(&thd->data_cond); + + while (!thd->data_avail && !thd->cancelled) { + pthread_cond_wait(&thd->data_cond, &thd->data_mutex); + } + + if (thd->cancelled) + break; + + thd->to_len = qlz_compress(thd->from, thd->to, thd->from_len, + &thd->state); + + /* qpress uses 0x00010000 as the initial value, but its own + Adler-32 implementation treats the value differently: + 1. higher order bits are the sum of all bytes in the sequence + 2. lower order bits are the sum of resulting values at every + step. + So it's the other way around as compared to zlib's adler32(). + That's why 0x00000001 is being passed here to be compatible + with qpress implementation. */ + + thd->adler = adler32(0x00000001, (uchar *) thd->to, + (uInt)thd->to_len); + } + + pthread_mutex_unlock(&thd->data_mutex); + + return NULL; +} diff --git a/extra/mariabackup/ds_compress.h b/extra/mariabackup/ds_compress.h new file mode 100644 index 00000000000..8498c965e13 --- /dev/null +++ b/extra/mariabackup/ds_compress.h @@ -0,0 +1,28 @@ +/****************************************************** +Copyright (c) 2011-2013 Percona LLC and/or its affiliates. + +Compression interface for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#ifndef DS_COMPRESS_H +#define DS_COMPRESS_H + +#include "datasink.h" + +extern datasink_t datasink_compress; + +#endif diff --git a/extra/mariabackup/ds_decrypt.c b/extra/mariabackup/ds_decrypt.c new file mode 100644 index 00000000000..e897ca101e5 --- /dev/null +++ b/extra/mariabackup/ds_decrypt.c @@ -0,0 +1,665 @@ +/****************************************************** +Copyright (c) 2017 Percona LLC and/or its affiliates. + +Encryption datasink implementation for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + + +#include <my_base.h> +#include "common.h" +#include "datasink.h" +#include "xbcrypt.h" +#include "xbcrypt_common.h" +#include "crc_glue.h" + +typedef struct { + pthread_t id; + uint num; + pthread_mutex_t ctrl_mutex; + pthread_cond_t ctrl_cond; + pthread_mutex_t data_mutex; + pthread_cond_t data_cond; + my_bool started; + my_bool data_avail; + my_bool cancelled; + my_bool failed; + const uchar *from; + size_t from_len; + uchar *to; + size_t to_len; + size_t to_size; + const uchar *iv; + size_t iv_len; + unsigned long long offset; + my_bool hash_appended; + gcry_cipher_hd_t cipher_handle; + xb_rcrypt_result_t parse_result; +} crypt_thread_ctxt_t; + +typedef struct { + crypt_thread_ctxt_t *threads; + uint nthreads; + int encrypt_algo; + size_t chunk_size; + char *encrypt_key; + char *encrypt_key_file; +} ds_decrypt_ctxt_t; + +typedef struct { + ds_decrypt_ctxt_t *crypt_ctxt; + size_t bytes_processed; + ds_file_t *dest_file; + uchar *buf; + size_t buf_len; + size_t buf_size; +} ds_decrypt_file_t; + +int ds_decrypt_encrypt_threads = 1; + +static ds_ctxt_t *decrypt_init(const char *root); +static ds_file_t *decrypt_open(ds_ctxt_t *ctxt, const char *path, + MY_STAT *mystat); +static int decrypt_write(ds_file_t *file, const void *buf, size_t len); +static int decrypt_close(ds_file_t *file); +static void decrypt_deinit(ds_ctxt_t *ctxt); + +datasink_t datasink_decrypt = { + &decrypt_init, + &decrypt_open, + &decrypt_write, + &decrypt_close, + &decrypt_deinit +}; + +static crypt_thread_ctxt_t *create_worker_threads(uint n); +static void destroy_worker_threads(crypt_thread_ctxt_t *threads, uint n); +static void *decrypt_worker_thread_func(void *arg); + +static +ds_ctxt_t * +decrypt_init(const char *root) +{ + ds_ctxt_t *ctxt; + ds_decrypt_ctxt_t *decrypt_ctxt; + crypt_thread_ctxt_t *threads; + + if (xb_crypt_init(NULL)) { + return NULL; + } + + /* Create and initialize the worker threads */ + threads = create_worker_threads(ds_decrypt_encrypt_threads); + if (threads == NULL) { + msg("decrypt: failed to create worker threads.\n"); + return NULL; + } + + ctxt = (ds_ctxt_t *) my_malloc(sizeof(ds_ctxt_t) + + sizeof(ds_decrypt_ctxt_t), + MYF(MY_FAE)); + + decrypt_ctxt = (ds_decrypt_ctxt_t *) (ctxt + 1); + decrypt_ctxt->threads = threads; + decrypt_ctxt->nthreads = ds_decrypt_encrypt_threads; + + ctxt->ptr = decrypt_ctxt; + ctxt->root = my_strdup(root, MYF(MY_FAE)); + + return ctxt; +} + +static +ds_file_t * +decrypt_open(ds_ctxt_t *ctxt, const char *path, MY_STAT *mystat) +{ + ds_ctxt_t *dest_ctxt; + + ds_decrypt_ctxt_t *crypt_ctxt; + ds_decrypt_file_t *crypt_file; + + char new_name[FN_REFLEN]; + ds_file_t *file; + + xb_ad(ctxt->pipe_ctxt != NULL); + dest_ctxt = ctxt->pipe_ctxt; + + crypt_ctxt = (ds_decrypt_ctxt_t *) ctxt->ptr; + + + file = (ds_file_t *) my_malloc(sizeof(ds_file_t) + + sizeof(ds_decrypt_file_t), + MYF(MY_FAE|MY_ZEROFILL)); + + crypt_file = (ds_decrypt_file_t *) (file + 1); + + /* Remove the .xbcrypt extension from the filename */ + strncpy(new_name, path, FN_REFLEN); + new_name[strlen(new_name) - 8] = 0; + crypt_file->dest_file = ds_open(dest_ctxt, new_name, mystat); + if (crypt_file->dest_file == NULL) { + msg("decrypt: ds_open(\"%s\") failed.\n", new_name); + goto err; + } + + crypt_file->crypt_ctxt = crypt_ctxt; + crypt_file->buf = NULL; + crypt_file->buf_size = 0; + crypt_file->buf_len = 0; + + file->ptr = crypt_file; + file->path = crypt_file->dest_file->path; + + return file; + +err: + if (crypt_file->dest_file) { + ds_close(crypt_file->dest_file); + } + my_free(file); + return NULL; +} + +#define CHECK_BUF_SIZE(ptr, size, buf, len) \ + if (ptr + size - buf > (ssize_t) len) { \ + result = XB_CRYPT_READ_INCOMPLETE; \ + goto exit; \ + } + +static +xb_rcrypt_result_t +parse_xbcrypt_chunk(crypt_thread_ctxt_t *thd, const uchar *buf, size_t len, + size_t *bytes_processed) +{ + const uchar *ptr; + uint version; + ulong checksum, checksum_exp; + ulonglong tmp; + xb_rcrypt_result_t result = XB_CRYPT_READ_CHUNK; + + *bytes_processed = 0; + ptr = buf; + + CHECK_BUF_SIZE(ptr, XB_CRYPT_CHUNK_MAGIC_SIZE, buf, len); + if (memcmp(ptr, XB_CRYPT_CHUNK_MAGIC3, + XB_CRYPT_CHUNK_MAGIC_SIZE) == 0) { + version = 3; + } else if (memcmp(ptr, XB_CRYPT_CHUNK_MAGIC2, + XB_CRYPT_CHUNK_MAGIC_SIZE) == 0) { + version = 2; + } else if (memcmp(ptr, XB_CRYPT_CHUNK_MAGIC1, + XB_CRYPT_CHUNK_MAGIC_SIZE) == 0) { + version = 1; + } else { + msg("%s:%s: wrong chunk magic at offset 0x%llx.\n", + my_progname, __FUNCTION__, thd->offset); + result = XB_CRYPT_READ_ERROR; + goto exit; + } + + ptr += XB_CRYPT_CHUNK_MAGIC_SIZE; + thd->offset += XB_CRYPT_CHUNK_MAGIC_SIZE; + + CHECK_BUF_SIZE(ptr, 8, buf, len); + tmp = uint8korr(ptr); /* reserved */ + ptr += 8; + thd->offset += 8; + + CHECK_BUF_SIZE(ptr, 8, buf, len); + tmp = uint8korr(ptr); /* original size */ + ptr += 8; + if (tmp > INT_MAX) { + msg("%s:%s: invalid original size at offset 0x%llx.\n", + my_progname, __FUNCTION__, thd->offset); + result = XB_CRYPT_READ_ERROR; + goto exit; + } + thd->offset += 8; + thd->to_len = (size_t)tmp; + + if (thd->to_size < thd->to_len + XB_CRYPT_HASH_LEN) { + thd->to = (uchar *) my_realloc( + thd->to, + thd->to_len + XB_CRYPT_HASH_LEN, + MYF(MY_FAE | MY_ALLOW_ZERO_PTR)); + thd->to_size = thd->to_len; + } + + CHECK_BUF_SIZE(ptr, 8, buf, len); + tmp = uint8korr(ptr); /* encrypted size */ + ptr += 8; + if (tmp > INT_MAX) { + msg("%s:%s: invalid encrypted size at offset 0x%llx.\n", + my_progname, __FUNCTION__, thd->offset); + result = XB_CRYPT_READ_ERROR; + goto exit; + } + thd->offset += 8; + thd->from_len = (size_t)tmp; + + xb_a(thd->from_len <= thd->to_len + XB_CRYPT_HASH_LEN); + + CHECK_BUF_SIZE(ptr, 4, buf, len); + checksum_exp = uint4korr(ptr); /* checksum */ + ptr += 4; + thd->offset += 4; + + /* iv size */ + if (version == 1) { + thd->iv_len = 0; + thd->iv = NULL; + } else { + CHECK_BUF_SIZE(ptr, 8, buf, len); + + tmp = uint8korr(ptr); + if (tmp > INT_MAX) { + msg("%s:%s: invalid iv size at offset 0x%llx.\n", + my_progname, __FUNCTION__, thd->offset); + result = XB_CRYPT_READ_ERROR; + goto exit; + } + ptr += 8; + thd->offset += 8; + thd->iv_len = (size_t)tmp; + } + + if (thd->iv_len > 0) { + CHECK_BUF_SIZE(ptr, thd->iv_len, buf, len); + thd->iv = ptr; + ptr += thd->iv_len; + } + + /* for version euqals 2 we need to read in the iv data but do not init + CTR with it */ + if (version == 2) { + thd->iv_len = 0; + thd->iv = 0; + } + + if (thd->from_len > 0) { + CHECK_BUF_SIZE(ptr, thd->from_len, buf, len); + thd->from = ptr; + ptr += thd->from_len; + } + + xb_ad(thd->from_len <= thd->to_len); + + checksum = crc32_iso3309(0, thd->from, thd->from_len); + if (checksum != checksum_exp) { + msg("%s:%s invalid checksum at offset 0x%llx, " + "expected 0x%lx, actual 0x%lx.\n", my_progname, + __FUNCTION__, thd->offset, checksum_exp, checksum); + result = XB_CRYPT_READ_ERROR; + goto exit; + } + + thd->offset += thd->from_len; + + thd->hash_appended = version > 2; + +exit: + + *bytes_processed = (size_t) (ptr - buf); + + return result; +} + +static +int +decrypt_write(ds_file_t *file, const void *buf, size_t len) +{ + ds_decrypt_file_t *crypt_file; + ds_decrypt_ctxt_t *crypt_ctxt; + crypt_thread_ctxt_t *threads; + crypt_thread_ctxt_t *thd; + uint nthreads; + uint i; + size_t bytes_processed; + xb_rcrypt_result_t parse_result = XB_CRYPT_READ_CHUNK; + my_bool err = FALSE; + + crypt_file = (ds_decrypt_file_t *) file->ptr; + crypt_ctxt = crypt_file->crypt_ctxt; + + threads = crypt_ctxt->threads; + nthreads = crypt_ctxt->nthreads; + + if (crypt_file->buf_len > 0) { + thd = threads; + + pthread_mutex_lock(&thd->ctrl_mutex); + + do { + if (parse_result == XB_CRYPT_READ_INCOMPLETE) { + crypt_file->buf_size = crypt_file->buf_size * 2; + crypt_file->buf = (uchar *) my_realloc( + crypt_file->buf, + crypt_file->buf_size, + MYF(MY_FAE|MY_ALLOW_ZERO_PTR)); + } + + memcpy(crypt_file->buf + crypt_file->buf_len, + buf, MY_MIN(crypt_file->buf_size - + crypt_file->buf_len, len)); + + parse_result = parse_xbcrypt_chunk( + thd, crypt_file->buf, + crypt_file->buf_size, &bytes_processed); + + if (parse_result == XB_CRYPT_READ_ERROR) { + pthread_mutex_unlock(&thd->ctrl_mutex); + return 1; + } + + } while (parse_result == XB_CRYPT_READ_INCOMPLETE && + crypt_file->buf_size < len); + + if (parse_result != XB_CRYPT_READ_CHUNK) { + msg("decrypt: incomplete data.\n"); + pthread_mutex_unlock(&thd->ctrl_mutex); + return 1; + } + + pthread_mutex_lock(&thd->data_mutex); + thd->data_avail = TRUE; + pthread_cond_signal(&thd->data_cond); + pthread_mutex_unlock(&thd->data_mutex); + + len -= bytes_processed - crypt_file->buf_len; + buf += bytes_processed - crypt_file->buf_len; + + /* reap */ + + pthread_mutex_lock(&thd->data_mutex); + while (thd->data_avail == TRUE) { + pthread_cond_wait(&thd->data_cond, + &thd->data_mutex); + } + + if (thd->failed) { + msg("decrypt: failed to decrypt chunk.\n"); + err = TRUE; + } + + xb_a(thd->to_len > 0); + + if (!err && + ds_write(crypt_file->dest_file, thd->to, thd->to_len)) { + msg("decrypt: write to destination failed.\n"); + err = TRUE; + } + + crypt_file->bytes_processed += thd->from_len; + + pthread_mutex_unlock(&thd->data_mutex); + pthread_mutex_unlock(&thd->ctrl_mutex); + + crypt_file->buf_len = 0; + + if (err) { + return 1; + } + } + + while (parse_result == XB_CRYPT_READ_CHUNK && len > 0) { + uint max_thread; + + for (i = 0; i < nthreads; i++) { + thd = threads + i; + + pthread_mutex_lock(&thd->ctrl_mutex); + + parse_result = parse_xbcrypt_chunk( + thd, buf, len, &bytes_processed); + + if (parse_result == XB_CRYPT_READ_ERROR) { + pthread_mutex_unlock(&thd->ctrl_mutex); + err = TRUE; + break; + } + + thd->parse_result = parse_result; + + if (parse_result != XB_CRYPT_READ_CHUNK) { + pthread_mutex_unlock(&thd->ctrl_mutex); + break; + } + + pthread_mutex_lock(&thd->data_mutex); + thd->data_avail = TRUE; + pthread_cond_signal(&thd->data_cond); + pthread_mutex_unlock(&thd->data_mutex); + + len -= bytes_processed; + buf += bytes_processed; + } + + max_thread = (i < nthreads) ? i : nthreads - 1; + + /* Reap and write decrypted data */ + for (i = 0; i <= max_thread; i++) { + thd = threads + i; + + if (thd->parse_result != XB_CRYPT_READ_CHUNK) { + break; + } + + pthread_mutex_lock(&thd->data_mutex); + while (thd->data_avail == TRUE) { + pthread_cond_wait(&thd->data_cond, + &thd->data_mutex); + } + + if (thd->failed) { + msg("decrypt: failed to decrypt chunk.\n"); + err = TRUE; + } + + xb_a(thd->to_len > 0); + + if (!err && ds_write(crypt_file->dest_file, thd->to, + thd->to_len)) { + msg("decrypt: write to destination failed.\n"); + err = TRUE; + } + + crypt_file->bytes_processed += thd->from_len; + + pthread_mutex_unlock(&thd->data_mutex); + pthread_mutex_unlock(&thd->ctrl_mutex); + } + + if (err) { + return 1; + } + } + + if (parse_result == XB_CRYPT_READ_INCOMPLETE && len > 0) { + crypt_file->buf_len = len; + if (crypt_file->buf_size < len) { + crypt_file->buf = (uchar *) my_realloc( + crypt_file->buf, + crypt_file->buf_len, + MYF(MY_FAE | MY_ALLOW_ZERO_PTR)); + crypt_file->buf_size = len; + } + memcpy(crypt_file->buf, buf, len); + } + + return 0; +} + +static +int +decrypt_close(ds_file_t *file) +{ + ds_decrypt_file_t *crypt_file; + ds_file_t *dest_file; + int rc = 0; + + crypt_file = (ds_decrypt_file_t *) file->ptr; + dest_file = crypt_file->dest_file; + + if (ds_close(dest_file)) { + rc = 1; + } + + my_free(crypt_file->buf); + my_free(file); + + return rc; +} + +static +void +decrypt_deinit(ds_ctxt_t *ctxt) +{ + ds_decrypt_ctxt_t *crypt_ctxt; + + xb_ad(ctxt->pipe_ctxt != NULL); + + crypt_ctxt = (ds_decrypt_ctxt_t *) ctxt->ptr; + + destroy_worker_threads(crypt_ctxt->threads, crypt_ctxt->nthreads); + + my_free(ctxt->root); + my_free(ctxt); +} + +static +crypt_thread_ctxt_t * +create_worker_threads(uint n) +{ + crypt_thread_ctxt_t *threads; + uint i; + + threads = (crypt_thread_ctxt_t *) + my_malloc(sizeof(crypt_thread_ctxt_t) * n, + MYF(MY_FAE | MY_ZEROFILL)); + + for (i = 0; i < n; i++) { + crypt_thread_ctxt_t *thd = threads + i; + + thd->num = i + 1; + + /* Initialize the control mutex and condition var */ + if (pthread_mutex_init(&thd->ctrl_mutex, NULL) || + pthread_cond_init(&thd->ctrl_cond, NULL)) { + goto err; + } + + /* Initialize and data mutex and condition var */ + if (pthread_mutex_init(&thd->data_mutex, NULL) || + pthread_cond_init(&thd->data_cond, NULL)) { + goto err; + } + + xb_crypt_cipher_open(&thd->cipher_handle); + + pthread_mutex_lock(&thd->ctrl_mutex); + + if (pthread_create(&thd->id, NULL, decrypt_worker_thread_func, + thd)) { + msg("decrypt: pthread_create() failed: " + "errno = %d\n", errno); + goto err; + } + } + + /* Wait for the threads to start */ + for (i = 0; i < n; i++) { + crypt_thread_ctxt_t *thd = threads + i; + + while (thd->started == FALSE) + pthread_cond_wait(&thd->ctrl_cond, &thd->ctrl_mutex); + pthread_mutex_unlock(&thd->ctrl_mutex); + } + + return threads; + +err: + return NULL; +} + +static +void +destroy_worker_threads(crypt_thread_ctxt_t *threads, uint n) +{ + uint i; + + for (i = 0; i < n; i++) { + crypt_thread_ctxt_t *thd = threads + i; + + pthread_mutex_lock(&thd->data_mutex); + threads[i].cancelled = TRUE; + pthread_cond_signal(&thd->data_cond); + pthread_mutex_unlock(&thd->data_mutex); + + pthread_join(thd->id, NULL); + + pthread_cond_destroy(&thd->data_cond); + pthread_mutex_destroy(&thd->data_mutex); + pthread_cond_destroy(&thd->ctrl_cond); + pthread_mutex_destroy(&thd->ctrl_mutex); + + xb_crypt_cipher_close(thd->cipher_handle); + + my_free(thd->to); + } + + my_free(threads); +} + +static +void * +decrypt_worker_thread_func(void *arg) +{ + crypt_thread_ctxt_t *thd = (crypt_thread_ctxt_t *) arg; + + pthread_mutex_lock(&thd->ctrl_mutex); + + pthread_mutex_lock(&thd->data_mutex); + + thd->started = TRUE; + pthread_cond_signal(&thd->ctrl_cond); + + pthread_mutex_unlock(&thd->ctrl_mutex); + + while (1) { + thd->data_avail = FALSE; + pthread_cond_signal(&thd->data_cond); + + while (!thd->data_avail && !thd->cancelled) { + pthread_cond_wait(&thd->data_cond, &thd->data_mutex); + } + + if (thd->cancelled) + break; + + if (xb_crypt_decrypt(thd->cipher_handle, thd->from, + thd->from_len, thd->to, &thd->to_len, + thd->iv, thd->iv_len, + thd->hash_appended)) { + thd->failed = TRUE; + continue; + } + + } + + pthread_mutex_unlock(&thd->data_mutex); + + return NULL; +} diff --git a/extra/mariabackup/ds_decrypt.h b/extra/mariabackup/ds_decrypt.h new file mode 100644 index 00000000000..3bb4de55f54 --- /dev/null +++ b/extra/mariabackup/ds_decrypt.h @@ -0,0 +1,30 @@ +/****************************************************** +Copyright (c) 2017 Percona LLC and/or its affiliates. + +Encryption interface for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#ifndef DS_DECRYPT_H +#define DS_DECRYPT_H + +#include "datasink.h" + +extern datasink_t datasink_decrypt; + +extern int ds_decrypt_encrypt_threads; + +#endif diff --git a/extra/mariabackup/ds_encrypt.c b/extra/mariabackup/ds_encrypt.c new file mode 100644 index 00000000000..576ea207eb1 --- /dev/null +++ b/extra/mariabackup/ds_encrypt.c @@ -0,0 +1,446 @@ +/****************************************************** +Copyright (c) 2013 Percona LLC and/or its affiliates. + +Encryption datasink implementation for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + + +#include <my_base.h> +#include "common.h" +#include "datasink.h" +#include "xbcrypt_common.h" +#ifdef HAVE_GRYPT +#include "xbcrypt.h" + +#define XB_CRYPT_CHUNK_SIZE ((size_t) (ds_encrypt_encrypt_chunk_size)) + +typedef struct { + pthread_t id; + uint num; + pthread_mutex_t ctrl_mutex; + pthread_cond_t ctrl_cond; + pthread_mutex_t data_mutex; + pthread_cond_t data_cond; + my_bool started; + my_bool data_avail; + my_bool cancelled; + const uchar *from; + size_t from_len; + uchar *to; + uchar *iv; + size_t to_len; + gcry_cipher_hd_t cipher_handle; +} crypt_thread_ctxt_t; + +typedef struct { + crypt_thread_ctxt_t *threads; + uint nthreads; +} ds_encrypt_ctxt_t; + +typedef struct { + xb_wcrypt_t *xbcrypt_file; + ds_encrypt_ctxt_t *crypt_ctxt; + size_t bytes_processed; + ds_file_t *dest_file; +} ds_encrypt_file_t; + +/* Encryption options */ +uint ds_encrypt_encrypt_threads; +ulonglong ds_encrypt_encrypt_chunk_size; + +static ds_ctxt_t *encrypt_init(const char *root); +static ds_file_t *encrypt_open(ds_ctxt_t *ctxt, const char *path, + MY_STAT *mystat); +static int encrypt_write(ds_file_t *file, const void *buf, size_t len); +static int encrypt_close(ds_file_t *file); +static void encrypt_deinit(ds_ctxt_t *ctxt); + +datasink_t datasink_encrypt = { + &encrypt_init, + &encrypt_open, + &encrypt_write, + &encrypt_close, + &encrypt_deinit +}; + +static crypt_thread_ctxt_t *create_worker_threads(uint n); +static void destroy_worker_threads(crypt_thread_ctxt_t *threads, uint n); +static void *encrypt_worker_thread_func(void *arg); + +static uint encrypt_iv_len = 0; + +static +ssize_t +my_xb_crypt_write_callback(void *userdata, const void *buf, size_t len) +{ + ds_encrypt_file_t *encrypt_file; + + encrypt_file = (ds_encrypt_file_t *) userdata; + + xb_ad(encrypt_file != NULL); + xb_ad(encrypt_file->dest_file != NULL); + + if (!ds_write(encrypt_file->dest_file, buf, len)) { + return len; + } + return -1; +} + +static +ds_ctxt_t * +encrypt_init(const char *root) +{ + ds_ctxt_t *ctxt; + ds_encrypt_ctxt_t *encrypt_ctxt; + crypt_thread_ctxt_t *threads; + + if (xb_crypt_init(&encrypt_iv_len)) { + return NULL; + } + + /* Create and initialize the worker threads */ + threads = create_worker_threads(ds_encrypt_encrypt_threads); + if (threads == NULL) { + msg("encrypt: failed to create worker threads.\n"); + return NULL; + } + + ctxt = (ds_ctxt_t *) my_malloc(sizeof(ds_ctxt_t) + + sizeof(ds_encrypt_ctxt_t), + MYF(MY_FAE)); + + encrypt_ctxt = (ds_encrypt_ctxt_t *) (ctxt + 1); + encrypt_ctxt->threads = threads; + encrypt_ctxt->nthreads = ds_encrypt_encrypt_threads; + + ctxt->ptr = encrypt_ctxt; + ctxt->root = my_strdup(root, MYF(MY_FAE)); + + return ctxt; +} + +static +ds_file_t * +encrypt_open(ds_ctxt_t *ctxt, const char *path, MY_STAT *mystat) +{ + ds_ctxt_t *dest_ctxt; + + ds_encrypt_ctxt_t *crypt_ctxt; + ds_encrypt_file_t *crypt_file; + + char new_name[FN_REFLEN]; + ds_file_t *file; + + xb_ad(ctxt->pipe_ctxt != NULL); + dest_ctxt = ctxt->pipe_ctxt; + + crypt_ctxt = (ds_encrypt_ctxt_t *) ctxt->ptr; + + + file = (ds_file_t *) my_malloc(sizeof(ds_file_t) + + sizeof(ds_encrypt_file_t), + MYF(MY_FAE|MY_ZEROFILL)); + + crypt_file = (ds_encrypt_file_t *) (file + 1); + + /* Append the .xbcrypt extension to the filename */ + fn_format(new_name, path, "", ".xbcrypt", MYF(MY_APPEND_EXT)); + crypt_file->dest_file = ds_open(dest_ctxt, new_name, mystat); + if (crypt_file->dest_file == NULL) { + msg("encrypt: ds_open(\"%s\") failed.\n", new_name); + goto err; + } + + crypt_file->crypt_ctxt = crypt_ctxt; + crypt_file->xbcrypt_file = xb_crypt_write_open(crypt_file, + my_xb_crypt_write_callback); + + if (crypt_file->xbcrypt_file == NULL) { + msg("encrypt: xb_crypt_write_open() failed.\n"); + goto err; + } + + + file->ptr = crypt_file; + file->path = crypt_file->dest_file->path; + + return file; + +err: + if (crypt_file->dest_file) { + ds_close(crypt_file->dest_file); + } + my_free(file); + return NULL; +} + +static +int +encrypt_write(ds_file_t *file, const void *buf, size_t len) +{ + ds_encrypt_file_t *crypt_file; + ds_encrypt_ctxt_t *crypt_ctxt; + crypt_thread_ctxt_t *threads; + crypt_thread_ctxt_t *thd; + uint nthreads; + uint i; + const uchar *ptr; + + crypt_file = (ds_encrypt_file_t *) file->ptr; + crypt_ctxt = crypt_file->crypt_ctxt; + + threads = crypt_ctxt->threads; + nthreads = crypt_ctxt->nthreads; + + ptr = (const uchar *) buf; + while (len > 0) { + uint max_thread; + + /* Send data to worker threads for encryption */ + for (i = 0; i < nthreads; i++) { + size_t chunk_len; + + thd = threads + i; + + pthread_mutex_lock(&thd->ctrl_mutex); + + chunk_len = (len > XB_CRYPT_CHUNK_SIZE) ? + XB_CRYPT_CHUNK_SIZE : len; + thd->from = ptr; + thd->from_len = chunk_len; + + pthread_mutex_lock(&thd->data_mutex); + thd->data_avail = TRUE; + pthread_cond_signal(&thd->data_cond); + pthread_mutex_unlock(&thd->data_mutex); + + len -= chunk_len; + if (len == 0) { + break; + } + ptr += chunk_len; + } + + max_thread = (i < nthreads) ? i : nthreads - 1; + + /* Reap and stream the encrypted data */ + for (i = 0; i <= max_thread; i++) { + thd = threads + i; + + pthread_mutex_lock(&thd->data_mutex); + while (thd->data_avail == TRUE) { + pthread_cond_wait(&thd->data_cond, + &thd->data_mutex); + } + + xb_a(threads[i].to_len > 0); + + if (xb_crypt_write_chunk(crypt_file->xbcrypt_file, + threads[i].to, + threads[i].from_len + + XB_CRYPT_HASH_LEN, + threads[i].to_len, + threads[i].iv, + encrypt_iv_len)) { + msg("encrypt: write to the destination file " + "failed.\n"); + return 1; + } + + crypt_file->bytes_processed += threads[i].from_len; + + pthread_mutex_unlock(&threads[i].data_mutex); + pthread_mutex_unlock(&threads[i].ctrl_mutex); + } + } + + return 0; +} + +static +int +encrypt_close(ds_file_t *file) +{ + ds_encrypt_file_t *crypt_file; + ds_file_t *dest_file; + int rc = 0; + + crypt_file = (ds_encrypt_file_t *) file->ptr; + dest_file = crypt_file->dest_file; + + rc = xb_crypt_write_close(crypt_file->xbcrypt_file); + + if (ds_close(dest_file)) { + rc = 1; + } + + my_free(file); + + return rc; +} + +static +void +encrypt_deinit(ds_ctxt_t *ctxt) +{ + ds_encrypt_ctxt_t *crypt_ctxt; + + xb_ad(ctxt->pipe_ctxt != NULL); + + crypt_ctxt = (ds_encrypt_ctxt_t *) ctxt->ptr; + + destroy_worker_threads(crypt_ctxt->threads, crypt_ctxt->nthreads); + + my_free(ctxt->root); + my_free(ctxt); +} + +static +crypt_thread_ctxt_t * +create_worker_threads(uint n) +{ + crypt_thread_ctxt_t *threads; + uint i; + + threads = (crypt_thread_ctxt_t *) + my_malloc(sizeof(crypt_thread_ctxt_t) * n, MYF(MY_FAE)); + + for (i = 0; i < n; i++) { + crypt_thread_ctxt_t *thd = threads + i; + + thd->num = i + 1; + thd->started = FALSE; + thd->cancelled = FALSE; + thd->data_avail = FALSE; + + thd->to = (uchar *) my_malloc(XB_CRYPT_CHUNK_SIZE + + XB_CRYPT_HASH_LEN, MYF(MY_FAE)); + + thd->iv = (uchar *) my_malloc(encrypt_iv_len, MYF(MY_FAE)); + + /* Initialize the control mutex and condition var */ + if (pthread_mutex_init(&thd->ctrl_mutex, NULL) || + pthread_cond_init(&thd->ctrl_cond, NULL)) { + goto err; + } + + /* Initialize and data mutex and condition var */ + if (pthread_mutex_init(&thd->data_mutex, NULL) || + pthread_cond_init(&thd->data_cond, NULL)) { + goto err; + } + + if (xb_crypt_cipher_open(&thd->cipher_handle)) { + goto err; + } + + pthread_mutex_lock(&thd->ctrl_mutex); + + if (pthread_create(&thd->id, NULL, encrypt_worker_thread_func, + thd)) { + msg("encrypt: pthread_create() failed: " + "errno = %d\n", errno); + goto err; + } + } + + /* Wait for the threads to start */ + for (i = 0; i < n; i++) { + crypt_thread_ctxt_t *thd = threads + i; + + while (thd->started == FALSE) + pthread_cond_wait(&thd->ctrl_cond, &thd->ctrl_mutex); + pthread_mutex_unlock(&thd->ctrl_mutex); + } + + return threads; + +err: + return NULL; +} + +static +void +destroy_worker_threads(crypt_thread_ctxt_t *threads, uint n) +{ + uint i; + + for (i = 0; i < n; i++) { + crypt_thread_ctxt_t *thd = threads + i; + + pthread_mutex_lock(&thd->data_mutex); + threads[i].cancelled = TRUE; + pthread_cond_signal(&thd->data_cond); + pthread_mutex_unlock(&thd->data_mutex); + + pthread_join(thd->id, NULL); + + pthread_cond_destroy(&thd->data_cond); + pthread_mutex_destroy(&thd->data_mutex); + pthread_cond_destroy(&thd->ctrl_cond); + pthread_mutex_destroy(&thd->ctrl_mutex); + + xb_crypt_cipher_close(thd->cipher_handle); + + my_free(thd->to); + my_free(thd->iv); + } + + my_free(threads); +} + +static +void * +encrypt_worker_thread_func(void *arg) +{ + crypt_thread_ctxt_t *thd = (crypt_thread_ctxt_t *) arg; + + pthread_mutex_lock(&thd->ctrl_mutex); + + pthread_mutex_lock(&thd->data_mutex); + + thd->started = TRUE; + pthread_cond_signal(&thd->ctrl_cond); + + pthread_mutex_unlock(&thd->ctrl_mutex); + + while (1) { + thd->data_avail = FALSE; + pthread_cond_signal(&thd->data_cond); + + while (!thd->data_avail && !thd->cancelled) { + pthread_cond_wait(&thd->data_cond, &thd->data_mutex); + } + + if (thd->cancelled) + break; + + thd->to_len = thd->from_len; + + if (xb_crypt_encrypt(thd->cipher_handle, thd->from, + thd->from_len, thd->to, &thd->to_len, + thd->iv)) { + thd->to_len = 0; + continue; + } + } + + pthread_mutex_unlock(&thd->data_mutex); + + return NULL; +} +#endif /* HAVE_GCRYPT*/ diff --git a/extra/mariabackup/ds_encrypt.h b/extra/mariabackup/ds_encrypt.h new file mode 100644 index 00000000000..c4d8d7f8427 --- /dev/null +++ b/extra/mariabackup/ds_encrypt.h @@ -0,0 +1,33 @@ +/****************************************************** +Copyright (c) 2013 Percona LLC and/or its affiliates. + +Encryption interface for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#ifndef DS_ENCRYPT_H +#define DS_ENCRYPT_H + +#include "datasink.h" +#ifdef HAVE_GCRYPT +extern datasink_t datasink_encrypt; +#endif +/* Encryption options */ +extern uint ds_encrypt_encrypt_threads; +extern ulonglong ds_encrypt_encrypt_chunk_size; + + +#endif diff --git a/extra/mariabackup/ds_local.c b/extra/mariabackup/ds_local.c new file mode 100644 index 00000000000..3e2b1e0129b --- /dev/null +++ b/extra/mariabackup/ds_local.c @@ -0,0 +1,151 @@ +/****************************************************** +Copyright (c) 2011-2013 Percona LLC and/or its affiliates. + +Local datasink implementation for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#include <mysql_version.h> +#include <my_base.h> +#include <mysys_err.h> +#include "common.h" +#include "datasink.h" + +typedef struct { + File fd; +} ds_local_file_t; + +static ds_ctxt_t *local_init(const char *root); +static ds_file_t *local_open(ds_ctxt_t *ctxt, const char *path, + MY_STAT *mystat); +static int local_write(ds_file_t *file, const void *buf, size_t len); +static int local_close(ds_file_t *file); +static void local_deinit(ds_ctxt_t *ctxt); + +datasink_t datasink_local = { + &local_init, + &local_open, + &local_write, + &local_close, + &local_deinit +}; + +static +ds_ctxt_t * +local_init(const char *root) +{ + ds_ctxt_t *ctxt; + + if (my_mkdir(root, 0777, MYF(0)) < 0 + && my_errno != EEXIST && my_errno != EISDIR) + { + char errbuf[MYSYS_STRERROR_SIZE]; + my_strerror(errbuf, sizeof(errbuf),my_errno); + my_error(EE_CANT_MKDIR, MYF(ME_BELL | ME_WAITTANG), + root, my_errno,errbuf, my_errno); + return NULL; + } + + ctxt = my_malloc(sizeof(ds_ctxt_t), MYF(MY_FAE)); + + ctxt->root = my_strdup(root, MYF(MY_FAE)); + + return ctxt; +} + +static +ds_file_t * +local_open(ds_ctxt_t *ctxt, const char *path, + MY_STAT *mystat __attribute__((unused))) +{ + char fullpath[FN_REFLEN]; + char dirpath[FN_REFLEN]; + size_t dirpath_len; + size_t path_len; + ds_local_file_t *local_file; + ds_file_t *file; + File fd; + + fn_format(fullpath, path, ctxt->root, "", MYF(MY_RELATIVE_PATH)); + + /* Create the directory if needed */ + dirname_part(dirpath, fullpath, &dirpath_len); + if (my_mkdir(dirpath, 0777, MYF(0)) < 0 && my_errno != EEXIST) { + char errbuf[MYSYS_STRERROR_SIZE]; + my_strerror(errbuf, sizeof(errbuf), my_errno); + my_error(EE_CANT_MKDIR, MYF(ME_BELL | ME_WAITTANG), + dirpath, my_errno, errbuf); + return NULL; + } + + fd = my_create(fullpath, 0, O_WRONLY | O_BINARY | O_EXCL | O_NOFOLLOW, + MYF(MY_WME)); + if (fd < 0) { + return NULL; + } + + path_len = strlen(fullpath) + 1; /* terminating '\0' */ + + file = (ds_file_t *) my_malloc(sizeof(ds_file_t) + + sizeof(ds_local_file_t) + + path_len, + MYF(MY_FAE)); + local_file = (ds_local_file_t *) (file + 1); + + local_file->fd = fd; + + file->path = (char *) local_file + sizeof(ds_local_file_t); + memcpy(file->path, fullpath, path_len); + + file->ptr = local_file; + + return file; +} + +static +int +local_write(ds_file_t *file, const void *buf, size_t len) +{ + File fd = ((ds_local_file_t *) file->ptr)->fd; + + if (!my_write(fd, buf, len, MYF(MY_WME | MY_NABP))) { + posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED); + return 0; + } + + return 1; +} + +static +int +local_close(ds_file_t *file) +{ + File fd = ((ds_local_file_t *) file->ptr)->fd; + + my_free(file); + + my_sync(fd, MYF(MY_WME)); + + return my_close(fd, MYF(MY_WME)); +} + +static +void +local_deinit(ds_ctxt_t *ctxt) +{ + my_free(ctxt->root); + my_free(ctxt); +} diff --git a/extra/mariabackup/ds_local.h b/extra/mariabackup/ds_local.h new file mode 100644 index 00000000000..b0f0f04030c --- /dev/null +++ b/extra/mariabackup/ds_local.h @@ -0,0 +1,28 @@ +/****************************************************** +Copyright (c) 2011-2013 Percona LLC and/or its affiliates. + +Local datasink interface for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#ifndef DS_LOCAL_H +#define DS_LOCAL_H + +#include "datasink.h" + +extern datasink_t datasink_local; + +#endif diff --git a/extra/mariabackup/ds_stdout.c b/extra/mariabackup/ds_stdout.c new file mode 100644 index 00000000000..91a514ddf64 --- /dev/null +++ b/extra/mariabackup/ds_stdout.c @@ -0,0 +1,121 @@ +/****************************************************** +Copyright (c) 2013 Percona LLC and/or its affiliates. + +Local datasink implementation for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#include <my_base.h> +#include <mysys_err.h> +#include "common.h" +#include "datasink.h" + +typedef struct { + File fd; +} ds_stdout_file_t; + +static ds_ctxt_t *stdout_init(const char *root); +static ds_file_t *stdout_open(ds_ctxt_t *ctxt, const char *path, + MY_STAT *mystat); +static int stdout_write(ds_file_t *file, const void *buf, size_t len); +static int stdout_close(ds_file_t *file); +static void stdout_deinit(ds_ctxt_t *ctxt); + +datasink_t datasink_stdout = { + &stdout_init, + &stdout_open, + &stdout_write, + &stdout_close, + &stdout_deinit +}; + +static +ds_ctxt_t * +stdout_init(const char *root) +{ + ds_ctxt_t *ctxt; + + ctxt = my_malloc(sizeof(ds_ctxt_t), MYF(MY_FAE)); + + ctxt->root = my_strdup(root, MYF(MY_FAE)); + + return ctxt; +} + +static +ds_file_t * +stdout_open(ds_ctxt_t *ctxt __attribute__((unused)), + const char *path __attribute__((unused)), + MY_STAT *mystat __attribute__((unused))) +{ + ds_stdout_file_t *stdout_file; + ds_file_t *file; + size_t pathlen; + const char *fullpath = "<STDOUT>"; + + pathlen = strlen(fullpath) + 1; + + file = (ds_file_t *) my_malloc(sizeof(ds_file_t) + + sizeof(ds_stdout_file_t) + + pathlen, + MYF(MY_FAE)); + stdout_file = (ds_stdout_file_t *) (file + 1); + + +#ifdef __WIN__ + setmode(fileno(stdout), _O_BINARY); +#endif + + stdout_file->fd = my_fileno(stdout); + + file->path = (char *) stdout_file + sizeof(ds_stdout_file_t); + memcpy(file->path, fullpath, pathlen); + + file->ptr = stdout_file; + + return file; +} + +static +int +stdout_write(ds_file_t *file, const void *buf, size_t len) +{ + File fd = ((ds_stdout_file_t *) file->ptr)->fd; + + if (!my_write(fd, buf, len, MYF(MY_WME | MY_NABP))) { + posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED); + return 0; + } + + return 1; +} + +static +int +stdout_close(ds_file_t *file) +{ + my_free(file); + + return 1; +} + +static +void +stdout_deinit(ds_ctxt_t *ctxt) +{ + my_free(ctxt->root); + my_free(ctxt); +} diff --git a/extra/mariabackup/ds_stdout.h b/extra/mariabackup/ds_stdout.h new file mode 100644 index 00000000000..58940264fef --- /dev/null +++ b/extra/mariabackup/ds_stdout.h @@ -0,0 +1,28 @@ +/****************************************************** +Copyright (c) 2013 Percona LLC and/or its affiliates. + +Local datasink interface for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#ifndef DS_STDOUT_H +#define DS_STDOUT_H + +#include "datasink.h" + +extern datasink_t datasink_stdout; + +#endif diff --git a/extra/mariabackup/ds_tmpfile.c b/extra/mariabackup/ds_tmpfile.c new file mode 100644 index 00000000000..b039d83ba03 --- /dev/null +++ b/extra/mariabackup/ds_tmpfile.c @@ -0,0 +1,247 @@ +/****************************************************** +Copyright (c) 2012 Percona LLC and/or its affiliates. + +tmpfile datasink for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +/* Do all writes to temporary files first, then pipe them to the specified +datasink in a serialized way in deinit(). */ + +#include <my_base.h> +#include "common.h" +#include "datasink.h" + +typedef struct { + pthread_mutex_t mutex; + LIST *file_list; +} ds_tmpfile_ctxt_t; + +typedef struct { + LIST list; + File fd; + char *orig_path; + MY_STAT mystat; + ds_file_t *file; +} ds_tmp_file_t; + +static ds_ctxt_t *tmpfile_init(const char *root); +static ds_file_t *tmpfile_open(ds_ctxt_t *ctxt, const char *path, + MY_STAT *mystat); +static int tmpfile_write(ds_file_t *file, const void *buf, size_t len); +static int tmpfile_close(ds_file_t *file); +static void tmpfile_deinit(ds_ctxt_t *ctxt); + +datasink_t datasink_tmpfile = { + &tmpfile_init, + &tmpfile_open, + &tmpfile_write, + &tmpfile_close, + &tmpfile_deinit +}; + + +static ds_ctxt_t * +tmpfile_init(const char *root) +{ + ds_ctxt_t *ctxt; + ds_tmpfile_ctxt_t *tmpfile_ctxt; + + ctxt = my_malloc(sizeof(ds_ctxt_t) + sizeof(ds_tmpfile_ctxt_t), + MYF(MY_FAE)); + tmpfile_ctxt = (ds_tmpfile_ctxt_t *) (ctxt + 1); + tmpfile_ctxt->file_list = NULL; + if (pthread_mutex_init(&tmpfile_ctxt->mutex, NULL)) { + + my_free(ctxt); + return NULL; + } + + ctxt->ptr = tmpfile_ctxt; + ctxt->root = my_strdup(root, MYF(MY_FAE)); + + return ctxt; +} + +static ds_file_t * +tmpfile_open(ds_ctxt_t *ctxt, const char *path, + MY_STAT *mystat) +{ + ds_tmpfile_ctxt_t *tmpfile_ctxt; + char tmp_path[FN_REFLEN]; + ds_tmp_file_t *tmp_file; + ds_file_t *file; + size_t path_len; + File fd; + + /* Create a temporary file in tmpdir. The file will be automatically + removed on close. Code copied from mysql_tmpfile(). */ + fd = create_temp_file(tmp_path,xtrabackup_tmpdir, + "xbtemp", +#ifdef __WIN__ + O_BINARY | O_TRUNC | O_SEQUENTIAL | + O_TEMPORARY | O_SHORT_LIVED | +#endif /* __WIN__ */ + O_CREAT | O_EXCL | O_RDWR, + MYF(MY_WME)); + +#ifndef __WIN__ + if (fd >= 0) { + /* On Windows, open files cannot be removed, but files can be + created with the O_TEMPORARY flag to the same effect + ("delete on close"). */ + unlink(tmp_path); + } +#endif /* !__WIN__ */ + + if (fd < 0) { + return NULL; + } + + path_len = strlen(path) + 1; /* terminating '\0' */ + + file = (ds_file_t *) my_malloc(sizeof(ds_file_t) + + sizeof(ds_tmp_file_t) + path_len, + MYF(MY_FAE)); + + tmp_file = (ds_tmp_file_t *) (file + 1); + tmp_file->file = file; + memcpy(&tmp_file->mystat, mystat, sizeof(MY_STAT)); + /* Save a copy of 'path', since it may not be accessible later */ + tmp_file->orig_path = (char *) tmp_file + sizeof(ds_tmp_file_t); + + tmp_file->fd = fd; + memcpy(tmp_file->orig_path, path, path_len); + + /* Store the real temporary file name in file->path */ + file->path = my_strdup(tmp_path, MYF(MY_FAE)); + file->ptr = tmp_file; + + /* Store the file object in the list to be piped later */ + tmpfile_ctxt = (ds_tmpfile_ctxt_t *) ctxt->ptr; + tmp_file->list.data = tmp_file; + + pthread_mutex_lock(&tmpfile_ctxt->mutex); + tmpfile_ctxt->file_list = list_add(tmpfile_ctxt->file_list, + &tmp_file->list); + pthread_mutex_unlock(&tmpfile_ctxt->mutex); + + return file; +} + +static int +tmpfile_write(ds_file_t *file, const void *buf, size_t len) +{ + File fd = ((ds_tmp_file_t *) file->ptr)->fd; + + if (!my_write(fd, buf, len, MYF(MY_WME | MY_NABP))) { + posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED); + return 0; + } + + return 1; +} + +static int +tmpfile_close(ds_file_t *file) +{ + /* Do nothing -- we will close (and thus remove) the file after piping + it to the destination datasink in tmpfile_deinit(). */ + + my_free(file->path); + + return 0; +} + +static void +tmpfile_deinit(ds_ctxt_t *ctxt) +{ + LIST *list; + ds_tmpfile_ctxt_t *tmpfile_ctxt; + MY_STAT mystat; + ds_tmp_file_t *tmp_file; + ds_file_t *dst_file; + ds_ctxt_t *pipe_ctxt; + void *buf = NULL; + const size_t buf_size = 10 * 1024 * 1024; + size_t bytes; + size_t offset; + + pipe_ctxt = ctxt->pipe_ctxt; + xb_a(pipe_ctxt != NULL); + + buf = my_malloc(buf_size, MYF(MY_FAE)); + + tmpfile_ctxt = (ds_tmpfile_ctxt_t *) ctxt->ptr; + list = tmpfile_ctxt->file_list; + + /* Walk the files in the order they have been added */ + list = list_reverse(list); + while (list != NULL) { + tmp_file = list->data; + /* Stat the file to replace size and mtime on the original + * mystat struct */ + if (my_fstat(tmp_file->fd, &mystat, MYF(0))) { + msg("error: my_fstat() failed.\n"); + exit(EXIT_FAILURE); + } + tmp_file->mystat.st_size = mystat.st_size; + tmp_file->mystat.st_mtime = mystat.st_mtime; + + dst_file = ds_open(pipe_ctxt, tmp_file->orig_path, + &tmp_file->mystat); + if (dst_file == NULL) { + msg("error: could not stream a temporary file to " + "'%s'\n", tmp_file->orig_path); + exit(EXIT_FAILURE); + } + + /* copy to the destination datasink */ + posix_fadvise(tmp_file->fd, 0, 0, POSIX_FADV_SEQUENTIAL); + if (my_seek(tmp_file->fd, 0, SEEK_SET, MYF(0)) == + MY_FILEPOS_ERROR) { + msg("error: my_seek() failed for '%s', errno = %d.\n", + tmp_file->file->path, my_errno); + exit(EXIT_FAILURE); + } + offset = 0; + while ((bytes = my_read(tmp_file->fd, buf, buf_size, + MYF(MY_WME))) > 0) { + posix_fadvise(tmp_file->fd, offset, buf_size, POSIX_FADV_DONTNEED); + offset += buf_size; + if (ds_write(dst_file, buf, bytes)) { + msg("error: cannot write to stream for '%s'.\n", + tmp_file->orig_path); + exit(EXIT_FAILURE); + } + } + if (bytes == (size_t) -1) { + exit(EXIT_FAILURE); + } + + my_close(tmp_file->fd, MYF(MY_WME)); + ds_close(dst_file); + + list = list_rest(list); + my_free(tmp_file->file); + } + + pthread_mutex_destroy(&tmpfile_ctxt->mutex); + + my_free(buf); + my_free(ctxt->root); + my_free(ctxt); +} diff --git a/extra/mariabackup/ds_tmpfile.h b/extra/mariabackup/ds_tmpfile.h new file mode 100644 index 00000000000..c21f1a3f0b5 --- /dev/null +++ b/extra/mariabackup/ds_tmpfile.h @@ -0,0 +1,30 @@ +/****************************************************** +Copyright (c) 2012 Percona LLC and/or its affiliates. + +tmpfile datasink for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#ifndef DS_TMPFILE_H +#define DS_TMPFILE_H + +#include "datasink.h" + +extern datasink_t datasink_tmpfile; + +extern MY_TMPDIR mysql_tmpdir_list; + +#endif diff --git a/extra/mariabackup/ds_xbstream.c b/extra/mariabackup/ds_xbstream.c new file mode 100644 index 00000000000..42924a72d7f --- /dev/null +++ b/extra/mariabackup/ds_xbstream.c @@ -0,0 +1,223 @@ +/****************************************************** +Copyright (c) 2011-2013 Percona LLC and/or its affiliates. + +Streaming implementation for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#include <mysql_version.h> +#include <my_base.h> +#include "common.h" +#include "datasink.h" +#include "xbstream.h" + +typedef struct { + xb_wstream_t *xbstream; + ds_file_t *dest_file; + pthread_mutex_t mutex; +} ds_stream_ctxt_t; + +typedef struct { + xb_wstream_file_t *xbstream_file; + ds_stream_ctxt_t *stream_ctxt; +} ds_stream_file_t; + +/*********************************************************************** +General streaming interface */ + +static ds_ctxt_t *xbstream_init(const char *root); +static ds_file_t *xbstream_open(ds_ctxt_t *ctxt, const char *path, + MY_STAT *mystat); +static int xbstream_write(ds_file_t *file, const void *buf, size_t len); +static int xbstream_close(ds_file_t *file); +static void xbstream_deinit(ds_ctxt_t *ctxt); + +datasink_t datasink_xbstream = { + &xbstream_init, + &xbstream_open, + &xbstream_write, + &xbstream_close, + &xbstream_deinit +}; + +static +ssize_t +my_xbstream_write_callback(xb_wstream_file_t *f __attribute__((unused)), + void *userdata, const void *buf, size_t len) +{ + ds_stream_ctxt_t *stream_ctxt; + + stream_ctxt = (ds_stream_ctxt_t *) userdata; + + xb_ad(stream_ctxt != NULL); + xb_ad(stream_ctxt->dest_file != NULL); + + if (!ds_write(stream_ctxt->dest_file, buf, len)) { + return len; + } + return -1; +} + +static +ds_ctxt_t * +xbstream_init(const char *root __attribute__((unused))) +{ + ds_ctxt_t *ctxt; + ds_stream_ctxt_t *stream_ctxt; + xb_wstream_t *xbstream; + + ctxt = my_malloc(sizeof(ds_ctxt_t) + sizeof(ds_stream_ctxt_t), + MYF(MY_FAE)); + stream_ctxt = (ds_stream_ctxt_t *)(ctxt + 1); + + if (pthread_mutex_init(&stream_ctxt->mutex, NULL)) { + msg("xbstream_init: pthread_mutex_init() failed.\n"); + goto err; + } + + xbstream = xb_stream_write_new(); + if (xbstream == NULL) { + msg("xb_stream_write_new() failed.\n"); + goto err; + } + stream_ctxt->xbstream = xbstream; + stream_ctxt->dest_file = NULL; + + ctxt->ptr = stream_ctxt; + + return ctxt; + +err: + my_free(ctxt); + return NULL; +} + +static +ds_file_t * +xbstream_open(ds_ctxt_t *ctxt, const char *path, MY_STAT *mystat) +{ + ds_file_t *file; + ds_stream_file_t *stream_file; + ds_stream_ctxt_t *stream_ctxt; + ds_ctxt_t *dest_ctxt; + xb_wstream_t *xbstream; + xb_wstream_file_t *xbstream_file; + + + xb_ad(ctxt->pipe_ctxt != NULL); + dest_ctxt = ctxt->pipe_ctxt; + + stream_ctxt = (ds_stream_ctxt_t *) ctxt->ptr; + + pthread_mutex_lock(&stream_ctxt->mutex); + if (stream_ctxt->dest_file == NULL) { + stream_ctxt->dest_file = ds_open(dest_ctxt, path, mystat); + if (stream_ctxt->dest_file == NULL) { + return NULL; + } + } + pthread_mutex_unlock(&stream_ctxt->mutex); + + file = (ds_file_t *) my_malloc(sizeof(ds_file_t) + + sizeof(ds_stream_file_t), + MYF(MY_FAE)); + stream_file = (ds_stream_file_t *) (file + 1); + + xbstream = stream_ctxt->xbstream; + + xbstream_file = xb_stream_write_open(xbstream, path, mystat, + stream_ctxt, + my_xbstream_write_callback); + + if (xbstream_file == NULL) { + msg("xb_stream_write_open() failed.\n"); + goto err; + } + + stream_file->xbstream_file = xbstream_file; + stream_file->stream_ctxt = stream_ctxt; + file->ptr = stream_file; + file->path = stream_ctxt->dest_file->path; + + return file; + +err: + if (stream_ctxt->dest_file) { + ds_close(stream_ctxt->dest_file); + stream_ctxt->dest_file = NULL; + } + my_free(file); + + return NULL; +} + +static +int +xbstream_write(ds_file_t *file, const void *buf, size_t len) +{ + ds_stream_file_t *stream_file; + xb_wstream_file_t *xbstream_file; + + + stream_file = (ds_stream_file_t *) file->ptr; + + xbstream_file = stream_file->xbstream_file; + + if (xb_stream_write_data(xbstream_file, buf, len)) { + msg("xb_stream_write_data() failed.\n"); + return 1; + } + + return 0; +} + +static +int +xbstream_close(ds_file_t *file) +{ + ds_stream_file_t *stream_file; + int rc = 0; + + stream_file = (ds_stream_file_t *)file->ptr; + + rc = xb_stream_write_close(stream_file->xbstream_file); + + my_free(file); + + return rc; +} + +static +void +xbstream_deinit(ds_ctxt_t *ctxt) +{ + ds_stream_ctxt_t *stream_ctxt; + + stream_ctxt = (ds_stream_ctxt_t *) ctxt->ptr; + + if (xb_stream_write_done(stream_ctxt->xbstream)) { + msg("xb_stream_done() failed.\n"); + } + + if (stream_ctxt->dest_file) { + ds_close(stream_ctxt->dest_file); + stream_ctxt->dest_file = NULL; + } + + pthread_mutex_destroy(&stream_ctxt->mutex); + + my_free(ctxt); +} diff --git a/extra/mariabackup/ds_xbstream.h b/extra/mariabackup/ds_xbstream.h new file mode 100644 index 00000000000..30f34ac8318 --- /dev/null +++ b/extra/mariabackup/ds_xbstream.h @@ -0,0 +1,28 @@ +/****************************************************** +Copyright (c) 2011-2013 Percona LLC and/or its affiliates. + +Streaming interface for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#ifndef DS_XBSTREAM_H +#define DS_XBSTREAM_H + +#include "datasink.h" + +extern datasink_t datasink_xbstream; + +#endif diff --git a/extra/mariabackup/encryption_plugin.cc b/extra/mariabackup/encryption_plugin.cc new file mode 100644 index 00000000000..9f2782d89a1 --- /dev/null +++ b/extra/mariabackup/encryption_plugin.cc @@ -0,0 +1,157 @@ +#include <mysqld.h> +#include <mysql.h> +#include <xtrabackup.h> +#include <encryption_plugin.h> +#include <backup_copy.h> +#include <sql_plugin.h> +#include <sstream> +#include <vector> +#include <common.h> +#include <backup_mysql.h> + + +extern struct st_maria_plugin *mysql_optional_plugins[]; +extern struct st_maria_plugin *mysql_mandatory_plugins[]; +static void encryption_plugin_init(int argc, char **argv); + +extern char *xb_plugin_load; +extern char *xb_plugin_dir; + +const int PLUGIN_MAX_ARGS = 1024; +vector<string> backup_plugins_args; + +const char *QUERY_PLUGIN = +"SELECT plugin_name, plugin_library, @@plugin_dir" +" FROM information_schema.plugins WHERE plugin_type='ENCRYPTION'" +" AND plugin_status='ACTIVE'"; + +string encryption_plugin_config; + +static void add_to_plugin_load_list(const char *plugin_def) +{ + opt_plugin_load_list_ptr->push_back(new i_string(plugin_def)); +} + +static char XTRABACKUP_EXE[] = "xtrabackup"; + +void encryption_plugin_backup_init(MYSQL *mysql) +{ + MYSQL_RES *result; + MYSQL_ROW row; + ostringstream oss; + char *argv[PLUGIN_MAX_ARGS]; + int argc; + + result = xb_mysql_query(mysql, QUERY_PLUGIN, true, true); + row = mysql_fetch_row(result); + if (!row) + { + mysql_free_result(result); + return; + } + + char *name= row[0]; + char *library= row[1]; + char *dir= row[2]; + +#ifdef _WIN32 + for (char *p = dir; *p; p++) + if (*p == '\\') *p = '/'; +#endif + + string plugin_load(name); + if (library) + plugin_load += string("=") + library; + + oss << "plugin_load=" << plugin_load << endl; + + /* Required to load the plugin later.*/ + add_to_plugin_load_list(plugin_load.c_str()); + strncpy(opt_plugin_dir, dir, FN_REFLEN); + + oss << "plugin_dir=" << '"' << dir << '"' << endl; + + + /* Read plugin variables. */ + char query[1024]; + snprintf(query, 1024, "SHOW variables like '%s_%%'", name); + mysql_free_result(result); + + result = xb_mysql_query(mysql, query, true, true); + while ((row = mysql_fetch_row(result))) + { + string arg("--"); + arg += row[0]; + arg += "="; + arg += row[1]; + backup_plugins_args.push_back(arg); + oss << row[0] << "=" << row[1] << endl; + } + + mysql_free_result(result); + + /* Check whether to encrypt logs. */ + result = xb_mysql_query(mysql, "select @@innodb_encrypt_log", true, true); + row = mysql_fetch_row(result); + srv_encrypt_log = (row != 0 && row[0][0] == '1'); + oss << "innodb_encrypt_log=" << row[0] << endl; + + mysql_free_result(result); + + encryption_plugin_config = oss.str(); + + argc = 0; + argv[argc++] = XTRABACKUP_EXE; + for(size_t i = 0; i < backup_plugins_args.size(); i++) + { + argv[argc++] = (char *)backup_plugins_args[i].c_str(); + if (argc == PLUGIN_MAX_ARGS - 2) + break; + } + argv[argc] = 0; + + encryption_plugin_init(argc, argv); +} + +const char *encryption_plugin_get_config() +{ + return encryption_plugin_config.c_str(); +} + +extern int finalize_encryption_plugin(st_plugin_int *plugin); + + +void encryption_plugin_prepare_init(int argc, char **argv) +{ + + if (!xb_plugin_load) + { + /* This prevents crashes e.g in --stats with wrong my.cnf*/ + finalize_encryption_plugin(0); + return; + } + + add_to_plugin_load_list(xb_plugin_load); + + if (xb_plugin_dir) + strncpy(opt_plugin_dir, xb_plugin_dir, FN_REFLEN); + + char **new_argv = new char *[argc + 1]; + new_argv[0] = XTRABACKUP_EXE; + memcpy(&new_argv[1], argv, argc*sizeof(char *)); + + encryption_plugin_init(argc+1, new_argv); + + delete[] new_argv; +} + +static void encryption_plugin_init(int argc, char **argv) +{ + /* Patch optional and mandatory plugins, we only need to load the one in xb_plugin_load. */ + mysql_optional_plugins[0] = mysql_mandatory_plugins[0] = 0; + msg("Loading encryption plugin\n"); + for (int i= 1; i < argc; i++) + msg("\t Encryption plugin parameter : '%s'\n", argv[i]); + plugin_init(&argc, argv, PLUGIN_INIT_SKIP_PLUGIN_TABLE); +} + diff --git a/extra/mariabackup/encryption_plugin.h b/extra/mariabackup/encryption_plugin.h new file mode 100644 index 00000000000..16d74790254 --- /dev/null +++ b/extra/mariabackup/encryption_plugin.h @@ -0,0 +1,7 @@ +#include <mysql.h> +#include <string> +extern void encryption_plugin_backup_init(MYSQL *mysql); +extern const char* encryption_plugin_get_config(); +extern void encryption_plugin_prepare_init(int argc, char **argv); + +//extern void encryption_plugin_init(int argc, char **argv); diff --git a/extra/mariabackup/fil_cur.cc b/extra/mariabackup/fil_cur.cc new file mode 100644 index 00000000000..820d8e10c29 --- /dev/null +++ b/extra/mariabackup/fil_cur.cc @@ -0,0 +1,409 @@ +/****************************************************** +XtraBackup: hot backup tool for InnoDB +(c) 2009-2013 Percona LLC and/or its affiliates. +Originally Created 3/3/2009 Yasufumi Kinoshita +Written by Alexey Kopytov, Aleksandr Kuzminsky, Stewart Smith, Vadim Tkachenko, +Yasufumi Kinoshita, Ignacio Nin and Baron Schwartz. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +/* Source file cursor implementation */ + +#include <my_base.h> + +#include <univ.i> +#include <fil0fil.h> +#include <srv0start.h> +#include <trx0sys.h> + +#include "fil_cur.h" +#include "common.h" +#include "read_filt.h" +#include "xtrabackup.h" +#include "xb0xb.h" + +/* Size of read buffer in pages (640 pages = 10M for 16K sized pages) */ +#define XB_FIL_CUR_PAGES 640 + +/*********************************************************************** +Extracts the relative path ("database/table.ibd") of a tablespace from a +specified possibly absolute path. + +For user tablespaces both "./database/table.ibd" and +"/remote/dir/database/table.ibd" result in "database/table.ibd". + +For system tablepsaces (i.e. When is_system is TRUE) both "/remote/dir/ibdata1" +and "./ibdata1" yield "ibdata1" in the output. */ +const char * +xb_get_relative_path( +/*=================*/ + const char* path, /*!< in: tablespace path (either + relative or absolute) */ + ibool is_system) /*!< in: TRUE for system tablespaces, + i.e. when only the filename must be + returned. */ +{ + const char *next; + const char *cur; + const char *prev; + + prev = NULL; + cur = path; + + while ((next = strchr(cur, SRV_PATH_SEPARATOR)) != NULL) { + + prev = cur; + cur = next + 1; + } + + if (is_system) { + + return(cur); + } else { + + return((prev == NULL) ? cur : prev); + } + +} + +/**********************************************************************//** +Closes a file. */ +static +void +xb_fil_node_close_file( +/*===================*/ + fil_node_t* node) /*!< in: file node */ +{ + ibool ret; + + mutex_enter(&fil_system->mutex); + + ut_ad(node); + ut_a(node->n_pending == 0); + ut_a(node->n_pending_flushes == 0); + ut_a(!node->being_extended); + + if (!node->open) { + + mutex_exit(&fil_system->mutex); + + return; + } + + ret = os_file_close(node->handle); + ut_a(ret); + + node->open = FALSE; + + ut_a(fil_system->n_open > 0); + fil_system->n_open--; + fil_n_file_opened--; + + if (node->space->purpose == FIL_TABLESPACE && + fil_is_user_tablespace_id(node->space->id)) { + + ut_a(UT_LIST_GET_LEN(fil_system->LRU) > 0); + + /* The node is in the LRU list, remove it */ + UT_LIST_REMOVE(LRU, fil_system->LRU, node); + } + + mutex_exit(&fil_system->mutex); +} + +/************************************************************************ +Open a source file cursor and initialize the associated read filter. + +@return XB_FIL_CUR_SUCCESS on success, XB_FIL_CUR_SKIP if the source file must +be skipped and XB_FIL_CUR_ERROR on error. */ +xb_fil_cur_result_t +xb_fil_cur_open( +/*============*/ + xb_fil_cur_t* cursor, /*!< out: source file cursor */ + xb_read_filt_t* read_filter, /*!< in/out: the read filter */ + fil_node_t* node, /*!< in: source tablespace node */ + uint thread_n) /*!< thread number for diagnostics */ +{ + ulint page_size; + ulint page_size_shift; + ulint zip_size; + ibool success; + + /* Initialize these first so xb_fil_cur_close() handles them correctly + in case of error */ + cursor->orig_buf = NULL; + cursor->node = NULL; + + cursor->space_id = node->space->id; + cursor->is_system = !fil_is_user_tablespace_id(node->space->id); + + strncpy(cursor->abs_path, node->name, sizeof(cursor->abs_path)); + + /* Get the relative path for the destination tablespace name, i.e. the + one that can be appended to the backup root directory. Non-system + tablespaces may have absolute paths for remote tablespaces in MySQL + 5.6+. We want to make "local" copies for the backup. */ + strncpy(cursor->rel_path, + xb_get_relative_path(cursor->abs_path, cursor->is_system), + sizeof(cursor->rel_path)); + + /* In the backup mode we should already have a tablespace handle created + by fil_load_single_table_tablespace() unless it is a system + tablespace. Otherwise we open the file here. */ + if (cursor->is_system || !srv_backup_mode || srv_close_files) { + node->handle = + os_file_create_simple_no_error_handling(0, node->name, + OS_FILE_OPEN, + OS_FILE_READ_ONLY, + &success,0); + if (!success) { + /* The following call prints an error message */ + os_file_get_last_error(TRUE); + + msg("[%02u] xtrabackup: error: cannot open " + "tablespace %s\n", + thread_n, cursor->abs_path); + + return(XB_FIL_CUR_ERROR); + } + mutex_enter(&fil_system->mutex); + + node->open = TRUE; + + fil_system->n_open++; + fil_n_file_opened++; + + if (node->space->purpose == FIL_TABLESPACE && + fil_is_user_tablespace_id(node->space->id)) { + + /* Put the node to the LRU list */ + UT_LIST_ADD_FIRST(LRU, fil_system->LRU, node); + } + + mutex_exit(&fil_system->mutex); + } + + ut_ad(node->open); + + cursor->node = node; + cursor->file = node->handle; + + if (stat(cursor->abs_path, &cursor->statinfo)) { + msg("[%02u] xtrabackup: error: cannot stat %s\n", + thread_n, cursor->abs_path); + + xb_fil_cur_close(cursor); + + return(XB_FIL_CUR_ERROR); + } + + if (srv_unix_file_flush_method == SRV_UNIX_O_DIRECT + || srv_unix_file_flush_method == SRV_UNIX_O_DIRECT_NO_FSYNC) { + + os_file_set_nocache(cursor->file, node->name, "OPEN"); + } + + posix_fadvise(cursor->file, 0, 0, POSIX_FADV_SEQUENTIAL); + + /* Determine the page size */ + zip_size = xb_get_zip_size(cursor->file); + if (zip_size == ULINT_UNDEFINED) { + xb_fil_cur_close(cursor); + return(XB_FIL_CUR_SKIP); + } else if (zip_size) { + page_size = zip_size; + page_size_shift = get_bit_shift(page_size); + msg("[%02u] %s is compressed with page size = " + "%lu bytes\n", thread_n, node->name, page_size); + if (page_size_shift < 10 || page_size_shift > 14) { + msg("[%02u] xtrabackup: Error: Invalid " + "page size: %lu.\n", thread_n, page_size); + ut_error; + } + } else { + page_size = UNIV_PAGE_SIZE; + page_size_shift = UNIV_PAGE_SIZE_SHIFT; + } + cursor->page_size = page_size; + cursor->page_size_shift = page_size_shift; + cursor->zip_size = zip_size; + + /* Allocate read buffer */ + cursor->buf_size = XB_FIL_CUR_PAGES * page_size; + cursor->orig_buf = static_cast<byte *> + (ut_malloc(cursor->buf_size + UNIV_PAGE_SIZE)); + cursor->buf = static_cast<byte *> + (ut_align(cursor->orig_buf, UNIV_PAGE_SIZE)); + + cursor->buf_read = 0; + cursor->buf_npages = 0; + cursor->buf_offset = 0; + cursor->buf_page_no = 0; + cursor->thread_n = thread_n; + + cursor->space_size = (ulint)(cursor->statinfo.st_size / page_size); + + cursor->read_filter = read_filter; + cursor->read_filter->init(&cursor->read_filter_ctxt, cursor, + node->space->id); + + return(XB_FIL_CUR_SUCCESS); +} + +/************************************************************************ +Reads and verifies the next block of pages from the source +file. Positions the cursor after the last read non-corrupted page. + +@return XB_FIL_CUR_SUCCESS if some have been read successfully, XB_FIL_CUR_EOF +if there are no more pages to read and XB_FIL_CUR_ERROR on error. */ +xb_fil_cur_result_t +xb_fil_cur_read( +/*============*/ + xb_fil_cur_t* cursor) /*!< in/out: source file cursor */ +{ + ibool success; + byte* page; + ulint i; + ulint npages; + ulint retry_count; + xb_fil_cur_result_t ret; + ib_int64_t offset; + ib_int64_t to_read; + + cursor->read_filter->get_next_batch(&cursor->read_filter_ctxt, + &offset, &to_read); + + if (to_read == 0LL) { + return(XB_FIL_CUR_EOF); + } + + if (to_read > (ib_int64_t) cursor->buf_size) { + to_read = (ib_int64_t) cursor->buf_size; + } + + xb_a(to_read > 0 && to_read <= 0xFFFFFFFFLL); + + if (to_read % cursor->page_size != 0 && + offset + to_read == cursor->statinfo.st_size) { + + if (to_read < (ib_int64_t) cursor->page_size) { + msg("[%02u] xtrabackup: Warning: junk at the end of " + "%s:\n", cursor->thread_n, cursor->abs_path); + msg("[%02u] xtrabackup: Warning: offset = %llu, " + "to_read = %llu\n", + cursor->thread_n, + (unsigned long long) offset, + (unsigned long long) to_read); + + return(XB_FIL_CUR_EOF); + } + + to_read = (ib_int64_t) (((ulint) to_read) & + ~(cursor->page_size - 1)); + } + + xb_a(to_read % cursor->page_size == 0); + + npages = (ulint) (to_read >> cursor->page_size_shift); + + retry_count = 10; + ret = XB_FIL_CUR_SUCCESS; + +read_retry: + xtrabackup_io_throttling(); + + cursor->buf_read = 0; + cursor->buf_npages = 0; + cursor->buf_offset = offset; + cursor->buf_page_no = (ulint)(offset >> cursor->page_size_shift); + + success = os_file_read(cursor->file, cursor->buf, offset, + (ulint)to_read); + if (!success) { + return(XB_FIL_CUR_ERROR); + } + + fil_system_enter(); + fil_space_t *space = fil_space_get_by_id(cursor->space_id); + fil_system_exit(); + + /* check pages for corruption and re-read if necessary. i.e. in case of + partially written pages */ + for (page = cursor->buf, i = 0; i < npages; + page += cursor->page_size, i++) { + ib_int64_t page_no = cursor->buf_page_no + i; + + bool checksum_ok = fil_space_verify_crypt_checksum(page, cursor->zip_size,space, (ulint)page_no); + + if (!checksum_ok && + buf_page_is_corrupted(true, page, cursor->zip_size,space)) { + + if (cursor->is_system && + page_no >= (ib_int64_t)FSP_EXTENT_SIZE && + page_no < (ib_int64_t) FSP_EXTENT_SIZE * 3) { + /* skip doublewrite buffer pages */ + xb_a(cursor->page_size == UNIV_PAGE_SIZE); + msg("[%02u] xtrabackup: " + "Page %lu is a doublewrite buffer page, " + "skipping.\n", cursor->thread_n, page_no); + } else { + retry_count--; + if (retry_count == 0) { + msg("[%02u] xtrabackup: " + "Error: failed to read page after " + "10 retries. File %s seems to be " + "corrupted.\n", cursor->thread_n, + cursor->abs_path); + ret = XB_FIL_CUR_ERROR; + break; + } + msg("[%02u] xtrabackup: " + "Database page corruption detected at page " + "%lu, retrying...\n", cursor->thread_n, + page_no); + + os_thread_sleep(100000); + + goto read_retry; + } + } + cursor->buf_read += cursor->page_size; + cursor->buf_npages++; + } + + posix_fadvise(cursor->file, offset, to_read, POSIX_FADV_DONTNEED); + + return(ret); +} + +/************************************************************************ +Close the source file cursor opened with xb_fil_cur_open() and its +associated read filter. */ +void +xb_fil_cur_close( +/*=============*/ + xb_fil_cur_t *cursor) /*!< in/out: source file cursor */ +{ + cursor->read_filter->deinit(&cursor->read_filter_ctxt); + + if (cursor->orig_buf != NULL) { + ut_free(cursor->orig_buf); + } + if (cursor->node != NULL) { + xb_fil_node_close_file(cursor->node); + cursor->file = XB_FILE_UNDEFINED; + } +} diff --git a/extra/mariabackup/fil_cur.h b/extra/mariabackup/fil_cur.h new file mode 100644 index 00000000000..88239efd2bb --- /dev/null +++ b/extra/mariabackup/fil_cur.h @@ -0,0 +1,123 @@ +/****************************************************** +XtraBackup: hot backup tool for InnoDB +(c) 2009-2013 Percona LLC and/or its affiliates. +Originally Created 3/3/2009 Yasufumi Kinoshita +Written by Alexey Kopytov, Aleksandr Kuzminsky, Stewart Smith, Vadim Tkachenko, +Yasufumi Kinoshita, Ignacio Nin and Baron Schwartz. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +/* Source file cursor interface */ + +#ifndef FIL_CUR_H +#define FIL_CUR_H + +#include <my_dir.h> +#include "read_filt.h" + +struct xb_fil_cur_t { + os_file_t file; /*!< source file handle */ + fil_node_t* node; /*!< source tablespace node */ + char rel_path[FN_REFLEN]; + /*!< normalized file path */ + char abs_path[FN_REFLEN]; + /*!< absolute file path */ + MY_STAT statinfo; /*!< information about the file */ + ulint zip_size; /*!< compressed page size in bytes or 0 + for uncompressed pages */ + ulint page_size; /*!< = zip_size for compressed pages or + UNIV_PAGE_SIZE for uncompressed ones */ + ulint page_size_shift;/*!< bit shift corresponding to + page_size */ + my_bool is_system; /*!< TRUE for system tablespace, FALSE + otherwise */ + xb_read_filt_t* read_filter; /*!< read filter */ + xb_read_filt_ctxt_t read_filter_ctxt; + /*!< read filter context */ + byte* orig_buf; /*!< read buffer */ + byte* buf; /*!< aligned pointer for orig_buf */ + size_t buf_size; /*!< buffer size in bytes */ + size_t buf_read; /*!< number of read bytes in buffer + after the last cursor read */ + size_t buf_npages; /*!< number of pages in buffer after the + last cursor read */ + ib_int64_t buf_offset; /*!< file offset of the first page in + buffer */ + ulint buf_page_no; /*!< number of the first page in + buffer */ + uint thread_n; /*!< thread number for diagnostics */ + ulint space_id; /*!< ID of tablespace */ + ulint space_size; /*!< space size in pages */ +}; + +typedef enum { + XB_FIL_CUR_SUCCESS, + XB_FIL_CUR_SKIP, + XB_FIL_CUR_ERROR, + XB_FIL_CUR_EOF +} xb_fil_cur_result_t; + +/************************************************************************ +Open a source file cursor and initialize the associated read filter. + +@return XB_FIL_CUR_SUCCESS on success, XB_FIL_CUR_SKIP if the source file must +be skipped and XB_FIL_CUR_ERROR on error. */ +xb_fil_cur_result_t +xb_fil_cur_open( +/*============*/ + xb_fil_cur_t* cursor, /*!< out: source file cursor */ + xb_read_filt_t* read_filter, /*!< in/out: the read filter */ + fil_node_t* node, /*!< in: source tablespace node */ + uint thread_n); /*!< thread number for diagnostics */ + +/************************************************************************ +Reads and verifies the next block of pages from the source +file. Positions the cursor after the last read non-corrupted page. + +@return XB_FIL_CUR_SUCCESS if some have been read successfully, XB_FIL_CUR_EOF +if there are no more pages to read and XB_FIL_CUR_ERROR on error. */ +xb_fil_cur_result_t +xb_fil_cur_read( +/*============*/ + xb_fil_cur_t* cursor); /*!< in/out: source file cursor */ + +/************************************************************************ +Close the source file cursor opened with xb_fil_cur_open() and its +associated read filter. */ +void +xb_fil_cur_close( +/*=============*/ + xb_fil_cur_t *cursor); /*!< in/out: source file cursor */ + +/*********************************************************************** +Extracts the relative path ("database/table.ibd") of a tablespace from a +specified possibly absolute path. + +For user tablespaces both "./database/table.ibd" and +"/remote/dir/database/table.ibd" result in "database/table.ibd". + +For system tablepsaces (i.e. When is_system is TRUE) both "/remote/dir/ibdata1" +and "./ibdata1" yield "ibdata1" in the output. */ +const char * +xb_get_relative_path( +/*=================*/ + const char* path, /*!< in: tablespace path (either + relative or absolute) */ + ibool is_system); /*!< in: TRUE for system tablespaces, + i.e. when only the filename must be + returned. */ + +#endif diff --git a/extra/mariabackup/innobackupex.cc b/extra/mariabackup/innobackupex.cc new file mode 100644 index 00000000000..59fb8fb5565 --- /dev/null +++ b/extra/mariabackup/innobackupex.cc @@ -0,0 +1,1132 @@ +/****************************************************** +hot backup tool for InnoDB +(c) 2009-2015 Percona LLC and/or its affiliates +Originally Created 3/3/2009 Yasufumi Kinoshita +Written by Alexey Kopytov, Aleksandr Kuzminsky, Stewart Smith, Vadim Tkachenko, +Yasufumi Kinoshita, Ignacio Nin and Baron Schwartz. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +******************************************************* + +This file incorporates work covered by the following copyright and +permission notice: + +Copyright (c) 2000, 2011, MySQL AB & Innobase Oy. All Rights Reserved. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 59 Temple +Place, Suite 330, Boston, MA 02111-1307 USA + +*******************************************************/ + +#include <my_global.h> +#include <stdio.h> +#include <string.h> +#include <mysql.h> +#include <my_dir.h> +#include <ut0mem.h> +#include <os0sync.h> +#include <os0file.h> +#include <srv0start.h> +#include <algorithm> +#include <mysqld.h> +#include <my_default.h> +#include <my_getopt.h> +#include <string> +#include <sstream> +#include <set> +#include "common.h" +#include "innobackupex.h" +#include "xtrabackup.h" +#include "xbstream.h" +#include "fil_cur.h" +#include "write_filt.h" +#include "backup_copy.h" + +using std::min; +using std::max; + +/* options */ +my_bool opt_ibx_version = FALSE; +my_bool opt_ibx_help = FALSE; +my_bool opt_ibx_apply_log = FALSE; +my_bool opt_ibx_redo_only = FALSE; +my_bool opt_ibx_incremental = FALSE; +my_bool opt_ibx_notimestamp = FALSE; + +my_bool opt_ibx_copy_back = FALSE; +my_bool opt_ibx_move_back = FALSE; +my_bool opt_ibx_galera_info = FALSE; +my_bool opt_ibx_slave_info = FALSE; +my_bool opt_ibx_no_lock = FALSE; +my_bool opt_ibx_safe_slave_backup = FALSE; +my_bool opt_ibx_rsync = FALSE; +my_bool opt_ibx_force_non_empty_dirs = FALSE; +my_bool opt_ibx_noversioncheck = FALSE; +my_bool opt_ibx_no_backup_locks = FALSE; +my_bool opt_ibx_decompress = FALSE; + +char *opt_ibx_incremental_history_name = NULL; +char *opt_ibx_incremental_history_uuid = NULL; + +char *opt_ibx_user = NULL; +char *opt_ibx_password = NULL; +char *opt_ibx_host = NULL; +char *opt_ibx_defaults_group = NULL; +char *opt_ibx_socket = NULL; +uint opt_ibx_port = 0; +char *opt_ibx_login_path = NULL; + + +ulong opt_ibx_lock_wait_query_type; +ulong opt_ibx_kill_long_query_type; + +ulong opt_ibx_decrypt_algo = 0; + +uint opt_ibx_kill_long_queries_timeout = 0; +uint opt_ibx_lock_wait_timeout = 0; +uint opt_ibx_lock_wait_threshold = 0; +uint opt_ibx_debug_sleep_before_unlock = 0; +uint opt_ibx_safe_slave_backup_timeout = 0; + +const char *opt_ibx_history = NULL; +bool opt_ibx_decrypt = false; + +char *opt_ibx_include = NULL; +char *opt_ibx_databases = NULL; +bool ibx_partial_backup = false; + +char *ibx_position_arg = NULL; +char *ibx_backup_directory = NULL; + +/* copy of proxied xtrabackup options */ +my_bool ibx_xb_close_files; +my_bool ibx_xtrabackup_compact; +const char *ibx_xtrabackup_compress_alg; +uint ibx_xtrabackup_compress_threads; +ulonglong ibx_xtrabackup_compress_chunk_size; +ulong ibx_xtrabackup_encrypt_algo; +char *ibx_xtrabackup_encrypt_key; +char *ibx_xtrabackup_encrypt_key_file; +uint ibx_xtrabackup_encrypt_threads; +ulonglong ibx_xtrabackup_encrypt_chunk_size; +my_bool ibx_xtrabackup_export; +char *ibx_xtrabackup_extra_lsndir; +char *ibx_xtrabackup_incremental_basedir; +char *ibx_xtrabackup_incremental_dir; +my_bool ibx_xtrabackup_incremental_force_scan; +ulint ibx_xtrabackup_log_copy_interval; +char *ibx_xtrabackup_incremental; +int ibx_xtrabackup_parallel; +my_bool ibx_xtrabackup_rebuild_indexes; +ulint ibx_xtrabackup_rebuild_threads; +char *ibx_xtrabackup_stream_str; +char *ibx_xtrabackup_tables_file; +long ibx_xtrabackup_throttle; +char *ibx_opt_mysql_tmpdir; +longlong ibx_xtrabackup_use_memory; + + +static inline int ibx_msg(const char *fmt, ...) ATTRIBUTE_FORMAT(printf, 1, 2); +static inline int ibx_msg(const char *fmt, ...) +{ + int result; + time_t t = time(NULL); + char date[100]; + char *line; + va_list args; + + strftime(date, sizeof(date), "%y%m%d %H:%M:%S", localtime(&t)); + + va_start(args, fmt); + + result = vasprintf(&line, fmt, args); + + va_end(args); + + if (result != -1) { + result = fprintf(stderr, "%s %s: %s", + date, INNOBACKUPEX_BIN_NAME, line); + free(line); + } + + return result; +} + +enum innobackupex_options +{ + OPT_APPLY_LOG = 256, + OPT_COPY_BACK, + OPT_MOVE_BACK, + OPT_REDO_ONLY, + OPT_GALERA_INFO, + OPT_SLAVE_INFO, + OPT_INCREMENTAL, + OPT_INCREMENTAL_HISTORY_NAME, + OPT_INCREMENTAL_HISTORY_UUID, + OPT_LOCK_WAIT_QUERY_TYPE, + OPT_KILL_LONG_QUERY_TYPE, + OPT_KILL_LONG_QUERIES_TIMEOUT, + OPT_LOCK_WAIT_TIMEOUT, + OPT_LOCK_WAIT_THRESHOLD, + OPT_DEBUG_SLEEP_BEFORE_UNLOCK, + OPT_NO_LOCK, + OPT_SAFE_SLAVE_BACKUP, + OPT_SAFE_SLAVE_BACKUP_TIMEOUT, + OPT_RSYNC, + OPT_HISTORY, + OPT_INCLUDE, + OPT_FORCE_NON_EMPTY_DIRS, + OPT_NO_TIMESTAMP, + OPT_NO_VERSION_CHECK, + OPT_NO_BACKUP_LOCKS, + OPT_DATABASES, + OPT_DECRYPT, + OPT_DECOMPRESS, + + /* options wich are passed directly to xtrabackup */ + OPT_CLOSE_FILES, + OPT_COMPACT, + OPT_COMPRESS, + OPT_COMPRESS_THREADS, + OPT_COMPRESS_CHUNK_SIZE, + OPT_ENCRYPT, + OPT_ENCRYPT_KEY, + OPT_ENCRYPT_KEY_FILE, + OPT_ENCRYPT_THREADS, + OPT_ENCRYPT_CHUNK_SIZE, + OPT_EXPORT, + OPT_EXTRA_LSNDIR, + OPT_INCREMENTAL_BASEDIR, + OPT_INCREMENTAL_DIR, + OPT_INCREMENTAL_FORCE_SCAN, + OPT_LOG_COPY_INTERVAL, + OPT_PARALLEL, + OPT_REBUILD_INDEXES, + OPT_REBUILD_THREADS, + OPT_STREAM, + OPT_TABLES_FILE, + OPT_THROTTLE, + OPT_USE_MEMORY +}; + +ibx_mode_t ibx_mode = IBX_MODE_BACKUP; + +static struct my_option ibx_long_options[] = +{ + {"version", 'v', "print xtrabackup version information", + (uchar *) &opt_ibx_version, (uchar *) &opt_ibx_version, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"help", '?', "This option displays a help screen and exits.", + (uchar *) &opt_ibx_help, (uchar *) &opt_ibx_help, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"apply-log", OPT_APPLY_LOG, "Prepare a backup in BACKUP-DIR by " + "applying the transaction log file named \"xtrabackup_logfile\" " + "located in the same directory. Also, create new transaction logs. " + "The InnoDB configuration is read from the file \"backup-my.cnf\".", + (uchar*) &opt_ibx_apply_log, (uchar*) &opt_ibx_apply_log, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"redo-only", OPT_REDO_ONLY, "This option should be used when " + "preparing the base full backup and when merging all incrementals " + "except the last one. This forces xtrabackup to skip the \"rollback\" " + "phase and do a \"redo\" only. This is necessary if the backup will " + "have incremental changes applied to it later. See the xtrabackup " + "documentation for details.", + (uchar *) &opt_ibx_redo_only, (uchar *) &opt_ibx_redo_only, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"copy-back", OPT_COPY_BACK, "Copy all the files in a previously made " + "backup from the backup directory to their original locations.", + (uchar *) &opt_ibx_copy_back, (uchar *) &opt_ibx_copy_back, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"move-back", OPT_MOVE_BACK, "Move all the files in a previously made " + "backup from the backup directory to the actual datadir location. " + "Use with caution, as it removes backup files.", + (uchar *) &opt_ibx_move_back, (uchar *) &opt_ibx_move_back, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"galera-info", OPT_GALERA_INFO, "This options creates the " + "xtrabackup_galera_info file which contains the local node state at " + "the time of the backup. Option should be used when performing the " + "backup of Percona-XtraDB-Cluster. Has no effect when backup locks " + "are used to create the backup.", + (uchar *) &opt_ibx_galera_info, (uchar *) &opt_ibx_galera_info, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"slave-info", OPT_SLAVE_INFO, "This option is useful when backing " + "up a replication slave server. It prints the binary log position " + "and name of the master server. It also writes this information to " + "the \"xtrabackup_slave_info\" file as a \"CHANGE MASTER\" command. " + "A new slave for this master can be set up by starting a slave server " + "on this backup and issuing a \"CHANGE MASTER\" command with the " + "binary log position saved in the \"xtrabackup_slave_info\" file.", + (uchar *) &opt_ibx_slave_info, (uchar *) &opt_ibx_slave_info, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"incremental", OPT_INCREMENTAL, "This option tells xtrabackup to " + "create an incremental backup, rather than a full one. It is passed " + "to the xtrabackup child process. When this option is specified, " + "either --incremental-lsn or --incremental-basedir can also be given. " + "If neither option is given, option --incremental-basedir is passed " + "to xtrabackup by default, set to the first timestamped backup " + "directory in the backup base directory.", + (uchar *) &opt_ibx_incremental, (uchar *) &opt_ibx_incremental, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"no-lock", OPT_NO_LOCK, "Use this option to disable table lock " + "with \"FLUSH TABLES WITH READ LOCK\". Use it only if ALL your " + "tables are InnoDB and you DO NOT CARE about the binary log " + "position of the backup. This option shouldn't be used if there " + "are any DDL statements being executed or if any updates are " + "happening on non-InnoDB tables (this includes the system MyISAM " + "tables in the mysql database), otherwise it could lead to an " + "inconsistent backup. If you are considering to use --no-lock " + "because your backups are failing to acquire the lock, this could " + "be because of incoming replication events preventing the lock " + "from succeeding. Please try using --safe-slave-backup to " + "momentarily stop the replication slave thread, this may help " + "the backup to succeed and you then don't need to resort to " + "using this option.", + (uchar *) &opt_ibx_no_lock, (uchar *) &opt_ibx_no_lock, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"safe-slave-backup", OPT_SAFE_SLAVE_BACKUP, "Stop slave SQL thread " + "and wait to start backup until Slave_open_temp_tables in " + "\"SHOW STATUS\" is zero. If there are no open temporary tables, " + "the backup will take place, otherwise the SQL thread will be " + "started and stopped until there are no open temporary tables. " + "The backup will fail if Slave_open_temp_tables does not become " + "zero after --safe-slave-backup-timeout seconds. The slave SQL " + "thread will be restarted when the backup finishes.", + (uchar *) &opt_ibx_safe_slave_backup, + (uchar *) &opt_ibx_safe_slave_backup, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"rsync", OPT_RSYNC, "Uses the rsync utility to optimize local file " + "transfers. When this option is specified, innobackupex uses rsync " + "to copy all non-InnoDB files instead of spawning a separate cp for " + "each file, which can be much faster for servers with a large number " + "of databases or tables. This option cannot be used together with " + "--stream.", + (uchar *) &opt_ibx_rsync, (uchar *) &opt_ibx_rsync, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"force-non-empty-directories", OPT_FORCE_NON_EMPTY_DIRS, "This " + "option, when specified, makes --copy-back or --move-back transfer " + "files to non-empty directories. Note that no existing files will be " + "overwritten. If --copy-back or --nove-back has to copy a file from " + "the backup directory which already exists in the destination " + "directory, it will still fail with an error.", + (uchar *) &opt_ibx_force_non_empty_dirs, + (uchar *) &opt_ibx_force_non_empty_dirs, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"no-timestamp", OPT_NO_TIMESTAMP, "This option prevents creation of a " + "time-stamped subdirectory of the BACKUP-ROOT-DIR given on the " + "command line. When it is specified, the backup is done in " + "BACKUP-ROOT-DIR instead.", + (uchar *) &opt_ibx_notimestamp, + (uchar *) &opt_ibx_notimestamp, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"no-version-check", OPT_NO_VERSION_CHECK, "This option disables the " + "version check which is enabled by the --version-check option.", + (uchar *) &opt_ibx_noversioncheck, + (uchar *) &opt_ibx_noversioncheck, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"no-backup-locks", OPT_NO_BACKUP_LOCKS, "This option controls if " + "backup locks should be used instead of FLUSH TABLES WITH READ LOCK " + "on the backup stage. The option has no effect when backup locks are " + "not supported by the server. This option is enabled by default, " + "disable with --no-backup-locks.", + (uchar *) &opt_ibx_no_backup_locks, + (uchar *) &opt_ibx_no_backup_locks, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"decompress", OPT_DECOMPRESS, "Decompresses all files with the .qp " + "extension in a backup previously made with the --compress option.", + (uchar *) &opt_ibx_decompress, + (uchar *) &opt_ibx_decompress, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"user", 'u', "This option specifies the MySQL username used " + "when connecting to the server, if that's not the current user. " + "The option accepts a string argument. See mysql --help for details.", + (uchar*) &opt_ibx_user, (uchar*) &opt_ibx_user, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"host", 'H', "This option specifies the host to use when " + "connecting to the database server with TCP/IP. The option accepts " + "a string argument. See mysql --help for details.", + (uchar*) &opt_ibx_host, (uchar*) &opt_ibx_host, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"port", 'P', "This option specifies the port to use when " + "connecting to the database server with TCP/IP. The option accepts " + "a string argument. See mysql --help for details.", + &opt_ibx_port, &opt_ibx_port, 0, GET_UINT, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"password", 'p', "This option specifies the password to use " + "when connecting to the database. It accepts a string argument. " + "See mysql --help for details.", + 0, 0, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"socket", 'S', "This option specifies the socket to use when " + "connecting to the local database server with a UNIX domain socket. " + "The option accepts a string argument. See mysql --help for details.", + (uchar*) &opt_ibx_socket, (uchar*) &opt_ibx_socket, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"incremental-history-name", OPT_INCREMENTAL_HISTORY_NAME, + "This option specifies the name of the backup series stored in the " + "PERCONA_SCHEMA.xtrabackup_history history record to base an " + "incremental backup on. Xtrabackup will search the history table " + "looking for the most recent (highest innodb_to_lsn), successful " + "backup in the series and take the to_lsn value to use as the " + "starting lsn for the incremental backup. This will be mutually " + "exclusive with --incremental-history-uuid, --incremental-basedir " + "and --incremental-lsn. If no valid lsn can be found (no series by " + "that name, no successful backups by that name) xtrabackup will " + "return with an error. It is used with the --incremental option.", + (uchar*) &opt_ibx_incremental_history_name, + (uchar*) &opt_ibx_incremental_history_name, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"incremental-history-uuid", OPT_INCREMENTAL_HISTORY_UUID, + "This option specifies the UUID of the specific history record " + "stored in the PERCONA_SCHEMA.xtrabackup_history to base an " + "incremental backup on. --incremental-history-name, " + "--incremental-basedir and --incremental-lsn. If no valid lsn can be " + "found (no success record with that uuid) xtrabackup will return " + "with an error. It is used with the --incremental option.", + (uchar*) &opt_ibx_incremental_history_uuid, + (uchar*) &opt_ibx_incremental_history_uuid, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"decrypt", OPT_DECRYPT, "Decrypts all files with the .xbcrypt " + "extension in a backup previously made with --encrypt option.", + &opt_ibx_decrypt_algo, &opt_ibx_decrypt_algo, + &xtrabackup_encrypt_algo_typelib, GET_ENUM, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"ftwrl-wait-query-type", OPT_LOCK_WAIT_QUERY_TYPE, + "This option specifies which types of queries are allowed to complete " + "before innobackupex will issue the global lock. Default is all.", + (uchar*) &opt_ibx_lock_wait_query_type, + (uchar*) &opt_ibx_lock_wait_query_type, &query_type_typelib, + GET_ENUM, REQUIRED_ARG, QUERY_TYPE_ALL, 0, 0, 0, 0, 0}, + + {"kill-long-query-type", OPT_KILL_LONG_QUERY_TYPE, + "This option specifies which types of queries should be killed to " + "unblock the global lock. Default is \"all\".", + (uchar*) &opt_ibx_kill_long_query_type, + (uchar*) &opt_ibx_kill_long_query_type, &query_type_typelib, + GET_ENUM, REQUIRED_ARG, QUERY_TYPE_SELECT, 0, 0, 0, 0, 0}, + + {"history", OPT_HISTORY, + "This option enables the tracking of backup history in the " + "PERCONA_SCHEMA.xtrabackup_history table. An optional history " + "series name may be specified that will be placed with the history " + "record for the current backup being taken.", + NULL, NULL, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, + + {"include", OPT_INCLUDE, + "This option is a regular expression to be matched against table " + "names in databasename.tablename format. It is passed directly to " + "xtrabackup's --tables option. See the xtrabackup documentation for " + "details.", + (uchar*) &opt_ibx_include, + (uchar*) &opt_ibx_include, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"databases", OPT_DATABASES, + "This option specifies the list of databases that innobackupex should " + "back up. The option accepts a string argument or path to file that " + "contains the list of databases to back up. The list is of the form " + "\"databasename1[.table_name1] databasename2[.table_name2] . . .\". " + "If this option is not specified, all databases containing MyISAM and " + "InnoDB tables will be backed up. Please make sure that --databases " + "contains all of the InnoDB databases and tables, so that all of the " + "innodb.frm files are also backed up. In case the list is very long, " + "this can be specified in a file, and the full path of the file can " + "be specified instead of the list. (See option --tables-file.)", + (uchar*) &opt_ibx_databases, + (uchar*) &opt_ibx_databases, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"kill-long-queries-timeout", OPT_KILL_LONG_QUERIES_TIMEOUT, + "This option specifies the number of seconds innobackupex waits " + "between starting FLUSH TABLES WITH READ LOCK and killing those " + "queries that block it. Default is 0 seconds, which means " + "innobackupex will not attempt to kill any queries.", + (uchar*) &opt_ibx_kill_long_queries_timeout, + (uchar*) &opt_ibx_kill_long_queries_timeout, 0, GET_UINT, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"ftwrl-wait-timeout", OPT_LOCK_WAIT_TIMEOUT, + "This option specifies time in seconds that innobackupex should wait " + "for queries that would block FTWRL before running it. If there are " + "still such queries when the timeout expires, innobackupex terminates " + "with an error. Default is 0, in which case innobackupex does not " + "wait for queries to complete and starts FTWRL immediately.", + (uchar*) &opt_ibx_lock_wait_timeout, + (uchar*) &opt_ibx_lock_wait_timeout, 0, GET_UINT, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"ftwrl-wait-threshold", OPT_LOCK_WAIT_THRESHOLD, + "This option specifies the query run time threshold which is used by " + "innobackupex to detect long-running queries with a non-zero value " + "of --ftwrl-wait-timeout. FTWRL is not started until such " + "long-running queries exist. This option has no effect if " + "--ftwrl-wait-timeout is 0. Default value is 60 seconds.", + (uchar*) &opt_ibx_lock_wait_threshold, + (uchar*) &opt_ibx_lock_wait_threshold, 0, GET_UINT, + REQUIRED_ARG, 60, 0, 0, 0, 0, 0}, + + {"debug-sleep-before-unlock", OPT_DEBUG_SLEEP_BEFORE_UNLOCK, + "This is a debug-only option used by the XtraBackup test suite.", + (uchar*) &opt_ibx_debug_sleep_before_unlock, + (uchar*) &opt_ibx_debug_sleep_before_unlock, 0, GET_UINT, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"safe-slave-backup-timeout", OPT_SAFE_SLAVE_BACKUP_TIMEOUT, + "How many seconds --safe-slave-backup should wait for " + "Slave_open_temp_tables to become zero. (default 300)", + (uchar*) &opt_ibx_safe_slave_backup_timeout, + (uchar*) &opt_ibx_safe_slave_backup_timeout, 0, GET_UINT, + REQUIRED_ARG, 300, 0, 0, 0, 0, 0}, + + + /* Following command-line options are actually handled by xtrabackup. + We put them here with only purpose for them to showup in + innobackupex --help output */ + + {"close_files", OPT_CLOSE_FILES, "Do not keep files opened. This " + "option is passed directly to xtrabackup. Use at your own risk.", + (uchar*) &ibx_xb_close_files, (uchar*) &ibx_xb_close_files, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"compact", OPT_COMPACT, "Create a compact backup with all secondary " + "index pages omitted. This option is passed directly to xtrabackup. " + "See xtrabackup documentation for details.", + (uchar*) &ibx_xtrabackup_compact, (uchar*) &ibx_xtrabackup_compact, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"compress", OPT_COMPRESS, "This option instructs xtrabackup to " + "compress backup copies of InnoDB data files. It is passed directly " + "to the xtrabackup child process. Try 'xtrabackup --help' for more " + "details.", (uchar*) &ibx_xtrabackup_compress_alg, + (uchar*) &ibx_xtrabackup_compress_alg, 0, + GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, + + {"compress-threads", OPT_COMPRESS_THREADS, + "This option specifies the number of worker threads that will be used " + "for parallel compression. It is passed directly to the xtrabackup " + "child process. Try 'xtrabackup --help' for more details.", + (uchar*) &ibx_xtrabackup_compress_threads, + (uchar*) &ibx_xtrabackup_compress_threads, + 0, GET_UINT, REQUIRED_ARG, 1, 1, UINT_MAX, 0, 0, 0}, + + {"compress-chunk-size", OPT_COMPRESS_CHUNK_SIZE, "Size of working " + "buffer(s) for compression threads in bytes. The default value " + "is 64K.", (uchar*) &ibx_xtrabackup_compress_chunk_size, + (uchar*) &ibx_xtrabackup_compress_chunk_size, + 0, GET_ULL, REQUIRED_ARG, (1 << 16), 1024, ULONGLONG_MAX, 0, 0, 0}, + + {"encrypt", OPT_ENCRYPT, "This option instructs xtrabackup to encrypt " + "backup copies of InnoDB data files using the algorithm specified in " + "the ENCRYPTION-ALGORITHM. It is passed directly to the xtrabackup " + "child process. Try 'xtrabackup --help' for more details.", + &ibx_xtrabackup_encrypt_algo, &ibx_xtrabackup_encrypt_algo, + &xtrabackup_encrypt_algo_typelib, GET_ENUM, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"encrypt-key", OPT_ENCRYPT_KEY, "This option instructs xtrabackup to " + "use the given ENCRYPTION-KEY when using the --encrypt or --decrypt " + "options. During backup it is passed directly to the xtrabackup child " + "process. Try 'xtrabackup --help' for more details.", + (uchar*) &ibx_xtrabackup_encrypt_key, + (uchar*) &ibx_xtrabackup_encrypt_key, 0, + GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"encrypt-key-file", OPT_ENCRYPT_KEY_FILE, "This option instructs " + "xtrabackup to use the encryption key stored in the given " + "ENCRYPTION-KEY-FILE when using the --encrypt or --decrypt options.", + (uchar*) &ibx_xtrabackup_encrypt_key_file, + (uchar*) &ibx_xtrabackup_encrypt_key_file, 0, + GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"encrypt-threads", OPT_ENCRYPT_THREADS, + "This option specifies the number of worker threads that will be used " + "for parallel encryption. It is passed directly to the xtrabackup " + "child process. Try 'xtrabackup --help' for more details.", + (uchar*) &ibx_xtrabackup_encrypt_threads, + (uchar*) &ibx_xtrabackup_encrypt_threads, + 0, GET_UINT, REQUIRED_ARG, 1, 1, UINT_MAX, 0, 0, 0}, + + {"encrypt-chunk-size", OPT_ENCRYPT_CHUNK_SIZE, + "This option specifies the size of the internal working buffer for " + "each encryption thread, measured in bytes. It is passed directly to " + "the xtrabackup child process. Try 'xtrabackup --help' for more " + "details.", + (uchar*) &ibx_xtrabackup_encrypt_chunk_size, + (uchar*) &ibx_xtrabackup_encrypt_chunk_size, + 0, GET_ULL, REQUIRED_ARG, (1 << 16), 1024, ULONGLONG_MAX, 0, 0, 0}, + + {"export", OPT_EXPORT, "This option is passed directly to xtrabackup's " + "--export option. It enables exporting individual tables for import " + "into another server. See the xtrabackup documentation for details.", + (uchar*) &ibx_xtrabackup_export, (uchar*) &ibx_xtrabackup_export, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"extra-lsndir", OPT_EXTRA_LSNDIR, "This option specifies the " + "directory in which to save an extra copy of the " + "\"xtrabackup_checkpoints\" file. The option accepts a string " + "argument. It is passed directly to xtrabackup's --extra-lsndir " + "option. See the xtrabackup documentation for details.", + (uchar*) &ibx_xtrabackup_extra_lsndir, + (uchar*) &ibx_xtrabackup_extra_lsndir, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"incremental-basedir", OPT_INCREMENTAL_BASEDIR, "This option " + "specifies the directory containing the full backup that is the base " + "dataset for the incremental backup. The option accepts a string " + "argument. It is used with the --incremental option.", + (uchar*) &ibx_xtrabackup_incremental_basedir, + (uchar*) &ibx_xtrabackup_incremental_basedir, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"incremental-dir", OPT_INCREMENTAL_DIR, "This option specifies the " + "directory where the incremental backup will be combined with the " + "full backup to make a new full backup. The option accepts a string " + "argument. It is used with the --incremental option.", + (uchar*) &ibx_xtrabackup_incremental_dir, + (uchar*) &ibx_xtrabackup_incremental_dir, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"incremental-force-scan", OPT_INCREMENTAL_FORCE_SCAN, + "This options tells xtrabackup to perform full scan of data files " + "for taking an incremental backup even if full changed page bitmap " + "data is available to enable the backup without the full scan.", + (uchar*)&ibx_xtrabackup_incremental_force_scan, + (uchar*)&ibx_xtrabackup_incremental_force_scan, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, + + {"log-copy-interval", OPT_LOG_COPY_INTERVAL, "This option specifies " + "time interval between checks done by log copying thread in " + "milliseconds.", (uchar*) &ibx_xtrabackup_log_copy_interval, + (uchar*) &ibx_xtrabackup_log_copy_interval, + 0, GET_LONG, REQUIRED_ARG, 1000, 0, LONG_MAX, 0, 1, 0}, + + {"incremental-lsn", OPT_INCREMENTAL, "This option specifies the log " + "sequence number (LSN) to use for the incremental backup. The option " + "accepts a string argument. It is used with the --incremental option. " + "It is used instead of specifying --incremental-basedir. For " + "databases created by MySQL and Percona Server 5.0-series versions, " + "specify the LSN as two 32-bit integers in high:low format. For " + "databases created in 5.1 and later, specify the LSN as a single " + "64-bit integer.", + (uchar*) &ibx_xtrabackup_incremental, + (uchar*) &ibx_xtrabackup_incremental, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"parallel", OPT_PARALLEL, "On backup, this option specifies the " + "number of threads the xtrabackup child process should use to back " + "up files concurrently. The option accepts an integer argument. It " + "is passed directly to xtrabackup's --parallel option. See the " + "xtrabackup documentation for details.", + (uchar*) &ibx_xtrabackup_parallel, (uchar*) &ibx_xtrabackup_parallel, + 0, GET_INT, REQUIRED_ARG, 1, 1, INT_MAX, 0, 0, 0}, + + + {"stream", OPT_STREAM, "This option specifies the format in which to " + "do the streamed backup. The option accepts a string argument. The " + "backup will be done to STDOUT in the specified format. Currently, " + "the only supported formats are tar and xbstream. This option is " + "passed directly to xtrabackup's --stream option.", + (uchar*) &ibx_xtrabackup_stream_str, + (uchar*) &ibx_xtrabackup_stream_str, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"tables-file", OPT_TABLES_FILE, "This option specifies the file in " + "which there are a list of names of the form database. The option " + "accepts a string argument.table, one per line. The option is passed " + "directly to xtrabackup's --tables-file option.", + (uchar*) &ibx_xtrabackup_tables_file, + (uchar*) &ibx_xtrabackup_tables_file, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"throttle", OPT_THROTTLE, "This option specifies a number of I/O " + "operations (pairs of read+write) per second. It accepts an integer " + "argument. It is passed directly to xtrabackup's --throttle option.", + (uchar*) &ibx_xtrabackup_throttle, (uchar*) &ibx_xtrabackup_throttle, + 0, GET_LONG, REQUIRED_ARG, 0, 0, LONG_MAX, 0, 1, 0}, + + {"tmpdir", 't', "This option specifies the location where a temporary " + "files will be stored. If the option is not specified, the default is " + "to use the value of tmpdir read from the server configuration.", + (uchar*) &ibx_opt_mysql_tmpdir, + (uchar*) &ibx_opt_mysql_tmpdir, 0, GET_STR, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"use-memory", OPT_USE_MEMORY, "This option accepts a string argument " + "that specifies the amount of memory in bytes for xtrabackup to use " + "for crash recovery while preparing a backup. Multiples are supported " + "providing the unit (e.g. 1MB, 1GB). It is used only with the option " + "--apply-log. It is passed directly to xtrabackup's --use-memory " + "option. See the xtrabackup documentation for details.", + (uchar*) &ibx_xtrabackup_use_memory, + (uchar*) &ibx_xtrabackup_use_memory, + 0, GET_LL, REQUIRED_ARG, 100*1024*1024L, 1024*1024L, LONGLONG_MAX, 0, + 1024*1024L, 0}, + + { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + + +static void usage(void) +{ + puts("Open source backup tool for InnoDB and XtraDB\n\ +\n\ +Copyright (C) 2009-2015 Percona LLC and/or its affiliates.\n\ +Portions Copyright (C) 2000, 2011, MySQL AB & Innobase Oy. All Rights Reserved.\n\ +\n\ +This program is free software; you can redistribute it and/or\n\ +modify it under the terms of the GNU General Public License\n\ +as published by the Free Software Foundation version 2\n\ +of the License.\n\ +\n\ +This program is distributed in the hope that it will be useful,\n\ +but WITHOUT ANY WARRANTY; without even the implied warranty of\n\ +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\ +GNU General Public License for more details.\n\ +\n\ +You can download full text of the license on http://www.gnu.org/licenses/gpl-2.0.txt\n\n"); + + puts("innobackupex - Non-blocking backup tool for InnoDB, XtraDB and HailDB databases\n\ +\n\ +SYNOPOSIS\n\ +\n\ +innobackupex [--compress] [--compress-threads=NUMBER-OF-THREADS] [--compress-chunk-size=CHUNK-SIZE]\n\ + [--encrypt=ENCRYPTION-ALGORITHM] [--encrypt-threads=NUMBER-OF-THREADS] [--encrypt-chunk-size=CHUNK-SIZE]\n\ + [--encrypt-key=LITERAL-ENCRYPTION-KEY] | [--encryption-key-file=MY.KEY]\n\ + [--include=REGEXP] [--user=NAME]\n\ + [--password=WORD] [--port=PORT] [--socket=SOCKET]\n\ + [--no-timestamp] [--ibbackup=IBBACKUP-BINARY]\n\ + [--slave-info] [--galera-info] [--stream=tar|xbstream]\n\ + [--defaults-file=MY.CNF] [--defaults-group=GROUP-NAME]\n\ + [--databases=LIST] [--no-lock] \n\ + [--tmpdir=DIRECTORY] [--tables-file=FILE]\n\ + [--history=NAME]\n\ + [--incremental] [--incremental-basedir]\n\ + [--incremental-dir] [--incremental-force-scan] [--incremental-lsn]\n\ + [--incremental-history-name=NAME] [--incremental-history-uuid=UUID]\n\ + [--close-files] [--compact] \n\ + BACKUP-ROOT-DIR\n\ +\n\ +innobackupex --apply-log [--use-memory=B]\n\ + [--defaults-file=MY.CNF]\n\ + [--export] [--redo-only] [--ibbackup=IBBACKUP-BINARY]\n\ + BACKUP-DIR\n\ +\n\ +innobackupex --copy-back [--defaults-file=MY.CNF] [--defaults-group=GROUP-NAME] BACKUP-DIR\n\ +\n\ +innobackupex --move-back [--defaults-file=MY.CNF] [--defaults-group=GROUP-NAME] BACKUP-DIR\n\ +\n\ +innobackupex [--decompress] [--decrypt=ENCRYPTION-ALGORITHM]\n\ + [--encrypt-key=LITERAL-ENCRYPTION-KEY] | [--encryption-key-file=MY.KEY]\n\ + [--parallel=NUMBER-OF-FORKS] BACKUP-DIR\n\ +\n\ +DESCRIPTION\n\ +\n\ +The first command line above makes a hot backup of a MySQL database.\n\ +By default it creates a backup directory (named by the current date\n\ + and time) in the given backup root directory. With the --no-timestamp\n\ +option it does not create a time-stamped backup directory, but it puts\n\ +the backup in the given directory (which must not exist). This\n\ +command makes a complete backup of all MyISAM and InnoDB tables and\n\ +indexes in all databases or in all of the databases specified with the\n\ +--databases option. The created backup contains .frm, .MRG, .MYD,\n\ +.MYI, .MAD, .MAI, .TRG, .TRN, .ARM, .ARZ, .CSM, CSV, .opt, .par, and\n\ +InnoDB data and log files. The MY.CNF options file defines the\n\ +location of the database. This command connects to the MySQL server\n\ +using the mysql client program, and runs xtrabackup as a child\n\ +process.\n\ +\n\ +The --apply-log command prepares a backup for starting a MySQL\n\ +server on the backup. This command recovers InnoDB data files as specified\n\ +in BACKUP-DIR/backup-my.cnf using BACKUP-DIR/xtrabackup_logfile,\n\ +and creates new InnoDB log files as specified in BACKUP-DIR/backup-my.cnf.\n\ +The BACKUP-DIR should be the path to a backup directory created by\n\ +xtrabackup. This command runs xtrabackup as a child process, but it does not \n\ +connect to the database server.\n\ +\n\ +The --copy-back command copies data, index, and log files\n\ +from the backup directory back to their original locations.\n\ +The MY.CNF options file defines the original location of the database.\n\ +The BACKUP-DIR is the path to a backup directory created by xtrabackup.\n\ +\n\ +The --move-back command is similar to --copy-back with the only difference that\n\ +it moves files to their original locations rather than copies them. As this\n\ +option removes backup files, it must be used with caution. It may be useful in\n\ +cases when there is not enough free disk space to copy files.\n\ +\n\ +The --decompress --decrypt command will decrypt and/or decompress a backup made\n\ +with the --compress and/or --encrypt options. When decrypting, the encryption\n\ +algorithm and key used when the backup was taken MUST be provided via the\n\ +specified options. --decrypt and --decompress may be used together at the same\n\ +time to completely normalize a previously compressed and encrypted backup. The\n\ +--parallel option will allow multiple files to be decrypted and/or decompressed\n\ +simultaneously. In order to decompress, the qpress utility MUST be installed\n\ +and accessable within the path. This process will remove the original\n\ +compressed/encrypted files and leave the results in the same location.\n\ +\n\ +On success the exit code innobackupex is 0. A non-zero exit code \n\ +indicates an error.\n"); + printf("Usage: [%s [--defaults-file=#] --backup | %s [--defaults-file=#] --prepare] [OPTIONS]\n", my_progname, my_progname); + my_print_help(ibx_long_options); +} + + +static +my_bool +ibx_get_one_option(int optid, + const struct my_option *opt __attribute__((unused)), + char *argument) +{ + switch(optid) { + case '?': + usage(); + exit(0); + break; + case 'v': + msg("innobackupex version %s %s (%s)\n", + MYSQL_SERVER_VERSION, + SYSTEM_TYPE, MACHINE_TYPE); + exit(0); + break; + case OPT_HISTORY: + if (argument) { + opt_ibx_history = argument; + } else { + opt_ibx_history = ""; + } + break; + case OPT_DECRYPT: + if (argument == NULL) { + ibx_msg("Missing --decrypt argument, must specify a " + "valid encryption algorithm.\n"); + return(1); + } + opt_ibx_decrypt = true; + break; + case OPT_STREAM: + if (!strcasecmp(argument, "tar")) + xtrabackup_stream_fmt = XB_STREAM_FMT_TAR; + else if (!strcasecmp(argument, "xbstream")) + xtrabackup_stream_fmt = XB_STREAM_FMT_XBSTREAM; + else { + ibx_msg("Invalid --stream argument: %s\n", argument); + return 1; + } + xtrabackup_stream = TRUE; + break; + case OPT_COMPRESS: + if (argument == NULL) + xtrabackup_compress_alg = "quicklz"; + else if (strcasecmp(argument, "quicklz")) + { + ibx_msg("Invalid --compress argument: %s\n", argument); + return 1; + } + xtrabackup_compress = TRUE; + break; + case OPT_ENCRYPT: + if (argument == NULL) + { + msg("Missing --encrypt argument, must specify a " + "valid encryption algorithm.\n"); + return 1; + } + xtrabackup_encrypt = TRUE; + break; + case 'p': + if (argument) + { + char *start = argument; + my_free(opt_ibx_password); + opt_ibx_password= my_strdup(argument, MYF(MY_FAE)); + /* Destroy argument */ + while (*argument) + *argument++= 'x'; + if (*start) + start[1]=0 ; + } + break; + } + return(0); +} + +bool +make_backup_dir() +{ + time_t t = time(NULL); + char buf[100]; + + if (!opt_ibx_notimestamp && !ibx_xtrabackup_stream_str) { + strftime(buf, sizeof(buf), "%Y-%m-%d_%H-%M-%S", localtime(&t)); + ut_a(asprintf(&ibx_backup_directory, "%s/%s", + ibx_position_arg, buf) != -1); + } else { + ibx_backup_directory = strdup(ibx_position_arg); + } + + if (!directory_exists(ibx_backup_directory, true)) { + return(false); + } + + return(true); +} + +bool +ibx_handle_options(int *argc, char ***argv) +{ + int i, n_arguments; + + if (handle_options(argc, argv, ibx_long_options, ibx_get_one_option)) { + return(false); + } + + if (opt_ibx_apply_log) { + ibx_mode = IBX_MODE_APPLY_LOG; + } else if (opt_ibx_copy_back) { + ibx_mode = IBX_MODE_COPY_BACK; + } else if (opt_ibx_move_back) { + ibx_mode = IBX_MODE_MOVE_BACK; + } else if (opt_ibx_decrypt || opt_ibx_decompress) { + ibx_mode = IBX_MODE_DECRYPT_DECOMPRESS; + } else { + ibx_mode = IBX_MODE_BACKUP; + } + + /* find and save position argument */ + i = 0; + n_arguments = 0; + while (i < *argc) { + char *opt = (*argv)[i]; + + if (strncmp(opt, "--", 2) != 0 + && !(strlen(opt) == 2 && opt[0] == '-')) { + if (ibx_position_arg != NULL + && ibx_position_arg != opt) { + ibx_msg("Error: extra argument found %s\n", + opt); + } + ibx_position_arg = opt; + ++n_arguments; + } + ++i; + } + + *argc -= n_arguments; + if (n_arguments > 1) { + return(false); + } + + if (ibx_position_arg == NULL) { + ibx_msg("Missing argument\n"); + return(false); + } + + /* set argv[0] to be the program name */ + --(*argv); + ++(*argc); + + return(true); +} + +/*********************************************************************//** +Parse command-line options, connect to MySQL server, +detect server capabilities, etc. +@return true on success. */ +bool +ibx_init() +{ + const char *run; + + /*=====================*/ + xtrabackup_copy_back = opt_ibx_copy_back; + xtrabackup_move_back = opt_ibx_move_back; + opt_galera_info = opt_ibx_galera_info; + opt_slave_info = opt_ibx_slave_info; + opt_no_lock = opt_ibx_no_lock; + opt_safe_slave_backup = opt_ibx_safe_slave_backup; + opt_rsync = opt_ibx_rsync; + opt_force_non_empty_dirs = opt_ibx_force_non_empty_dirs; + opt_noversioncheck = opt_ibx_noversioncheck; + opt_no_backup_locks = opt_ibx_no_backup_locks; + opt_decompress = opt_ibx_decompress; + + opt_incremental_history_name = opt_ibx_incremental_history_name; + opt_incremental_history_uuid = opt_ibx_incremental_history_uuid; + + opt_user = opt_ibx_user; + opt_password = opt_ibx_password; + opt_host = opt_ibx_host; + opt_defaults_group = opt_ibx_defaults_group; + opt_socket = opt_ibx_socket; + opt_port = opt_ibx_port; + opt_login_path = opt_ibx_login_path; + + opt_lock_wait_query_type = opt_ibx_lock_wait_query_type; + opt_kill_long_query_type = opt_ibx_kill_long_query_type; + + opt_decrypt_algo = opt_ibx_decrypt_algo; + + opt_kill_long_queries_timeout = opt_ibx_kill_long_queries_timeout; + opt_lock_wait_timeout = opt_ibx_lock_wait_timeout; + opt_lock_wait_threshold = opt_ibx_lock_wait_threshold; + opt_debug_sleep_before_unlock = opt_ibx_debug_sleep_before_unlock; + opt_safe_slave_backup_timeout = opt_ibx_safe_slave_backup_timeout; + + opt_history = opt_ibx_history; + opt_decrypt = opt_ibx_decrypt; + + /* setup xtrabackup options */ + xb_close_files = ibx_xb_close_files; + xtrabackup_compress_alg = ibx_xtrabackup_compress_alg; + xtrabackup_compress_threads = ibx_xtrabackup_compress_threads; + xtrabackup_compress_chunk_size = ibx_xtrabackup_compress_chunk_size; + xtrabackup_encrypt_algo = ibx_xtrabackup_encrypt_algo; + xtrabackup_encrypt_key = ibx_xtrabackup_encrypt_key; + xtrabackup_encrypt_key_file = ibx_xtrabackup_encrypt_key_file; + xtrabackup_encrypt_threads = ibx_xtrabackup_encrypt_threads; + xtrabackup_encrypt_chunk_size = ibx_xtrabackup_encrypt_chunk_size; + xtrabackup_export = ibx_xtrabackup_export; + xtrabackup_extra_lsndir = ibx_xtrabackup_extra_lsndir; + xtrabackup_incremental_basedir = ibx_xtrabackup_incremental_basedir; + xtrabackup_incremental_dir = ibx_xtrabackup_incremental_dir; + xtrabackup_incremental_force_scan = + ibx_xtrabackup_incremental_force_scan; + xtrabackup_log_copy_interval = ibx_xtrabackup_log_copy_interval; + xtrabackup_incremental = ibx_xtrabackup_incremental; + xtrabackup_parallel = ibx_xtrabackup_parallel; + xtrabackup_stream_str = ibx_xtrabackup_stream_str; + xtrabackup_tables_file = ibx_xtrabackup_tables_file; + xtrabackup_throttle = ibx_xtrabackup_throttle; + opt_mysql_tmpdir = ibx_opt_mysql_tmpdir; + xtrabackup_use_memory = ibx_xtrabackup_use_memory; + + if (!opt_ibx_incremental + && (xtrabackup_incremental + || xtrabackup_incremental_basedir + || opt_ibx_incremental_history_name + || opt_ibx_incremental_history_uuid)) { + ibx_msg("Error: --incremental-lsn, --incremental-basedir, " + "--incremental-history-name and " + "--incremental-history-uuid require the " + "--incremental option.\n"); + return(false); + } + + if (opt_ibx_databases != NULL) { + if (is_path_separator(*opt_ibx_databases)) { + xtrabackup_databases_file = opt_ibx_databases; + } else { + xtrabackup_databases = opt_ibx_databases; + } + } + + /* --tables and --tables-file options are xtrabackup only */ + ibx_partial_backup = (opt_ibx_include || opt_ibx_databases); + + if (ibx_mode == IBX_MODE_BACKUP) { + + if (!make_backup_dir()) { + return(false); + } + } + + /* --binlog-info is xtrabackup only, so force + --binlog-info=ON. i.e. behavior before the feature had been + implemented */ + opt_binlog_info = BINLOG_INFO_ON; + + switch (ibx_mode) { + case IBX_MODE_APPLY_LOG: + xtrabackup_prepare = TRUE; + if (opt_ibx_redo_only) { + xtrabackup_apply_log_only = TRUE; + } + xtrabackup_target_dir = ibx_position_arg; + run = "apply-log"; + break; + case IBX_MODE_BACKUP: + xtrabackup_backup = TRUE; + xtrabackup_target_dir = ibx_backup_directory; + if (opt_ibx_include != NULL) { + xtrabackup_tables = opt_ibx_include; + } + run = "backup"; + break; + case IBX_MODE_COPY_BACK: + xtrabackup_copy_back = TRUE; + xtrabackup_target_dir = ibx_position_arg; + run = "copy-back"; + break; + case IBX_MODE_MOVE_BACK: + xtrabackup_move_back = TRUE; + xtrabackup_target_dir = ibx_position_arg; + run = "move-back"; + break; + case IBX_MODE_DECRYPT_DECOMPRESS: + xtrabackup_decrypt_decompress = TRUE; + xtrabackup_target_dir = ibx_position_arg; + run = "decrypt and decompress"; + break; + default: + ut_error; + } + + ibx_msg("Starting the %s operation\n\n" + "IMPORTANT: Please check that the %s run completes " + "successfully.\n" + " At the end of a successful %s run innobackupex\n" + " prints \"completed OK!\".\n\n", run, run, run); + + + return(true); +} + +void +ibx_cleanup() +{ + free(ibx_backup_directory); +} diff --git a/extra/mariabackup/innobackupex.h b/extra/mariabackup/innobackupex.h new file mode 100644 index 00000000000..e2ad9bd2511 --- /dev/null +++ b/extra/mariabackup/innobackupex.h @@ -0,0 +1,45 @@ +/****************************************************** +Copyright (c) 2011-2014 Percona LLC and/or its affiliates. + +Declarations for innobackupex.cc + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#ifndef INNOBACKUPEX_H +#define INNOBACKUPEX_H + +#define INNOBACKUPEX_BIN_NAME "innobackupex" + +enum ibx_mode_t { + IBX_MODE_BACKUP, + IBX_MODE_APPLY_LOG, + IBX_MODE_COPY_BACK, + IBX_MODE_MOVE_BACK, + IBX_MODE_DECRYPT_DECOMPRESS +}; + +extern ibx_mode_t ibx_mode; + +bool +ibx_handle_options(int *argc, char ***argv); + +bool +ibx_init(); + +void +ibx_cleanup(); + +#endif diff --git a/extra/mariabackup/quicklz/quicklz.c b/extra/mariabackup/quicklz/quicklz.c new file mode 100644 index 00000000000..3742129023a --- /dev/null +++ b/extra/mariabackup/quicklz/quicklz.c @@ -0,0 +1,848 @@ +// Fast data compression library +// Copyright (C) 2006-2011 Lasse Mikkel Reinhold +// lar@quicklz.com +// +// QuickLZ can be used for free under the GPL 1, 2 or 3 license (where anything +// released into public must be open source) or under a commercial license if such +// has been acquired (see http://www.quicklz.com/order.html). The commercial license +// does not cover derived or ported versions created by third parties under GPL. + +// 1.5.0 final + +#include "quicklz.h" + +#if QLZ_VERSION_MAJOR != 1 || QLZ_VERSION_MINOR != 5 || QLZ_VERSION_REVISION != 0 + #error quicklz.c and quicklz.h have different versions +#endif + +#if (defined(__X86__) || defined(__i386__) || defined(i386) || defined(_M_IX86) || defined(__386__) || defined(__x86_64__) || defined(_M_X64)) + #define X86X64 +#endif + +#define MINOFFSET 2 +#define UNCONDITIONAL_MATCHLEN 6 +#define UNCOMPRESSED_END 4 +#define CWORD_LEN 4 + +#if QLZ_COMPRESSION_LEVEL == 1 && defined QLZ_PTR_64 && QLZ_STREAMING_BUFFER == 0 + #define OFFSET_BASE source + #define CAST (ui32)(size_t) +#else + #define OFFSET_BASE 0 + #define CAST +#endif + +int qlz_get_setting(int setting) +{ + switch (setting) + { + case 0: return QLZ_COMPRESSION_LEVEL; + case 1: return sizeof(qlz_state_compress); + case 2: return sizeof(qlz_state_decompress); + case 3: return QLZ_STREAMING_BUFFER; +#ifdef QLZ_MEMORY_SAFE + case 6: return 1; +#else + case 6: return 0; +#endif + case 7: return QLZ_VERSION_MAJOR; + case 8: return QLZ_VERSION_MINOR; + case 9: return QLZ_VERSION_REVISION; + } + return -1; +} + +#if QLZ_COMPRESSION_LEVEL == 1 +static int same(const unsigned char *src, size_t n) +{ + while(n > 0 && *(src + n) == *src) + n--; + return n == 0 ? 1 : 0; +} +#endif + +static void reset_table_compress(qlz_state_compress *state) +{ + int i; + for(i = 0; i < QLZ_HASH_VALUES; i++) + { +#if QLZ_COMPRESSION_LEVEL == 1 + state->hash[i].offset = 0; +#else + state->hash_counter[i] = 0; +#endif + } +} + +static void reset_table_decompress(qlz_state_decompress *state) +{ + int i; + (void)state; + (void)i; +#if QLZ_COMPRESSION_LEVEL == 2 + for(i = 0; i < QLZ_HASH_VALUES; i++) + { + state->hash_counter[i] = 0; + } +#endif +} + +static __inline ui32 hash_func(ui32 i) +{ +#if QLZ_COMPRESSION_LEVEL == 2 + return ((i >> 9) ^ (i >> 13) ^ i) & (QLZ_HASH_VALUES - 1); +#else + return ((i >> 12) ^ i) & (QLZ_HASH_VALUES - 1); +#endif +} + +static __inline ui32 fast_read(void const *src, ui32 bytes) +{ +#ifndef X86X64 + unsigned char *p = (unsigned char*)src; + switch (bytes) + { + case 4: + return(*p | *(p + 1) << 8 | *(p + 2) << 16 | *(p + 3) << 24); + case 3: + return(*p | *(p + 1) << 8 | *(p + 2) << 16); + case 2: + return(*p | *(p + 1) << 8); + case 1: + return(*p); + } + return 0; +#else + if (bytes >= 1 && bytes <= 4) + return *((ui32*)src); + else + return 0; +#endif +} + +static __inline ui32 hashat(const unsigned char *src) +{ + ui32 fetch, hash; + fetch = fast_read(src, 3); + hash = hash_func(fetch); + return hash; +} + +static __inline void fast_write(ui32 f, void *dst, size_t bytes) +{ +#ifndef X86X64 + unsigned char *p = (unsigned char*)dst; + + switch (bytes) + { + case 4: + *p = (unsigned char)f; + *(p + 1) = (unsigned char)(f >> 8); + *(p + 2) = (unsigned char)(f >> 16); + *(p + 3) = (unsigned char)(f >> 24); + return; + case 3: + *p = (unsigned char)f; + *(p + 1) = (unsigned char)(f >> 8); + *(p + 2) = (unsigned char)(f >> 16); + return; + case 2: + *p = (unsigned char)f; + *(p + 1) = (unsigned char)(f >> 8); + return; + case 1: + *p = (unsigned char)f; + return; + } +#else + switch (bytes) + { + case 4: + *((ui32*)dst) = f; + return; + case 3: + *((ui32*)dst) = f; + return; + case 2: + *((ui16 *)dst) = (ui16)f; + return; + case 1: + *((unsigned char*)dst) = (unsigned char)f; + return; + } +#endif +} + + +size_t qlz_size_decompressed(const char *source) +{ + ui32 n, r; + n = (((*source) & 2) == 2) ? 4 : 1; + r = fast_read(source + 1 + n, n); + r = r & (0xffffffff >> ((4 - n)*8)); + return r; +} + +size_t qlz_size_compressed(const char *source) +{ + ui32 n, r; + n = (((*source) & 2) == 2) ? 4 : 1; + r = fast_read(source + 1, n); + r = r & (0xffffffff >> ((4 - n)*8)); + return r; +} + +size_t qlz_size_header(const char *source) +{ + size_t n = 2*((((*source) & 2) == 2) ? 4 : 1) + 1; + return n; +} + + +static __inline void memcpy_up(unsigned char *dst, const unsigned char *src, ui32 n) +{ + // Caution if modifying memcpy_up! Overlap of dst and src must be special handled. +#ifndef X86X64 + unsigned char *end = dst + n; + while(dst < end) + { + *dst = *src; + dst++; + src++; + } +#else + ui32 f = 0; + do + { + *(ui32 *)(dst + f) = *(ui32 *)(src + f); + f += MINOFFSET + 1; + } + while (f < n); +#endif +} + +static __inline void update_hash(qlz_state_decompress *state, const unsigned char *s) +{ +#if QLZ_COMPRESSION_LEVEL == 1 + ui32 hash; + hash = hashat(s); + state->hash[hash].offset = s; + state->hash_counter[hash] = 1; +#elif QLZ_COMPRESSION_LEVEL == 2 + ui32 hash; + unsigned char c; + hash = hashat(s); + c = state->hash_counter[hash]; + state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = s; + c++; + state->hash_counter[hash] = c; +#endif + (void)state; + (void)s; +} + +#if QLZ_COMPRESSION_LEVEL <= 2 +static void update_hash_upto(qlz_state_decompress *state, unsigned char **lh, const unsigned char *max) +{ + while(*lh < max) + { + (*lh)++; + update_hash(state, *lh); + } +} +#endif + +static size_t qlz_compress_core(const unsigned char *source, unsigned char *destination, size_t size, qlz_state_compress *state) +{ + const unsigned char *last_byte = source + size - 1; + const unsigned char *src = source; + unsigned char *cword_ptr = destination; + unsigned char *dst = destination + CWORD_LEN; + ui32 cword_val = 1U << 31; + const unsigned char *last_matchstart = last_byte - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END; + ui32 fetch = 0; + unsigned int lits = 0; + + (void) lits; + + if(src <= last_matchstart) + fetch = fast_read(src, 3); + + while(src <= last_matchstart) + { + if ((cword_val & 1) == 1) + { + // store uncompressed if compression ratio is too low + if (src > source + (size >> 1) && dst - destination > src - source - ((src - source) >> 5)) + return 0; + + fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN); + + cword_ptr = dst; + dst += CWORD_LEN; + cword_val = 1U << 31; + fetch = fast_read(src, 3); + } +#if QLZ_COMPRESSION_LEVEL == 1 + { + const unsigned char *o; + ui32 hash, cached; + + hash = hash_func(fetch); + cached = fetch ^ state->hash[hash].cache; + state->hash[hash].cache = fetch; + + o = state->hash[hash].offset + OFFSET_BASE; + state->hash[hash].offset = CAST(src - OFFSET_BASE); + +#ifdef X86X64 + if ((cached & 0xffffff) == 0 && o != OFFSET_BASE && (src - o > MINOFFSET || (src == o + 1 && lits >= 3 && src > source + 3 && same(src - 3, 6)))) + { + if(cached != 0) + { +#else + if (cached == 0 && o != OFFSET_BASE && (src - o > MINOFFSET || (src == o + 1 && lits >= 3 && src > source + 3 && same(src - 3, 6)))) + { + if (*(o + 3) != *(src + 3)) + { +#endif + hash <<= 4; + cword_val = (cword_val >> 1) | (1U << 31); + fast_write((3 - 2) | hash, dst, 2); + src += 3; + dst += 2; + } + else + { + const unsigned char *old_src = src; + size_t matchlen; + hash <<= 4; + + cword_val = (cword_val >> 1) | (1U << 31); + src += 4; + + if(*(o + (src - old_src)) == *src) + { + src++; + if(*(o + (src - old_src)) == *src) + { + size_t q = last_byte - UNCOMPRESSED_END - (src - 5) + 1; + size_t remaining = q > 255 ? 255 : q; + src++; + while(*(o + (src - old_src)) == *src && (size_t)(src - old_src) < remaining) + src++; + } + } + + matchlen = src - old_src; + if (matchlen < 18) + { + fast_write((ui32)(matchlen - 2) | hash, dst, 2); + dst += 2; + } + else + { + fast_write((ui32)(matchlen << 16) | hash, dst, 3); + dst += 3; + } + } + fetch = fast_read(src, 3); + lits = 0; + } + else + { + lits++; + *dst = *src; + src++; + dst++; + cword_val = (cword_val >> 1); +#ifdef X86X64 + fetch = fast_read(src, 3); +#else + fetch = (fetch >> 8 & 0xffff) | (*(src + 2) << 16); +#endif + } + } +#elif QLZ_COMPRESSION_LEVEL >= 2 + { + const unsigned char *o, *offset2; + ui32 hash, matchlen, k, m, best_k = 0; + unsigned char c; + size_t remaining = (last_byte - UNCOMPRESSED_END - src + 1) > 255 ? 255 : (last_byte - UNCOMPRESSED_END - src + 1); + (void)best_k; + + + //hash = hashat(src); + fetch = fast_read(src, 3); + hash = hash_func(fetch); + + c = state->hash_counter[hash]; + + offset2 = state->hash[hash].offset[0]; + if(offset2 < src - MINOFFSET && c > 0 && ((fast_read(offset2, 3) ^ fetch) & 0xffffff) == 0) + { + matchlen = 3; + if(*(offset2 + matchlen) == *(src + matchlen)) + { + matchlen = 4; + while(*(offset2 + matchlen) == *(src + matchlen) && matchlen < remaining) + matchlen++; + } + } + else + matchlen = 0; + for(k = 1; k < QLZ_POINTERS && c > k; k++) + { + o = state->hash[hash].offset[k]; +#if QLZ_COMPRESSION_LEVEL == 3 + if(((fast_read(o, 3) ^ fetch) & 0xffffff) == 0 && o < src - MINOFFSET) +#elif QLZ_COMPRESSION_LEVEL == 2 + if(*(src + matchlen) == *(o + matchlen) && ((fast_read(o, 3) ^ fetch) & 0xffffff) == 0 && o < src - MINOFFSET) +#endif + { + m = 3; + while(*(o + m) == *(src + m) && m < remaining) + m++; +#if QLZ_COMPRESSION_LEVEL == 3 + if ((m > matchlen) || (m == matchlen && o > offset2)) +#elif QLZ_COMPRESSION_LEVEL == 2 + if (m > matchlen) +#endif + { + offset2 = o; + matchlen = m; + best_k = k; + } + } + } + o = offset2; + state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src; + c++; + state->hash_counter[hash] = c; + +#if QLZ_COMPRESSION_LEVEL == 3 + if(matchlen > 2 && src - o < 131071) + { + ui32 u; + size_t offset = src - o; + + for(u = 1; u < matchlen; u++) + { + hash = hashat(src + u); + c = state->hash_counter[hash]++; + state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src + u; + } + + cword_val = (cword_val >> 1) | (1U << 31); + src += matchlen; + + if(matchlen == 3 && offset <= 63) + { + *dst = (unsigned char)(offset << 2); + dst++; + } + else if (matchlen == 3 && offset <= 16383) + { + ui32 f = (ui32)((offset << 2) | 1); + fast_write(f, dst, 2); + dst += 2; + } + else if (matchlen <= 18 && offset <= 1023) + { + ui32 f = ((matchlen - 3) << 2) | ((ui32)offset << 6) | 2; + fast_write(f, dst, 2); + dst += 2; + } + + else if(matchlen <= 33) + { + ui32 f = ((matchlen - 2) << 2) | ((ui32)offset << 7) | 3; + fast_write(f, dst, 3); + dst += 3; + } + else + { + ui32 f = ((matchlen - 3) << 7) | ((ui32)offset << 15) | 3; + fast_write(f, dst, 4); + dst += 4; + } + } + else + { + *dst = *src; + src++; + dst++; + cword_val = (cword_val >> 1); + } +#elif QLZ_COMPRESSION_LEVEL == 2 + + if(matchlen > 2) + { + cword_val = (cword_val >> 1) | (1U << 31); + src += matchlen; + + if (matchlen < 10) + { + ui32 f = best_k | ((matchlen - 2) << 2) | (hash << 5); + fast_write(f, dst, 2); + dst += 2; + } + else + { + ui32 f = best_k | (matchlen << 16) | (hash << 5); + fast_write(f, dst, 3); + dst += 3; + } + } + else + { + *dst = *src; + src++; + dst++; + cword_val = (cword_val >> 1); + } +#endif + } +#endif + } + while (src <= last_byte) + { + if ((cword_val & 1) == 1) + { + fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN); + cword_ptr = dst; + dst += CWORD_LEN; + cword_val = 1U << 31; + } +#if QLZ_COMPRESSION_LEVEL < 3 + if (src <= last_byte - 3) + { +#if QLZ_COMPRESSION_LEVEL == 1 + ui32 hash, fetch; + fetch = fast_read(src, 3); + hash = hash_func(fetch); + state->hash[hash].offset = CAST(src - OFFSET_BASE); + state->hash[hash].cache = fetch; +#elif QLZ_COMPRESSION_LEVEL == 2 + ui32 hash; + unsigned char c; + hash = hashat(src); + c = state->hash_counter[hash]; + state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src; + c++; + state->hash_counter[hash] = c; +#endif + } +#endif + *dst = *src; + src++; + dst++; + cword_val = (cword_val >> 1); + } + + while((cword_val & 1) != 1) + cword_val = (cword_val >> 1); + + fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN); + + // min. size must be 9 bytes so that the qlz_size functions can take 9 bytes as argument + return dst - destination < 9 ? 9 : dst - destination; +} + +static size_t qlz_decompress_core(const unsigned char *source, unsigned char *destination, size_t size, qlz_state_decompress *state, const unsigned char *history) +{ + const unsigned char *src = source + qlz_size_header((const char *)source); + unsigned char *dst = destination; + const unsigned char *last_destination_byte = destination + size - 1; + ui32 cword_val = 1; + const unsigned char *last_matchstart = last_destination_byte - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END; + unsigned char *last_hashed = destination - 1; + const unsigned char *last_source_byte = source + qlz_size_compressed((const char *)source) - 1; + static const ui32 bitlut[16] = {4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0}; + + (void) last_source_byte; + (void) last_hashed; + (void) state; + (void) history; + + for(;;) + { + ui32 fetch; + + if (cword_val == 1) + { +#ifdef QLZ_MEMORY_SAFE + if(src + CWORD_LEN - 1 > last_source_byte) + return 0; +#endif + cword_val = fast_read(src, CWORD_LEN); + src += CWORD_LEN; + } + +#ifdef QLZ_MEMORY_SAFE + if(src + 4 - 1 > last_source_byte) + return 0; +#endif + + fetch = fast_read(src, 4); + + if ((cword_val & 1) == 1) + { + ui32 matchlen; + const unsigned char *offset2; + +#if QLZ_COMPRESSION_LEVEL == 1 + ui32 hash; + cword_val = cword_val >> 1; + hash = (fetch >> 4) & 0xfff; + offset2 = (const unsigned char *)(size_t)state->hash[hash].offset; + + if((fetch & 0xf) != 0) + { + matchlen = (fetch & 0xf) + 2; + src += 2; + } + else + { + matchlen = *(src + 2); + src += 3; + } + +#elif QLZ_COMPRESSION_LEVEL == 2 + ui32 hash; + unsigned char c; + cword_val = cword_val >> 1; + hash = (fetch >> 5) & 0x7ff; + c = (unsigned char)(fetch & 0x3); + offset2 = state->hash[hash].offset[c]; + + if((fetch & (28)) != 0) + { + matchlen = ((fetch >> 2) & 0x7) + 2; + src += 2; + } + else + { + matchlen = *(src + 2); + src += 3; + } + +#elif QLZ_COMPRESSION_LEVEL == 3 + ui32 offset; + cword_val = cword_val >> 1; + if ((fetch & 3) == 0) + { + offset = (fetch & 0xff) >> 2; + matchlen = 3; + src++; + } + else if ((fetch & 2) == 0) + { + offset = (fetch & 0xffff) >> 2; + matchlen = 3; + src += 2; + } + else if ((fetch & 1) == 0) + { + offset = (fetch & 0xffff) >> 6; + matchlen = ((fetch >> 2) & 15) + 3; + src += 2; + } + else if ((fetch & 127) != 3) + { + offset = (fetch >> 7) & 0x1ffff; + matchlen = ((fetch >> 2) & 0x1f) + 2; + src += 3; + } + else + { + offset = (fetch >> 15); + matchlen = ((fetch >> 7) & 255) + 3; + src += 4; + } + + offset2 = dst - offset; +#endif + +#ifdef QLZ_MEMORY_SAFE + if(offset2 < history || offset2 > dst - MINOFFSET - 1) + return 0; + + if(matchlen > (ui32)(last_destination_byte - dst - UNCOMPRESSED_END + 1)) + return 0; +#endif + + memcpy_up(dst, offset2, matchlen); + dst += matchlen; + +#if QLZ_COMPRESSION_LEVEL <= 2 + update_hash_upto(state, &last_hashed, dst - matchlen); + last_hashed = dst - 1; +#endif + } + else + { + if (dst < last_matchstart) + { + unsigned int n = bitlut[cword_val & 0xf]; +#ifdef X86X64 + *(ui32 *)dst = *(ui32 *)src; +#else + memcpy_up(dst, src, 4); +#endif + cword_val = cword_val >> n; + dst += n; + src += n; +#if QLZ_COMPRESSION_LEVEL <= 2 + update_hash_upto(state, &last_hashed, dst - 3); +#endif + } + else + { + while(dst <= last_destination_byte) + { + if (cword_val == 1) + { + src += CWORD_LEN; + cword_val = 1U << 31; + } +#ifdef QLZ_MEMORY_SAFE + if(src >= last_source_byte + 1) + return 0; +#endif + *dst = *src; + dst++; + src++; + cword_val = cword_val >> 1; + } + +#if QLZ_COMPRESSION_LEVEL <= 2 + update_hash_upto(state, &last_hashed, last_destination_byte - 3); // todo, use constant +#endif + return size; + } + + } + } +} + +size_t qlz_compress(const void *source, char *destination, size_t size, qlz_state_compress *state) +{ + size_t r; + ui32 compressed; + size_t base; + + if(size == 0 || size > 0xffffffff - 400) + return 0; + + if(size < 216) + base = 3; + else + base = 9; + +#if QLZ_STREAMING_BUFFER > 0 + if (state->stream_counter + size - 1 >= QLZ_STREAMING_BUFFER) +#endif + { + reset_table_compress(state); + r = base + qlz_compress_core((const unsigned char *)source, (unsigned char*)destination + base, size, state); +#if QLZ_STREAMING_BUFFER > 0 + reset_table_compress(state); +#endif + if(r == base) + { + memcpy(destination + base, source, size); + r = size + base; + compressed = 0; + } + else + { + compressed = 1; + } + state->stream_counter = 0; + } +#if QLZ_STREAMING_BUFFER > 0 + else + { + unsigned char *src = state->stream_buffer + state->stream_counter; + + memcpy(src, source, size); + r = base + qlz_compress_core(src, (unsigned char*)destination + base, size, state); + + if(r == base) + { + memcpy(destination + base, src, size); + r = size + base; + compressed = 0; + reset_table_compress(state); + } + else + { + compressed = 1; + } + state->stream_counter += size; + } +#endif + if(base == 3) + { + *destination = (unsigned char)(0 | compressed); + *(destination + 1) = (unsigned char)r; + *(destination + 2) = (unsigned char)size; + } + else + { + *destination = (unsigned char)(2 | compressed); + fast_write((ui32)r, destination + 1, 4); + fast_write((ui32)size, destination + 5, 4); + } + + *destination |= (QLZ_COMPRESSION_LEVEL << 2); + *destination |= (1 << 6); + *destination |= ((QLZ_STREAMING_BUFFER == 0 ? 0 : (QLZ_STREAMING_BUFFER == 100000 ? 1 : (QLZ_STREAMING_BUFFER == 1000000 ? 2 : 3))) << 4); + +// 76543210 +// 01SSLLHC + + return r; +} + +size_t qlz_decompress(const char *source, void *destination, qlz_state_decompress *state) +{ + size_t dsiz = qlz_size_decompressed(source); + +#if QLZ_STREAMING_BUFFER > 0 + if (state->stream_counter + qlz_size_decompressed(source) - 1 >= QLZ_STREAMING_BUFFER) +#endif + { + if((*source & 1) == 1) + { + reset_table_decompress(state); + dsiz = qlz_decompress_core((const unsigned char *)source, (unsigned char *)destination, dsiz, state, (const unsigned char *)destination); + } + else + { + memcpy(destination, source + qlz_size_header(source), dsiz); + } + state->stream_counter = 0; + reset_table_decompress(state); + } +#if QLZ_STREAMING_BUFFER > 0 + else + { + unsigned char *dst = state->stream_buffer + state->stream_counter; + if((*source & 1) == 1) + { + dsiz = qlz_decompress_core((const unsigned char *)source, dst, dsiz, state, (const unsigned char *)state->stream_buffer); + } + else + { + memcpy(dst, source + qlz_size_header(source), dsiz); + reset_table_decompress(state); + } + memcpy(destination, dst, dsiz); + state->stream_counter += dsiz; + } +#endif + return dsiz; +} + diff --git a/extra/mariabackup/quicklz/quicklz.h b/extra/mariabackup/quicklz/quicklz.h new file mode 100644 index 00000000000..6ffe00f3a91 --- /dev/null +++ b/extra/mariabackup/quicklz/quicklz.h @@ -0,0 +1,144 @@ +#ifndef QLZ_HEADER +#define QLZ_HEADER + +// Fast data compression library +// Copyright (C) 2006-2011 Lasse Mikkel Reinhold +// lar@quicklz.com +// +// QuickLZ can be used for free under the GPL 1, 2 or 3 license (where anything +// released into public must be open source) or under a commercial license if such +// has been acquired (see http://www.quicklz.com/order.html). The commercial license +// does not cover derived or ported versions created by third parties under GPL. + +// You can edit following user settings. Data must be decompressed with the same +// setting of QLZ_COMPRESSION_LEVEL and QLZ_STREAMING_BUFFER as it was compressed +// (see manual). If QLZ_STREAMING_BUFFER > 0, scratch buffers must be initially +// zeroed out (see manual). First #ifndef makes it possible to define settings from +// the outside like the compiler command line. + +// 1.5.0 final + +#ifndef QLZ_COMPRESSION_LEVEL + #define QLZ_COMPRESSION_LEVEL 1 + //#define QLZ_COMPRESSION_LEVEL 2 + //#define QLZ_COMPRESSION_LEVEL 3 + + #define QLZ_STREAMING_BUFFER 0 + //#define QLZ_STREAMING_BUFFER 100000 + //#define QLZ_STREAMING_BUFFER 1000000 + + //#define QLZ_MEMORY_SAFE +#endif + +#define QLZ_VERSION_MAJOR 1 +#define QLZ_VERSION_MINOR 5 +#define QLZ_VERSION_REVISION 0 + +// Using size_t, memset() and memcpy() +#include <string.h> + +// Verify compression level +#if QLZ_COMPRESSION_LEVEL != 1 && QLZ_COMPRESSION_LEVEL != 2 && QLZ_COMPRESSION_LEVEL != 3 +#error QLZ_COMPRESSION_LEVEL must be 1, 2 or 3 +#endif + +typedef unsigned int ui32; +typedef unsigned short int ui16; + +// Decrease QLZ_POINTERS for level 3 to increase compression speed. Do not touch any other values! +#if QLZ_COMPRESSION_LEVEL == 1 +#define QLZ_POINTERS 1 +#define QLZ_HASH_VALUES 4096 +#elif QLZ_COMPRESSION_LEVEL == 2 +#define QLZ_POINTERS 4 +#define QLZ_HASH_VALUES 2048 +#elif QLZ_COMPRESSION_LEVEL == 3 +#define QLZ_POINTERS 16 +#define QLZ_HASH_VALUES 4096 +#endif + +// Detect if pointer size is 64-bit. It's not fatal if some 64-bit target is not detected because this is only for adding an optional 64-bit optimization. +#if defined _LP64 || defined __LP64__ || defined __64BIT__ || _ADDR64 || defined _WIN64 || defined __arch64__ || __WORDSIZE == 64 || (defined __sparc && defined __sparcv9) || defined __x86_64 || defined __amd64 || defined __x86_64__ || defined _M_X64 || defined _M_IA64 || defined __ia64 || defined __IA64__ + #define QLZ_PTR_64 +#endif + +// hash entry +typedef struct +{ +#if QLZ_COMPRESSION_LEVEL == 1 + ui32 cache; +#if defined QLZ_PTR_64 && QLZ_STREAMING_BUFFER == 0 + unsigned int offset; +#else + const unsigned char *offset; +#endif +#else + const unsigned char *offset[QLZ_POINTERS]; +#endif + +} qlz_hash_compress; + +typedef struct +{ +#if QLZ_COMPRESSION_LEVEL == 1 + const unsigned char *offset; +#else + const unsigned char *offset[QLZ_POINTERS]; +#endif +} qlz_hash_decompress; + + +// states +typedef struct +{ + #if QLZ_STREAMING_BUFFER > 0 + unsigned char stream_buffer[QLZ_STREAMING_BUFFER]; + #endif + size_t stream_counter; + qlz_hash_compress hash[QLZ_HASH_VALUES]; + unsigned char hash_counter[QLZ_HASH_VALUES]; +} qlz_state_compress; + + +#if QLZ_COMPRESSION_LEVEL == 1 || QLZ_COMPRESSION_LEVEL == 2 + typedef struct + { +#if QLZ_STREAMING_BUFFER > 0 + unsigned char stream_buffer[QLZ_STREAMING_BUFFER]; +#endif + qlz_hash_decompress hash[QLZ_HASH_VALUES]; + unsigned char hash_counter[QLZ_HASH_VALUES]; + size_t stream_counter; + } qlz_state_decompress; +#elif QLZ_COMPRESSION_LEVEL == 3 + typedef struct + { +#if QLZ_STREAMING_BUFFER > 0 + unsigned char stream_buffer[QLZ_STREAMING_BUFFER]; +#endif +#if QLZ_COMPRESSION_LEVEL <= 2 + qlz_hash_decompress hash[QLZ_HASH_VALUES]; +#endif + size_t stream_counter; + } qlz_state_decompress; +#endif + + +#if defined (__cplusplus) +extern "C" { +#endif + +// Public functions of QuickLZ +size_t qlz_size_decompressed(const char *source); +size_t qlz_size_compressed(const char *source); +size_t qlz_compress(const void *source, char *destination, size_t size, qlz_state_compress *state); +size_t qlz_decompress(const char *source, void *destination, qlz_state_decompress *state); +int qlz_get_setting(int setting); +size_t qlz_size_header(const char *source); + +#if defined (__cplusplus) +} +#endif + +#endif + diff --git a/extra/mariabackup/read_filt.cc b/extra/mariabackup/read_filt.cc new file mode 100644 index 00000000000..05e6b7c86c7 --- /dev/null +++ b/extra/mariabackup/read_filt.cc @@ -0,0 +1,206 @@ +/****************************************************** +XtraBackup: hot backup tool for InnoDB +(c) 2009-2012 Percona Inc. +Originally Created 3/3/2009 Yasufumi Kinoshita +Written by Alexey Kopytov, Aleksandr Kuzminsky, Stewart Smith, Vadim Tkachenko, +Yasufumi Kinoshita, Ignacio Nin and Baron Schwartz. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +/* Data file read filter implementation */ + +#include "read_filt.h" +#include "common.h" +#include "fil_cur.h" +#include "xtrabackup.h" + +/****************************************************************//** +Perform read filter context initialization that is common to all read +filters. */ +static +void +common_init( +/*========*/ + xb_read_filt_ctxt_t* ctxt, /*!<in/out: read filter context */ + const xb_fil_cur_t* cursor) /*!<in: file cursor */ +{ + ctxt->offset = 0; + ctxt->data_file_size = cursor->statinfo.st_size; + ctxt->buffer_capacity = cursor->buf_size; + ctxt->page_size = cursor->page_size; +} + +/****************************************************************//** +Initialize the pass-through read filter. */ +static +void +rf_pass_through_init( +/*=================*/ + xb_read_filt_ctxt_t* ctxt, /*!<in/out: read filter context */ + const xb_fil_cur_t* cursor, /*!<in: file cursor */ + ulint space_id __attribute__((unused))) + /*!<in: space id we are reading */ +{ + common_init(ctxt, cursor); +} + +/****************************************************************//** +Get the next batch of pages for the pass-through read filter. */ +static +void +rf_pass_through_get_next_batch( +/*===========================*/ + xb_read_filt_ctxt_t* ctxt, /*!<in/out: read filter + context */ + ib_int64_t* read_batch_start, /*!<out: starting read + offset in bytes for the + next batch of pages */ + ib_int64_t* read_batch_len) /*!<out: length in + bytes of the next batch + of pages */ +{ + *read_batch_start = ctxt->offset; + *read_batch_len = ctxt->data_file_size - ctxt->offset; + + if (*read_batch_len > (ib_int64_t)ctxt->buffer_capacity) { + *read_batch_len = ctxt->buffer_capacity; + } + + ctxt->offset += *read_batch_len; +} + +/****************************************************************//** +Deinitialize the pass-through read filter. */ +static +void +rf_pass_through_deinit( +/*===================*/ + xb_read_filt_ctxt_t* ctxt __attribute__((unused))) + /*!<in: read filter context */ +{ +} + +/****************************************************************//** +Initialize the changed page bitmap-based read filter. Assumes that +the bitmap is already set up in changed_page_bitmap. */ +static +void +rf_bitmap_init( +/*===========*/ + xb_read_filt_ctxt_t* ctxt, /*!<in/out: read filter + context */ + const xb_fil_cur_t* cursor, /*!<in: read cursor */ + ulint space_id) /*!<in: space id */ +{ + common_init(ctxt, cursor); + ctxt->bitmap_range = xb_page_bitmap_range_init(changed_page_bitmap, + space_id); + ctxt->filter_batch_end = 0; +} + +/****************************************************************//** +Get the next batch of pages for the bitmap read filter. */ +static +void +rf_bitmap_get_next_batch( +/*=====================*/ + xb_read_filt_ctxt_t* ctxt, /*!<in/out: read filter + context */ + ib_int64_t* read_batch_start, /*!<out: starting read + offset in bytes for the + next batch of pages */ + ib_int64_t* read_batch_len) /*!<out: length in + bytes of the next batch + of pages */ +{ + ulint start_page_id; + + start_page_id = (ulint)(ctxt->offset / ctxt->page_size); + + xb_a (ctxt->offset % ctxt->page_size == 0); + + if (start_page_id == ctxt->filter_batch_end) { + + /* Used up all the previous bitmap range, get some more */ + ulint next_page_id; + + /* Find the next changed page using the bitmap */ + next_page_id = xb_page_bitmap_range_get_next_bit + (ctxt->bitmap_range, TRUE); + + if (next_page_id == ULINT_UNDEFINED) { + *read_batch_len = 0; + return; + } + + ctxt->offset = next_page_id * ctxt->page_size; + + /* Find the end of the current changed page block by searching + for the next cleared bitmap bit */ + ctxt->filter_batch_end + = xb_page_bitmap_range_get_next_bit(ctxt->bitmap_range, + FALSE); + xb_a(next_page_id < ctxt->filter_batch_end); + } + + *read_batch_start = ctxt->offset; + if (ctxt->filter_batch_end == ULINT_UNDEFINED) { + /* No more cleared bits in the bitmap, need to copy all the + remaining pages. */ + *read_batch_len = ctxt->data_file_size - ctxt->offset; + } else { + *read_batch_len = ctxt->filter_batch_end * ctxt->page_size + - ctxt->offset; + } + + /* If the page block is larger than the buffer capacity, limit it to + buffer capacity. The subsequent invocations will continue returning + the current block in buffer-sized pieces until ctxt->filter_batch_end + is reached, trigerring the next bitmap query. */ + if (*read_batch_len > (ib_int64_t)ctxt->buffer_capacity) { + *read_batch_len = ctxt->buffer_capacity; + } + + ctxt->offset += *read_batch_len; + xb_a (ctxt->offset % ctxt->page_size == 0); + xb_a (*read_batch_start % ctxt->page_size == 0); + xb_a (*read_batch_len % ctxt->page_size == 0); +} + +/****************************************************************//** +Deinitialize the changed page bitmap-based read filter. */ +static +void +rf_bitmap_deinit( +/*=============*/ + xb_read_filt_ctxt_t* ctxt) /*!<in/out: read filter context */ +{ + xb_page_bitmap_range_deinit(ctxt->bitmap_range); +} + +/* The pass-through read filter */ +xb_read_filt_t rf_pass_through = { + &rf_pass_through_init, + &rf_pass_through_get_next_batch, + &rf_pass_through_deinit +}; + +/* The changed page bitmap-based read filter */ +xb_read_filt_t rf_bitmap = { + &rf_bitmap_init, + &rf_bitmap_get_next_batch, + &rf_bitmap_deinit +}; diff --git a/extra/mariabackup/read_filt.h b/extra/mariabackup/read_filt.h new file mode 100644 index 00000000000..d16f4e1093d --- /dev/null +++ b/extra/mariabackup/read_filt.h @@ -0,0 +1,62 @@ +/****************************************************** +XtraBackup: hot backup tool for InnoDB +(c) 2009-2012 Percona Inc. +Originally Created 3/3/2009 Yasufumi Kinoshita +Written by Alexey Kopytov, Aleksandr Kuzminsky, Stewart Smith, Vadim Tkachenko, +Yasufumi Kinoshita, Ignacio Nin and Baron Schwartz. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +/* Data file read filter interface */ + +#ifndef XB_READ_FILT_H +#define XB_READ_FILT_H + +#include "changed_page_bitmap.h" + +struct xb_fil_cur_t; + +/* The read filter context */ +struct xb_read_filt_ctxt_t { + ib_int64_t offset; /*!< current file offset */ + ib_int64_t data_file_size; /*!< data file size */ + size_t buffer_capacity;/*!< read buffer capacity */ + ib_int64_t space_id; /*!< space id */ + /* The following fields used only in bitmap filter */ + /* Move these to union if any other filters are added in future */ + xb_page_bitmap_range *bitmap_range; /*!< changed page bitmap range + iterator for space_id */ + size_t page_size; /*!< page size */ + ulint filter_batch_end;/*!< the ending page id of the + current changed page block in + the bitmap */ +}; + +/* The read filter */ +struct xb_read_filt_t { + void (*init)(xb_read_filt_ctxt_t* ctxt, + const xb_fil_cur_t* cursor, + ulint space_id); + void (*get_next_batch)(xb_read_filt_ctxt_t* ctxt, + ib_int64_t* read_batch_start, + ib_int64_t* read_batch_len); + void (*deinit)(xb_read_filt_ctxt_t* ctxt); +}; + +extern xb_read_filt_t rf_pass_through; +extern xb_read_filt_t rf_bitmap; + +#endif diff --git a/extra/mariabackup/write_filt.cc b/extra/mariabackup/write_filt.cc new file mode 100644 index 00000000000..cf7753bf380 --- /dev/null +++ b/extra/mariabackup/write_filt.cc @@ -0,0 +1,219 @@ +/****************************************************** +XtraBackup: hot backup tool for InnoDB +(c) 2009-2013 Percona LLC and/or its affiliates. +Originally Created 3/3/2009 Yasufumi Kinoshita +Written by Alexey Kopytov, Aleksandr Kuzminsky, Stewart Smith, Vadim Tkachenko, +Yasufumi Kinoshita, Ignacio Nin and Baron Schwartz. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +/* Page write filters implementation */ + +#include <my_base.h> +#include "common.h" +#include "write_filt.h" +#include "fil_cur.h" +#include "xtrabackup.h" + +/************************************************************************ +Write-through page write filter. */ +static my_bool wf_wt_init(xb_write_filt_ctxt_t *ctxt, char *dst_name, + xb_fil_cur_t *cursor); +static my_bool wf_wt_process(xb_write_filt_ctxt_t *ctxt, ds_file_t *dstfile); + +xb_write_filt_t wf_write_through = { + &wf_wt_init, + &wf_wt_process, + NULL, + NULL +}; + +/************************************************************************ +Incremental page write filter. */ +static my_bool wf_incremental_init(xb_write_filt_ctxt_t *ctxt, char *dst_name, + xb_fil_cur_t *cursor); +static my_bool wf_incremental_process(xb_write_filt_ctxt_t *ctxt, + ds_file_t *dstfile); +static my_bool wf_incremental_finalize(xb_write_filt_ctxt_t *ctxt, + ds_file_t *dstfile); +static void wf_incremental_deinit(xb_write_filt_ctxt_t *ctxt); + +xb_write_filt_t wf_incremental = { + &wf_incremental_init, + &wf_incremental_process, + &wf_incremental_finalize, + &wf_incremental_deinit +}; + +/************************************************************************ +Initialize incremental page write filter. + +@return TRUE on success, FALSE on error. */ +static my_bool +wf_incremental_init(xb_write_filt_ctxt_t *ctxt, char *dst_name, + xb_fil_cur_t *cursor) +{ + char meta_name[FN_REFLEN]; + xb_delta_info_t info; + ulint buf_size; + xb_wf_incremental_ctxt_t *cp = + &(ctxt->u.wf_incremental_ctxt); + + ctxt->cursor = cursor; + + /* allocate buffer for incremental backup (4096 pages) */ + buf_size = (cursor->page_size / 4 + 1) * cursor->page_size; + cp->delta_buf_base = static_cast<byte *>(ut_malloc(buf_size)); + memset(cp->delta_buf_base, 0, buf_size); + cp->delta_buf = static_cast<byte *> + (ut_align(cp->delta_buf_base, UNIV_PAGE_SIZE_MAX)); + + /* write delta meta info */ + snprintf(meta_name, sizeof(meta_name), "%s%s", dst_name, + XB_DELTA_INFO_SUFFIX); + info.page_size = cursor->page_size; + info.zip_size = cursor->zip_size; + info.space_id = cursor->space_id; + if (!xb_write_delta_metadata(meta_name, &info)) { + msg("[%02u] xtrabackup: Error: " + "failed to write meta info for %s\n", + cursor->thread_n, cursor->rel_path); + return(FALSE); + } + + /* change the target file name, since we are only going to write + delta pages */ + strcat(dst_name, ".delta"); + + mach_write_to_4(cp->delta_buf, 0x78747261UL); /*"xtra"*/ + cp->npages = 1; + + return(TRUE); +} + +/************************************************************************ +Run the next batch of pages through incremental page write filter. + +@return TRUE on success, FALSE on error. */ +static my_bool +wf_incremental_process(xb_write_filt_ctxt_t *ctxt, ds_file_t *dstfile) +{ + ulint i; + xb_fil_cur_t *cursor = ctxt->cursor; + ulint page_size = cursor->page_size; + byte *page; + xb_wf_incremental_ctxt_t *cp = &(ctxt->u.wf_incremental_ctxt); + + for (i = 0, page = cursor->buf; i < cursor->buf_npages; + i++, page += page_size) { + + if (incremental_lsn >= mach_read_from_8(page + FIL_PAGE_LSN)) { + + continue; + } + + /* updated page */ + if (cp->npages == page_size / 4) { + /* flush buffer */ + if (ds_write(dstfile, cp->delta_buf, + cp->npages * page_size)) { + return(FALSE); + } + + /* clear buffer */ + memset(cp->delta_buf, 0, page_size / 4 * page_size); + /*"xtra"*/ + mach_write_to_4(cp->delta_buf, 0x78747261UL); + cp->npages = 1; + } + + mach_write_to_4(cp->delta_buf + cp->npages * 4, + cursor->buf_page_no + i); + memcpy(cp->delta_buf + cp->npages * page_size, page, + page_size); + + cp->npages++; + } + + return(TRUE); +} + +/************************************************************************ +Flush the incremental page write filter's buffer. + +@return TRUE on success, FALSE on error. */ +static my_bool +wf_incremental_finalize(xb_write_filt_ctxt_t *ctxt, ds_file_t *dstfile) +{ + xb_fil_cur_t *cursor = ctxt->cursor; + ulint page_size = cursor->page_size; + xb_wf_incremental_ctxt_t *cp = &(ctxt->u.wf_incremental_ctxt); + + if (cp->npages != page_size / 4) { + mach_write_to_4(cp->delta_buf + cp->npages * 4, 0xFFFFFFFFUL); + } + + /* Mark the final block */ + mach_write_to_4(cp->delta_buf, 0x58545241UL); /*"XTRA"*/ + + /* flush buffer */ + if (ds_write(dstfile, cp->delta_buf, cp->npages * page_size)) { + return(FALSE); + } + + return(TRUE); +} + +/************************************************************************ +Free the incremental page write filter's buffer. */ +static void +wf_incremental_deinit(xb_write_filt_ctxt_t *ctxt) +{ + xb_wf_incremental_ctxt_t *cp = &(ctxt->u.wf_incremental_ctxt); + + if (cp->delta_buf_base != NULL) { + ut_free(cp->delta_buf_base); + } +} + +/************************************************************************ +Initialize the write-through page write filter. + +@return TRUE on success, FALSE on error. */ +static my_bool +wf_wt_init(xb_write_filt_ctxt_t *ctxt, char *dst_name __attribute__((unused)), + xb_fil_cur_t *cursor) +{ + ctxt->cursor = cursor; + + return(TRUE); +} + +/************************************************************************ +Write the next batch of pages to the destination datasink. + +@return TRUE on success, FALSE on error. */ +static my_bool +wf_wt_process(xb_write_filt_ctxt_t *ctxt, ds_file_t *dstfile) +{ + xb_fil_cur_t *cursor = ctxt->cursor; + + if (ds_write(dstfile, cursor->buf, cursor->buf_read)) { + return(FALSE); + } + + return(TRUE); +} diff --git a/extra/mariabackup/write_filt.h b/extra/mariabackup/write_filt.h new file mode 100644 index 00000000000..bcab263f1dd --- /dev/null +++ b/extra/mariabackup/write_filt.h @@ -0,0 +1,58 @@ +/****************************************************** +XtraBackup: hot backup tool for InnoDB +(c) 2009-2013 Percona LLC and/or its affiliates. +Originally Created 3/3/2009 Yasufumi Kinoshita +Written by Alexey Kopytov, Aleksandr Kuzminsky, Stewart Smith, Vadim Tkachenko, +Yasufumi Kinoshita, Ignacio Nin and Baron Schwartz. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +/* Page write filter interface */ + +#ifndef XB_WRITE_FILT_H +#define XB_WRITE_FILT_H + +#include "fil_cur.h" +#include "datasink.h" + +/* Incremental page filter context */ +typedef struct { + byte *delta_buf_base; + byte *delta_buf; + ulint npages; +} xb_wf_incremental_ctxt_t; + +/* Page filter context used as an opaque structure by callers */ +typedef struct { + xb_fil_cur_t *cursor; + union { + xb_wf_incremental_ctxt_t wf_incremental_ctxt; + } u; +} xb_write_filt_ctxt_t; + + +typedef struct { + my_bool (*init)(xb_write_filt_ctxt_t *ctxt, char *dst_name, + xb_fil_cur_t *cursor); + my_bool (*process)(xb_write_filt_ctxt_t *ctxt, ds_file_t *dstfile); + my_bool (*finalize)(xb_write_filt_ctxt_t *, ds_file_t *dstfile); + void (*deinit)(xb_write_filt_ctxt_t *); +} xb_write_filt_t; + +extern xb_write_filt_t wf_write_through; +extern xb_write_filt_t wf_incremental; + +#endif /* XB_WRITE_FILT_H */ diff --git a/extra/mariabackup/wsrep.cc b/extra/mariabackup/wsrep.cc new file mode 100644 index 00000000000..be11e058255 --- /dev/null +++ b/extra/mariabackup/wsrep.cc @@ -0,0 +1,220 @@ +/****************************************************** +Percona XtraBackup: hot backup tool for InnoDB +(c) 2009-2014 Percona LLC and/or its affiliates +Originally Created 3/3/2009 Yasufumi Kinoshita +Written by Alexey Kopytov, Aleksandr Kuzminsky, Stewart Smith, Vadim Tkachenko, +Yasufumi Kinoshita, Ignacio Nin and Baron Schwartz. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +******************************************************* + +This file incorporates work covered by the following copyright and +permission notice: + + Copyright 2010 Codership Oy <http://www.codership.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#include <mysql_version.h> +#include <my_base.h> +#include <handler.h> +#include <trx0sys.h> + +#include "common.h" +#ifdef WITH_WSREP +#define WSREP_XID_PREFIX "WSREPXid" +#define WSREP_XID_PREFIX_LEN MYSQL_XID_PREFIX_LEN +#define WSREP_XID_UUID_OFFSET 8 +#define WSREP_XID_SEQNO_OFFSET (WSREP_XID_UUID_OFFSET + sizeof(wsrep_uuid_t)) +#define WSREP_XID_GTRID_LEN (WSREP_XID_SEQNO_OFFSET + sizeof(wsrep_seqno_t)) + +/*! undefined seqno */ +#define WSREP_SEQNO_UNDEFINED (-1) + +/*! Name of file where Galera info is stored on recovery */ +#define XB_GALERA_INFO_FILENAME "xtrabackup_galera_info" + +/* Galera UUID type - for all unique IDs */ +typedef struct wsrep_uuid { + unsigned char data[16]; +} wsrep_uuid_t; + +/* sequence number of a writeset, etc. */ +typedef long long wsrep_seqno_t; + +/* Undefined UUID */ +static const wsrep_uuid_t WSREP_UUID_UNDEFINED = {{0,}}; + +/***********************************************************************//** +Check if a given WSREP XID is valid. + +@return true if valid. +*/ +static +bool +wsrep_is_wsrep_xid( +/*===============*/ + const void* xid_ptr) +{ + const XID* xid = reinterpret_cast<const XID*>(xid_ptr); + + return((xid->formatID == 1 && + xid->gtrid_length == WSREP_XID_GTRID_LEN && + xid->bqual_length == 0 && + !memcmp(xid->data, WSREP_XID_PREFIX, WSREP_XID_PREFIX_LEN))); +} + +/***********************************************************************//** +Retrieve binary WSREP UUID from XID. + +@return binary WSREP UUID represenataion, if UUID is valid, or + WSREP_UUID_UNDEFINED otherwise. +*/ +static +const wsrep_uuid_t* +wsrep_xid_uuid( +/*===========*/ + const XID* xid) +{ + if (wsrep_is_wsrep_xid(xid)) { + return(reinterpret_cast<const wsrep_uuid_t*> + (xid->data + WSREP_XID_UUID_OFFSET)); + } else { + return(&WSREP_UUID_UNDEFINED); + } +} + +/***********************************************************************//** +Retrieve WSREP seqno from XID. + +@return WSREP seqno, if it is valid, or WSREP_SEQNO_UNDEFINED otherwise. +*/ +wsrep_seqno_t wsrep_xid_seqno( +/*==========================*/ + const XID* xid) +{ + if (wsrep_is_wsrep_xid(xid)) { + wsrep_seqno_t seqno; + memcpy(&seqno, xid->data + WSREP_XID_SEQNO_OFFSET, + sizeof(wsrep_seqno_t)); + + return(seqno); + } else { + return(WSREP_SEQNO_UNDEFINED); + } +} + +/***********************************************************************//** +Write UUID to string. + +@return length of UUID string representation or -EMSGSIZE if string is too +short. +*/ +static +int +wsrep_uuid_print( +/*=============*/ + const wsrep_uuid_t* uuid, + char* str, + size_t str_len) +{ + if (str_len > 36) { + const unsigned char* u = uuid->data; + return snprintf(str, str_len, + "%02x%02x%02x%02x-%02x%02x-%02x%02x-" + "%02x%02x-%02x%02x%02x%02x%02x%02x", + u[ 0], u[ 1], u[ 2], u[ 3], u[ 4], u[ 5], u[ 6], + u[ 7], u[ 8], u[ 9], u[10], u[11], u[12], u[13], + u[14], u[15]); + } + else { + return -EMSGSIZE; + } +} + +/*********************************************************************** +Store Galera checkpoint info in the 'xtrabackup_galera_info' file, if that +information is present in the trx system header. Otherwise, do nothing. */ +void +xb_write_galera_info(bool incremental_prepare) +/*==================*/ +{ + FILE* fp; + XID xid; + char uuid_str[40]; + wsrep_seqno_t seqno; + MY_STAT statinfo; + + /* Do not overwrite existing an existing file to be compatible with + servers with older server versions */ + if (!incremental_prepare && + my_stat(XB_GALERA_INFO_FILENAME, &statinfo, MYF(0)) != NULL) { + + return; + } + + memset(&xid, 0, sizeof(xid)); + xid.formatID = -1; + + if (!trx_sys_read_wsrep_checkpoint(&xid)) { + + return; + } + + if (wsrep_uuid_print(wsrep_xid_uuid(&xid), uuid_str, + sizeof(uuid_str)) < 0) { + return; + } + + fp = fopen(XB_GALERA_INFO_FILENAME, "w"); + if (fp == NULL) { + + msg("xtrabackup: error: " + "could not create " XB_GALERA_INFO_FILENAME + ", errno = %d\n", + errno); + exit(EXIT_FAILURE); + } + + seqno = wsrep_xid_seqno(&xid); + + msg("xtrabackup: Recovered WSREP position: %s:%lld\n", + uuid_str, (long long) seqno); + + if (fprintf(fp, "%s:%lld", uuid_str, (long long) seqno) < 0) { + + msg("xtrabackup: error: " + "could not write to " XB_GALERA_INFO_FILENAME + ", errno = %d\n", + errno); + exit(EXIT_FAILURE); + } + + fclose(fp); +} +#endif diff --git a/extra/mariabackup/wsrep.h b/extra/mariabackup/wsrep.h new file mode 100644 index 00000000000..7638d1f2b54 --- /dev/null +++ b/extra/mariabackup/wsrep.h @@ -0,0 +1,32 @@ +/****************************************************** +Percona XtraBackup: hot backup tool for InnoDB +(c) 2009-2014 Percona LLC and/or its affiliates +Originally Created 3/3/2009 Yasufumi Kinoshita +Written by Alexey Kopytov, Aleksandr Kuzminsky, Stewart Smith, Vadim Tkachenko, +Yasufumi Kinoshita, Ignacio Nin and Baron Schwartz. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +*******************************************************/ + +#ifndef WSREP_H +#define WSREP_H + +/*********************************************************************** +Store Galera checkpoint info in the 'xtrabackup_galera_info' file, if that +information is present in the trx system header. Otherwise, do nothing. */ +void +xb_write_galera_info(bool incremental_prepare); +/*==================*/ + +#endif diff --git a/extra/mariabackup/xb0xb.h b/extra/mariabackup/xb0xb.h new file mode 100644 index 00000000000..659ab8ea5d0 --- /dev/null +++ b/extra/mariabackup/xb0xb.h @@ -0,0 +1,78 @@ +/****************************************************** +Copyright (c) 2012 Percona LLC and/or its affiliates. + +Declarations of XtraBackup functions called by InnoDB code. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#ifndef xb0xb_h +#define xb0xb_h + + +extern void os_io_init_simple(void); +extern os_file_t files[1000]; +extern const char *innodb_checksum_algorithm_names[]; +extern TYPELIB innodb_checksum_algorithm_typelib; +extern dberr_t open_or_create_data_files( + ibool* create_new_db, +#ifdef UNIV_LOG_ARCHIVE + lsn_t* min_arch_log_no, + lsn_t* max_arch_log_no, +#endif + lsn_t* min_flushed_lsn, + lsn_t* max_flushed_lsn, + ulint* sum_of_new_sizes) + ; +int +fil_file_readdir_next_file( +/*=======================*/ +dberr_t* err, /*!< out: this is set to DB_ERROR if an error + was encountered, otherwise not changed */ + const char* dirname,/*!< in: directory name or path */ + os_file_dir_t dir, /*!< in: directory stream */ + os_file_stat_t* info) /*!< in/out: buffer where the + info is returned */; +buf_block_t* btr_node_ptr_get_child( + const rec_t* node_ptr,/*!< in: node pointer */ + dict_index_t* index, /*!< in: index */ + const ulint* offsets,/*!< in: array returned by rec_get_offsets() */ + mtr_t* mtr) /*!< in: mtr */; + +buf_block_t* +btr_root_block_get( +/*===============*/ +const dict_index_t* index, /*!< in: index tree */ +ulint mode, /*!< in: either RW_S_LATCH + or RW_X_LATCH */ + mtr_t* mtr) /*!< in: mtr */; +fil_space_t* +fil_space_get_by_name(const char *); +ibool +recv_check_cp_is_consistent(const byte* buf); +void +innodb_log_checksum_func_update( +/*============================*/ +ulint algorithm) /*!< in: algorithm */; +dberr_t recv_find_max_checkpoint(log_group_t** max_group, ulint* max_field); +dberr_t +srv_undo_tablespaces_init( +/*======================*/ +ibool create_new_db, +ibool backup_mode, +const ulint n_conf_tablespaces, +ulint* n_opened); + +#endif diff --git a/extra/mariabackup/xb_regex.h b/extra/mariabackup/xb_regex.h new file mode 100644 index 00000000000..2e07e434e27 --- /dev/null +++ b/extra/mariabackup/xb_regex.h @@ -0,0 +1,48 @@ +/****************************************************** +Copyright (c) 2011-2013 Percona LLC and/or its affiliates. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +/* This file is required to abstract away regex(3) calls so that +my_regex is used on Windows and native calls are used on POSIX platforms. */ + +#ifndef XB_REGEX_H +#define XB_REGEX_H + +#ifdef HAVE_SYSTEM_REGEX +#include <regex.h> +#else +#include <pcreposix.h> +#endif + +typedef regex_t* xb_regex_t; + +#define xb_regex_init() + +#define xb_regexec(preg,string,nmatch,pmatch,eflags) \ + regexec(preg, string, nmatch, pmatch, eflags) + +#define xb_regerror(errcode,preg,errbuf,errbuf_size) \ + regerror(errcode, preg, errbuf, errbuf_size) + +#define xb_regcomp(preg,regex,cflags) \ + regcomp(preg, regex, cflags) + +#define xb_regfree(preg) regfree(preg) + +#define xb_regex_end() + +#endif /* XB_REGEX_H */ diff --git a/extra/mariabackup/xbcloud.cc b/extra/mariabackup/xbcloud.cc new file mode 100644 index 00000000000..56661b03dd0 --- /dev/null +++ b/extra/mariabackup/xbcloud.cc @@ -0,0 +1,2721 @@ +/****************************************************** +Copyright (c) 2014 Percona LLC and/or its affiliates. + +The xbstream utility: serialize/deserialize files in the XBSTREAM format. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#include <my_global.h> +#include <my_default.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <curl/curl.h> +#include <ev.h> +#include <unistd.h> +#include <errno.h> +#include <gcrypt.h> +#include <assert.h> +#include <my_sys.h> +#include <my_dir.h> +#include <my_getopt.h> +#include <algorithm> +#include <map> +#include <string> +#include <jsmn.h> +#include "xbstream.h" + +using std::min; +using std::max; +using std::map; +using std::string; + +#define XBCLOUD_VERSION "1.0" + +#define SWIFT_MAX_URL_SIZE 8192 +#define SWIFT_MAX_HDR_SIZE 8192 + +#define SWIFT_CHUNK_SIZE 11 * 1024 * 1024 + +#if ((LIBCURL_VERSION_MAJOR >= 7) && (LIBCURL_VERSION_MINOR >= 16)) +#define OLD_CURL_MULTI 0 +#else +#define OLD_CURL_MULTI 1 +#endif + +/*****************************************************************************/ + +typedef struct swift_auth_info_struct swift_auth_info; +typedef struct connection_info_struct connection_info; +typedef struct socket_info_struct socket_info; +typedef struct global_io_info_struct global_io_info; +typedef struct slo_chunk_struct slo_chunk; +typedef struct container_list_struct container_list; +typedef struct object_info_struct object_info; + +struct swift_auth_info_struct { + char url[SWIFT_MAX_URL_SIZE]; + char token[SWIFT_MAX_HDR_SIZE]; +}; + +struct global_io_info_struct { + struct ev_loop *loop; + struct ev_io input_event; + struct ev_timer timer_event; + CURLM *multi; + int still_running; + int eof; + curl_socket_t input_fd; + connection_info **connections; + long chunk_no; + connection_info *current_connection; + const char *url; + const char *container; + const char *token; + const char *backup_name; +}; + +struct socket_info_struct { + curl_socket_t sockfd; + CURL *easy; + int action; + long timeout; + struct ev_io ev; + int evset; + global_io_info *global; +}; + +struct connection_info_struct { + CURL *easy; + global_io_info *global; + char *buffer; + size_t buffer_size; + size_t filled_size; + size_t upload_size; + bool chunk_uploaded; + bool chunk_acked; + char error[CURL_ERROR_SIZE]; + struct curl_slist *slist; + char *name; + size_t name_len; + char hash[33]; + size_t chunk_no; + bool magic_verified; + size_t chunk_path_len; + xb_chunk_type_t chunk_type; + size_t payload_size; + size_t chunk_size; + int retry_count; + bool upload_started; + ulong global_idx; +}; + +struct slo_chunk_struct { + char name[SWIFT_MAX_URL_SIZE]; + char md5[33]; + int idx; + size_t size; +}; + +struct object_info_struct { + char hash[33]; + char name[SWIFT_MAX_URL_SIZE]; + size_t bytes; +}; + +struct container_list_struct { + size_t content_length; + size_t content_bufsize; + char *content_json; + size_t object_count; + size_t idx; + object_info *objects; + bool final; +}; + +enum {SWIFT, S3}; +const char *storage_names[] = +{ "SWIFT", "S3", NullS}; + +static my_bool opt_verbose = 0; +static ulong opt_storage = SWIFT; +static const char *opt_swift_user = NULL; +static const char *opt_swift_user_id = NULL; +static const char *opt_swift_password = NULL; +static const char *opt_swift_tenant = NULL; +static const char *opt_swift_tenant_id = NULL; +static const char *opt_swift_project = NULL; +static const char *opt_swift_project_id = NULL; +static const char *opt_swift_domain = NULL; +static const char *opt_swift_domain_id = NULL; +static const char *opt_swift_region = NULL; +static const char *opt_swift_container = NULL; +static const char *opt_swift_storage_url = NULL; +static const char *opt_swift_auth_url = NULL; +static const char *opt_swift_key = NULL; +static const char *opt_swift_auth_version = NULL; +static const char *opt_name = NULL; +static const char *opt_cacert = NULL; +static ulong opt_parallel = 1; +static my_bool opt_insecure = 0; +static enum {MODE_GET, MODE_PUT, MODE_DELETE} opt_mode; + +static char **file_list = NULL; +static int file_list_size = 0; + +TYPELIB storage_typelib = +{array_elements(storage_names)-1, "", storage_names, NULL}; + +enum { + OPT_STORAGE = 256, + OPT_SWIFT_CONTAINER, + OPT_SWIFT_AUTH_URL, + OPT_SWIFT_KEY, + OPT_SWIFT_USER, + OPT_SWIFT_USER_ID, + OPT_SWIFT_PASSWORD, + OPT_SWIFT_TENANT, + OPT_SWIFT_TENANT_ID, + OPT_SWIFT_PROJECT, + OPT_SWIFT_PROJECT_ID, + OPT_SWIFT_DOMAIN, + OPT_SWIFT_DOMAIN_ID, + OPT_SWIFT_REGION, + OPT_SWIFT_STORAGE_URL, + OPT_SWIFT_AUTH_VERSION, + OPT_PARALLEL, + OPT_CACERT, + OPT_INSECURE, + OPT_VERBOSE +}; + + +static struct my_option my_long_options[] = +{ + {"help", '?', "Display this help and exit.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"storage", OPT_STORAGE, "Specify storage type S3/SWIFT.", + &opt_storage, &opt_storage, &storage_typelib, + GET_ENUM, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"swift-auth-version", OPT_SWIFT_AUTH_VERSION, + "Swift authentication verison to use.", + &opt_swift_auth_version, &opt_swift_auth_version, 0, + GET_STR_ALLOC, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"swift-container", OPT_SWIFT_CONTAINER, + "Swift container to store backups into.", + &opt_swift_container, &opt_swift_container, 0, + GET_STR_ALLOC, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"swift-user", OPT_SWIFT_USER, + "Swift user name.", + &opt_swift_user, &opt_swift_user, 0, GET_STR_ALLOC, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"swift-user-id", OPT_SWIFT_USER_ID, + "Swift user ID.", + &opt_swift_user_id, &opt_swift_user_id, 0, GET_STR_ALLOC, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"swift-auth-url", OPT_SWIFT_AUTH_URL, + "Base URL of SWIFT authentication service.", + &opt_swift_auth_url, &opt_swift_auth_url, 0, + GET_STR_ALLOC, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"swift-storage-url", OPT_SWIFT_STORAGE_URL, + "URL of object-store endpoint. Usually received from authentication " + "service. Specify to override this value.", + &opt_swift_storage_url, &opt_swift_storage_url, 0, + GET_STR_ALLOC, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"swift-key", OPT_SWIFT_KEY, + "Swift key.", + &opt_swift_key, &opt_swift_key, 0, GET_STR_ALLOC, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"swift-tenant", OPT_SWIFT_TENANT, + "The tenant name. Both the --swift-tenant and --swift-tenant-id " + "options are optional, but should not be specified together.", + &opt_swift_tenant, &opt_swift_tenant, 0, GET_STR_ALLOC, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"swift-tenant-id", OPT_SWIFT_TENANT_ID, + "The tenant ID. Both the --swift-tenant and --swift-tenant-id " + "options are optional, but should not be specified together.", + &opt_swift_tenant_id, &opt_swift_tenant_id, 0, + GET_STR_ALLOC, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"swift-project", OPT_SWIFT_PROJECT, + "The project name.", + &opt_swift_project, &opt_swift_project, 0, GET_STR_ALLOC, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"swift-project-id", OPT_SWIFT_PROJECT_ID, + "The project ID.", + &opt_swift_project_id, &opt_swift_project_id, 0, + GET_STR_ALLOC, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"swift-domain", OPT_SWIFT_DOMAIN, + "The domain name.", + &opt_swift_domain, &opt_swift_domain, 0, GET_STR_ALLOC, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"swift-domain-id", OPT_SWIFT_DOMAIN_ID, + "The domain ID.", + &opt_swift_domain_id, &opt_swift_domain_id, 0, + GET_STR_ALLOC, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"swift-password", OPT_SWIFT_PASSWORD, + "The password of the user.", + &opt_swift_password, &opt_swift_password, 0, + GET_STR_ALLOC, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"swift-region", OPT_SWIFT_REGION, + "The region object-store endpoint.", + &opt_swift_region, &opt_swift_region, 0, + GET_STR_ALLOC, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"parallel", OPT_PARALLEL, + "Number of parallel chunk uploads.", + &opt_parallel, &opt_parallel, 0, GET_ULONG, REQUIRED_ARG, + 1, 0, 0, 0, 0, 0}, + + {"cacert", OPT_CACERT, + "CA certificate file.", + &opt_cacert, &opt_cacert, 0, GET_STR_ALLOC, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"insecure", OPT_INSECURE, + "Do not verify server SSL certificate.", + &opt_insecure, &opt_insecure, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, + + {"verbose", OPT_VERBOSE, + "Turn ON cURL tracing.", + &opt_verbose, &opt_verbose, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, + + {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + +/* The values of these arguments should be masked + on the command line */ +static const char * const masked_args[] = { + "--swift-password", + "--swift-key", + "--swift-auth-url", + "--swift-storage-url", + "--swift-container", + "--swift-user", + "--swift-tenant", + "--swift-user-id", + "--swift-tenant-id", + 0 +}; + +static map<string, ulonglong> file_chunk_count; + +static +void +print_version() +{ + printf("%s Ver %s for %s (%s)\n", my_progname, XBCLOUD_VERSION, + SYSTEM_TYPE, MACHINE_TYPE); +} + +static +void +usage() +{ + print_version(); + puts("Copyright (C) 2015 Percona LLC and/or its affiliates."); + puts("This software comes with ABSOLUTELY NO WARRANTY. " + "This is free software,\nand you are welcome to modify and " + "redistribute it under the GPL license.\n"); + + puts("Manage backups on Cloud services.\n"); + + puts("Usage: "); + printf(" %s -c put [OPTIONS...] <NAME> upload backup from STDIN into " + "the cloud service with given name.\n", my_progname); + printf(" %s -c get [OPTIONS...] <NAME> [FILES...] stream specified " + "backup or individual files from cloud service into STDOUT.\n", + my_progname); + + puts("\nOptions:"); + my_print_help(my_long_options); +} + +static +my_bool +get_one_option(int optid, const struct my_option *opt __attribute__((unused)), + char *argument __attribute__((unused))) +{ + switch (optid) { + case '?': + usage(); + exit(0); + } + + return(FALSE); +} + +static const char *load_default_groups[]= + { "xbcloud", 0 }; + +/*********************************************************************//** +mask sensitive values on the command line */ +static +void +mask_args(int argc, char **argv) +{ + int i; + for (i = 0; i < argc-1; i++) { + int j = 0; + if (argv[i]) while (masked_args[j]) { + char *p; + if ((p = strstr(argv[i], masked_args[j]))) { + p += strlen(masked_args[j]); + while (*p && *p != '=') { + p++; + } + if (*p == '=') { + p++; + while (*p) { + *p++ = 'x'; + } + } + } + j++; + } + } +} + +static +int parse_args(int argc, char **argv) +{ + const char *command; + + if (argc < 2) { + fprintf(stderr, "Command isn't specified. " + "Supported commands are put and get\n"); + usage(); + exit(EXIT_FAILURE); + } + + command = argv[1]; + argc--; argv++; + + if (strcasecmp(command, "put") == 0) { + opt_mode = MODE_PUT; + } else if (strcasecmp(command, "get") == 0) { + opt_mode = MODE_GET; + } else if (strcasecmp(command, "delete") == 0) { + opt_mode = MODE_DELETE; + } else { + fprintf(stderr, "Unknown command %s. " + "Supported commands are put and get\n", command); + usage(); + exit(EXIT_FAILURE); + } + + if (load_defaults("my", load_default_groups, &argc, &argv)) { + exit(EXIT_FAILURE); + } + + if (handle_options(&argc, &argv, my_long_options, get_one_option)) { + exit(EXIT_FAILURE); + } + + /* make sure name is specified */ + if (argc < 1) { + fprintf(stderr, "Backup name is required argument\n"); + exit(EXIT_FAILURE); + } + opt_name = argv[0]; + argc--; argv++; + + /* validate arguments */ + if (opt_storage == SWIFT) { + if (opt_swift_user == NULL) { + fprintf(stderr, "Swift user is not specified\n"); + exit(EXIT_FAILURE); + } + if (opt_swift_container == NULL) { + fprintf(stderr, + "Swift container is not specified\n"); + exit(EXIT_FAILURE); + } + if (opt_swift_auth_url == NULL) { + fprintf(stderr, "Swift auth URL is not specified\n"); + exit(EXIT_FAILURE); + } + } else { + fprintf(stderr, "Swift is only supported storage API\n"); + } + + if (argc > 0) { + file_list = argv; + file_list_size = argc; + } + + return(0); +} + +static char *hex_md5(const unsigned char *hash, char *out) +{ + enum { hash_len = 16 }; + char *p; + int i; + + for (i = 0, p = out; i < hash_len; i++, p+=2) { + sprintf(p, "%02x", hash[i]); + } + + return out; +} + +/* If header starts with prefix it's value will be copied into output buffer */ +static +int get_http_header(const char *prefix, const char *buffer, + char *out, size_t out_size) +{ + const char *beg, *end; + size_t len, prefix_len; + + prefix_len = strlen(prefix); + + if (strncasecmp(buffer, prefix, prefix_len) == 0) { + beg = buffer + prefix_len; + end = strchr(beg, '\r'); + + len = min<size_t>(end - beg, out_size - 1); + + strncpy(out, beg, len); + + out[len] = 0; + + return 1; + } + + return 0; +} + +static +size_t swift_auth_header_read_cb(char *ptr, size_t size, size_t nmemb, + void *data) +{ + swift_auth_info *info = (swift_auth_info*)(data); + + get_http_header("X-Storage-Url: ", ptr, + info->url, array_elements(info->url)); + get_http_header("X-Auth-Token: ", ptr, + info->token, array_elements(info->token)); + + return nmemb * size; +} + +/*********************************************************************//** +Authenticate against Swift TempAuth. Fills swift_auth_info struct. +Uses creadentials privided as global variables. +@returns true if access is granted and token received. */ +static +bool +swift_temp_auth(const char *auth_url, swift_auth_info *info) +{ + CURL *curl; + CURLcode res; + long http_code; + char *hdr_buf = NULL; + struct curl_slist *slist = NULL; + + if (opt_swift_user == NULL) { + fprintf(stderr, "Swift user must be specified for TempAuth.\n"); + return(false); + } + + if (opt_swift_key == NULL) { + fprintf(stderr, "Swift key must be specified for TempAuth.\n"); + return(false); + } + + curl = curl_easy_init(); + + if (curl != NULL) { + + hdr_buf = (char *)(calloc(14 + max(strlen(opt_swift_user), + strlen(opt_swift_key)), 1)); + + if (!hdr_buf) { + res = CURLE_FAILED_INIT; + goto cleanup; + } + + sprintf(hdr_buf, "X-Auth-User: %s", opt_swift_user); + slist = curl_slist_append(slist, hdr_buf); + + sprintf(hdr_buf, "X-Auth-Key: %s", opt_swift_key); + slist = curl_slist_append(slist, hdr_buf); + + curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose); + curl_easy_setopt(curl, CURLOPT_URL, auth_url); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, + swift_auth_header_read_cb); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, info); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); + if (opt_cacert != NULL) + curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert); + if (opt_insecure) + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE); + + res = curl_easy_perform(curl); + + if (res != CURLE_OK) { + fprintf(stderr, "error: authentication failed: " + "curl_easy_perform(): %s\n", + curl_easy_strerror(res)); + goto cleanup; + } + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + if (http_code != 200 && + http_code != 204) { + fprintf(stderr, "error: authentication failed " + "with response code: %ld\n", http_code); + res = CURLE_LOGIN_DENIED; + goto cleanup; + } + } else { + res = CURLE_FAILED_INIT; + fprintf(stderr, "error: curl_easy_init() failed\n"); + goto cleanup; + } + +cleanup: + if (hdr_buf) { + free(hdr_buf); + } + if (slist) { + curl_slist_free_all(slist); + } + if (curl) { + curl_easy_cleanup(curl); + } + + if (res == CURLE_OK) { + /* check that we received token and storage URL */ + if (*info->url == 0) { + fprintf(stderr, "error: malformed response: " + "X-Storage-Url is missing\n"); + return(false); + } + if (*info->token == 0) { + fprintf(stderr, "error: malformed response: " + "X-Auth-Token is missing\n"); + return(false); + } + return(true); + } + + return(false); +} + +static +size_t +write_null_cb(char *buffer, size_t size, size_t nmemb, void *stream) +{ + return fwrite(buffer, size, nmemb, stderr); +} + + +static +size_t +read_null_cb(char *ptr, size_t size, size_t nmemb, void *data) +{ + return 0; +} + + +static +int +swift_create_container(swift_auth_info *info, const char *name) +{ + char url[SWIFT_MAX_URL_SIZE]; + char auth_token[SWIFT_MAX_HDR_SIZE]; + CURLcode res; + long http_code; + CURL *curl; + struct curl_slist *slist = NULL; + + snprintf(url, array_elements(url), "%s/%s", info->url, name); + snprintf(auth_token, array_elements(auth_token), "X-Auth-Token: %s", + info->token); + + curl = curl_easy_init(); + + if (curl != NULL) { + slist = curl_slist_append(slist, auth_token); + slist = curl_slist_append(slist, "Content-Length: 0"); + + curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_null_cb); + curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_null_cb); + curl_easy_setopt(curl, CURLOPT_INFILESIZE, 0L); + curl_easy_setopt(curl, CURLOPT_PUT, 1L); + if (opt_cacert != NULL) + curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert); + if (opt_insecure) + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE); + + res = curl_easy_perform(curl); + + if (res != CURLE_OK) { + fprintf(stderr, + "error: curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); + goto cleanup; + } + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + if (http_code != 201 && /* created */ + http_code != 202 /* accepted (already exists) */) { + fprintf(stderr, "error: request failed " + "with response code: %ld\n", http_code); + res = CURLE_LOGIN_DENIED; + goto cleanup; + } + } else { + res = CURLE_FAILED_INIT; + fprintf(stderr, "error: curl_easy_init() failed\n"); + goto cleanup; + } + +cleanup: + if (slist) { + curl_slist_free_all(slist); + } + if (curl) { + curl_easy_cleanup(curl); + } + + return res; +} + + +/*********************************************************************//** +Delete object with given url. +@returns true if object deleted successfully. */ +static +bool +swift_delete_object(swift_auth_info *info, const char *url) +{ + char auth_token[SWIFT_MAX_HDR_SIZE]; + CURLcode res; + long http_code; + CURL *curl; + struct curl_slist *slist = NULL; + bool ret = false; + + snprintf(auth_token, array_elements(auth_token), "X-Auth-Token: %s", + info->token); + + curl = curl_easy_init(); + + if (curl != NULL) { + slist = curl_slist_append(slist, auth_token); + + curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + if (opt_cacert != NULL) + curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert); + if (opt_insecure) + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE); + + res = curl_easy_perform(curl); + + if (res != CURLE_OK) { + fprintf(stderr, + "error: curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); + goto cleanup; + } + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + if (http_code != 200 && /* OK */ + http_code != 204 /* no content */) { + fprintf(stderr, "error: request failed " + "with response code: %ld\n", http_code); + goto cleanup; + } + ret = true; + } else { + fprintf(stderr, "error: curl_easy_init() failed\n"); + goto cleanup; + } + +cleanup: + if (slist) { + curl_slist_free_all(slist); + } + if (curl) { + curl_easy_cleanup(curl); + } + + return ret; +} + +static int conn_upload_init(connection_info *conn); +static void conn_buffer_updated(connection_info *conn); +static connection_info *conn_new(global_io_info *global, ulong global_idx); +static void conn_cleanup(connection_info *conn); +static void conn_upload_retry(connection_info *conn); + +/* Check for completed transfers, and remove their easy handles */ +static void check_multi_info(global_io_info *g) +{ + char *eff_url; + CURLMsg *msg; + int msgs_left; + connection_info *conn; + CURL *easy; + + while ((msg = curl_multi_info_read(g->multi, &msgs_left))) { + if (msg->msg == CURLMSG_DONE) { + easy = msg->easy_handle; + curl_easy_getinfo(easy, CURLINFO_PRIVATE, &conn); + curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, + &eff_url); + curl_multi_remove_handle(g->multi, easy); + curl_easy_cleanup(easy); + conn->easy = NULL; + if (conn->chunk_acked) { + conn->chunk_uploaded = true; + fprintf(stderr, "%s is done\n", conn->hash); + } else { + fprintf(stderr, "error: chunk %zu '%s' %s " + "is not uploaded, but socket closed " + "(%zu bytes of %zu left to upload)\n", + conn->chunk_no, + conn->name, + conn->hash, + conn->chunk_size - conn->upload_size, + conn->chunk_size); + conn_upload_retry(conn); + } + } + } +} + +/* Die if we get a bad CURLMcode somewhere */ +static void mcode_or_die(const char *where, CURLMcode code) +{ + if (code != CURLM_OK) + { + const char *s; + switch (code) + { + case CURLM_BAD_HANDLE: + s = "CURLM_BAD_HANDLE"; + break; + case CURLM_BAD_EASY_HANDLE: + s = "CURLM_BAD_EASY_HANDLE"; + break; + case CURLM_OUT_OF_MEMORY: + s = "CURLM_OUT_OF_MEMORY"; + break; + case CURLM_INTERNAL_ERROR: + s = "CURLM_INTERNAL_ERROR"; + break; + case CURLM_UNKNOWN_OPTION: + s = "CURLM_UNKNOWN_OPTION"; + break; + case CURLM_LAST: + s = "CURLM_LAST"; + break; + default: + s = "CURLM_unknown"; + break; + case CURLM_BAD_SOCKET: + s = "CURLM_BAD_SOCKET"; + fprintf(stderr, "error: %s returns (%d) %s\n", + where, code, s); + /* ignore this error */ + return; + } + fprintf(stderr, "error: %s returns (%d) %s\n", + where, code, s); + assert(0); + } +} + +/* Called by libev when we get action on a multi socket */ +static void event_cb(EV_P_ struct ev_io *w, int revents) +{ + global_io_info *global = (global_io_info*)(w->data); + CURLMcode rc; + +#if !(OLD_CURL_MULTI) + int action = (revents & EV_READ ? CURL_POLL_IN : 0) | + (revents & EV_WRITE ? CURL_POLL_OUT : 0); + + do { + rc = curl_multi_socket_action(global->multi, w->fd, action, + &global->still_running); + } while (rc == CURLM_CALL_MULTI_PERFORM); +#else + do { + rc = curl_multi_socket(global->multi, w->fd, + &global->still_running); + } while (rc == CURLM_CALL_MULTI_PERFORM); +#endif + mcode_or_die("error: event_cb: curl_multi_socket_action", rc); + check_multi_info(global); + if (global->still_running <= 0) { + ev_timer_stop(global->loop, &global->timer_event); + } +} + +static void remsock(curl_socket_t s, socket_info *fdp, global_io_info *global) +{ + if (fdp) { + if (fdp->evset) { + ev_io_stop(global->loop, &fdp->ev); + } + free(fdp); + } +} + +static void setsock(socket_info *fdp, curl_socket_t s, CURL *easy, int action, + global_io_info *global) +{ + int kind = (action & CURL_POLL_IN ? (int)(EV_READ) : 0) | + (action & CURL_POLL_OUT ? (int)(EV_WRITE) : 0); + + fdp->sockfd = s; + fdp->action = action; + fdp->easy = easy; + if (fdp->evset) + ev_io_stop(global->loop, &fdp->ev); + ev_io_init(&fdp->ev, event_cb, fdp->sockfd, kind); + fdp->ev.data = global; + fdp->evset = 1; + ev_io_start(global->loop, &fdp->ev); +} + +static void addsock(curl_socket_t s, CURL *easy, int action, + global_io_info *global) +{ + socket_info *fdp = (socket_info *)(calloc(sizeof(socket_info), 1)); + + fdp->global = global; + setsock(fdp, s, easy, action, global); + curl_multi_assign(global->multi, s, fdp); +} + +static int sock_cb(CURL *easy, curl_socket_t s, int what, void *cbp, + void *sockp) +{ + global_io_info *global = (global_io_info*)(cbp); + socket_info *fdp = (socket_info*)(sockp); + + if (what == CURL_POLL_REMOVE) { + remsock(s, fdp, global); + } else { + if (!fdp) { + addsock(s, easy, what, global); + } else { + setsock(fdp, s, easy, what, global); + } + } + return 0; +} + +/* Called by libev when our timeout expires */ +static void timer_cb(EV_P_ struct ev_timer *w, int revents) +{ + global_io_info *io_global = (global_io_info*)(w->data); + CURLMcode rc; + +#if !(OLD_CURL_MULTI) + do { + rc = curl_multi_socket_action(io_global->multi, + CURL_SOCKET_TIMEOUT, 0, + &io_global->still_running); + } while (rc == CURLM_CALL_MULTI_PERFORM); +#else + do { + rc = curl_multi_socket_all(io_global->multi, + &io_global->still_running); + } while (rc == CURLM_CALL_MULTI_PERFORM); +#endif + mcode_or_die("timer_cb: curl_multi_socket_action", rc); + check_multi_info(io_global); +} + +static connection_info *get_current_connection(global_io_info *global) +{ + connection_info *conn = global->current_connection; + ulong i; + + if (conn && conn->filled_size < conn->chunk_size) + return conn; + + for (i = 0; i < opt_parallel; i++) { + conn = global->connections[i]; + if (conn->chunk_uploaded || conn->filled_size == 0) { + global->current_connection = conn; + conn_upload_init(conn); + return conn; + } + } + + return NULL; +} + +/* This gets called whenever data is received from the input */ +static void input_cb(EV_P_ struct ev_io *w, int revents) +{ + global_io_info *io_global = (global_io_info *)(w->data); + connection_info *conn = get_current_connection(io_global); + + if (conn == NULL) + return; + + if (conn->filled_size < conn->chunk_size) { + if (revents & EV_READ) { + ssize_t nbytes = read(io_global->input_fd, + conn->buffer + conn->filled_size, + conn->chunk_size - + conn->filled_size); + if (nbytes > 0) { + conn->filled_size += nbytes; + conn_buffer_updated(conn); + } else if (nbytes < 0) { + if (errno != EAGAIN && errno != EINTR) { + char error[200]; + my_strerror(error, sizeof(error), + errno); + fprintf(stderr, "error: failed to read " + "input stream (%s)\n", error); + /* failed to read input */ + exit(1); + } + } else { + io_global->eof = 1; + ev_io_stop(io_global->loop, w); + } + } + } + + assert(conn->filled_size <= conn->chunk_size); +} + +static int swift_upload_read_cb(char *ptr, size_t size, size_t nmemb, + void *data) +{ + size_t realsize; + + connection_info *conn = (connection_info*)(data); + + if (conn->filled_size == conn->upload_size && + conn->upload_size < conn->chunk_size && !conn->global->eof) { + ssize_t nbytes; + assert(conn->global->current_connection == conn); + do { + nbytes = read(conn->global->input_fd, + conn->buffer + conn->filled_size, + conn->chunk_size - conn->filled_size); + } while (nbytes == -1 && errno == EAGAIN); + if (nbytes > 0) { + conn->filled_size += nbytes; + conn_buffer_updated(conn); + } else { + conn->global->eof = 1; + } + } + + realsize = min(size * nmemb, conn->filled_size - conn->upload_size); + + memcpy(ptr, conn->buffer + conn->upload_size, realsize); + conn->upload_size += realsize; + + assert(conn->filled_size <= conn->chunk_size); + assert(conn->upload_size <= conn->filled_size); + + return realsize; +} + +static +size_t upload_header_read_cb(char *ptr, size_t size, size_t nmemb, + void *data) +{ + connection_info *conn = (connection_info *)(data); + char etag[33]; + + if (get_http_header("Etag: ", ptr, etag, array_elements(etag))) { + if (strcmp(conn->hash, etag) != 0) { + fprintf(stderr, "error: ETag mismatch\n"); + exit(EXIT_FAILURE); + } + fprintf(stderr, "acked chunk %s\n", etag); + conn->chunk_acked = true; + } + + return nmemb * size; +} + +static int conn_upload_init(connection_info *conn) +{ + conn->filled_size = 0; + conn->upload_size = 0; + conn->chunk_uploaded = false; + conn->chunk_acked = false; + conn->chunk_size = CHUNK_HEADER_CONSTANT_LEN; + conn->magic_verified = false; + conn->chunk_path_len = 0; + conn->chunk_type = XB_CHUNK_TYPE_UNKNOWN; + conn->payload_size = 0; + conn->upload_started = false; + conn->retry_count = 0; + if (conn->name != NULL) { + conn->name[0] = 0; + } + + if (conn->easy != NULL) { + conn->easy = 0; + } + + if (conn->slist != NULL) { + curl_slist_free_all(conn->slist); + conn->slist = NULL; + } + + return 0; +} + +static void conn_upload_prepare(connection_info *conn) +{ + gcry_md_hd_t md5; + + gcry_md_open(&md5, GCRY_MD_MD5, 0); + gcry_md_write(md5, conn->buffer, conn->chunk_size); + hex_md5(gcry_md_read(md5, GCRY_MD_MD5), conn->hash); + gcry_md_close(md5); +} + +static int conn_upload_start(connection_info *conn) +{ + char token_header[SWIFT_MAX_HDR_SIZE]; + char object_url[SWIFT_MAX_URL_SIZE]; + char content_len[200], etag[200]; + global_io_info *global; + CURLMcode rc; + + global = conn->global; + + fprintf(stderr, "uploading chunk %s/%s/%s.%020zu " + "(md5: %s, size: %zu)\n", + global->container, global->backup_name, conn->name, + conn->chunk_no, conn->hash, conn->chunk_size); + + snprintf(object_url, array_elements(object_url), "%s/%s/%s/%s.%020zu", + global->url, global->container, global->backup_name, + conn->name, conn->chunk_no); + + snprintf(content_len, sizeof(content_len), "Content-Length: %lu", + (ulong)(conn->chunk_size)); + + snprintf(etag, sizeof(etag), "ETag: %s", conn->hash); + + snprintf(token_header, array_elements(token_header), + "X-Auth-Token: %s", global->token); + + conn->slist = curl_slist_append(conn->slist, token_header); + conn->slist = curl_slist_append(conn->slist, + "Connection: keep-alive"); + conn->slist = curl_slist_append(conn->slist, + "Content-Type: " + "application/octet-stream"); + conn->slist = curl_slist_append(conn->slist, content_len); + conn->slist = curl_slist_append(conn->slist, etag); + + conn->easy = curl_easy_init(); + if (!conn->easy) { + fprintf(stderr, "error: curl_easy_init() failed\n"); + return 1; + } + curl_easy_setopt(conn->easy, CURLOPT_URL, object_url); + curl_easy_setopt(conn->easy, CURLOPT_READFUNCTION, + swift_upload_read_cb); + curl_easy_setopt(conn->easy, CURLOPT_READDATA, conn); + curl_easy_setopt(conn->easy, CURLOPT_VERBOSE, opt_verbose); + curl_easy_setopt(conn->easy, CURLOPT_ERRORBUFFER, conn->error); + curl_easy_setopt(conn->easy, CURLOPT_PRIVATE, conn); + curl_easy_setopt(conn->easy, CURLOPT_NOPROGRESS, 1L); + curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_TIME, 5L); + curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_LIMIT, 1024L); + curl_easy_setopt(conn->easy, CURLOPT_PUT, 1L); + curl_easy_setopt(conn->easy, CURLOPT_HTTPHEADER, conn->slist); + curl_easy_setopt(conn->easy, CURLOPT_HEADERFUNCTION, + upload_header_read_cb); + curl_easy_setopt(conn->easy, CURLOPT_HEADERDATA, conn); + curl_easy_setopt(conn->easy, CURLOPT_INFILESIZE, + (long) conn->chunk_size); + if (opt_cacert != NULL) + curl_easy_setopt(conn->easy, CURLOPT_CAINFO, opt_cacert); + if (opt_insecure) + curl_easy_setopt(conn->easy, CURLOPT_SSL_VERIFYPEER, FALSE); + + rc = curl_multi_add_handle(conn->global->multi, conn->easy); + mcode_or_die("conn_upload_init: curl_multi_add_handle", rc); + +#if (OLD_CURL_MULTI) + do { + rc = curl_multi_socket_all(global->multi, + &global->still_running); + } while(rc == CURLM_CALL_MULTI_PERFORM); +#endif + + conn->upload_started = true; + + return 0; +} + +static void conn_cleanup(connection_info *conn) +{ + if (conn) { + free(conn->name); + free(conn->buffer); + if (conn->slist) { + curl_slist_free_all(conn->slist); + conn->slist = NULL; + } + if (conn->easy) { + curl_easy_cleanup(conn->easy); + conn->easy = NULL; + } + } + free(conn); +} + +static void conn_upload_retry(connection_info *conn) +{ + /* already closed by cURL */ + conn->easy = NULL; + + if (conn->slist != NULL) { + curl_slist_free_all(conn->slist); + conn->slist = NULL; + } + + if (conn->retry_count++ > 3) { + fprintf(stderr, "error: retry count limit reached\n"); + exit(EXIT_FAILURE); + } + + fprintf(stderr, "warning: retrying to upload chunk %zu of '%s'\n", + conn->chunk_no, conn->name); + + conn->upload_size = 0; + + conn_upload_start(conn); +} + +static connection_info *conn_new(global_io_info *global, ulong global_idx) +{ + connection_info *conn; + + conn = (connection_info *)(calloc(1, sizeof(connection_info))); + if (conn == NULL) { + goto error; + } + + conn->global = global; + conn->global_idx = global_idx; + conn->buffer_size = SWIFT_CHUNK_SIZE; + if ((conn->buffer = (char *)(calloc(conn->buffer_size, 1))) == + NULL) { + goto error; + } + + return conn; + +error: + if (conn != NULL) { + conn_cleanup(conn); + } + + fprintf(stderr, "error: out of memory\n"); + exit(EXIT_FAILURE); + + return NULL; +} + +/*********************************************************************//** +Handle input buffer updates. Parse chunk header and set appropriate +buffer size. */ +static +void +conn_buffer_updated(connection_info *conn) +{ + bool ready_for_upload = false; + + /* chunk header */ + if (!conn->magic_verified && + conn->filled_size >= CHUNK_HEADER_CONSTANT_LEN) { + if (strncmp(XB_STREAM_CHUNK_MAGIC, conn->buffer, + sizeof(XB_STREAM_CHUNK_MAGIC) - 1) != 0) { + + fprintf(stderr, "Error: magic expected\n"); + exit(EXIT_FAILURE); + } + conn->magic_verified = true; + conn->chunk_path_len = uint4korr(conn->buffer + + PATH_LENGTH_OFFSET); + conn->chunk_type = (xb_chunk_type_t) + (conn->buffer[CHUNK_TYPE_OFFSET]); + conn->chunk_size = CHUNK_HEADER_CONSTANT_LEN + + conn->chunk_path_len; + if (conn->chunk_type != XB_CHUNK_TYPE_EOF) { + conn->chunk_size += 16; + } + } + + /* ordinary chunk */ + if (conn->magic_verified && + conn->payload_size == 0 && + conn->chunk_type != XB_CHUNK_TYPE_EOF && + conn->filled_size >= CHUNK_HEADER_CONSTANT_LEN + + conn->chunk_path_len + 16) { + + conn->payload_size = uint8korr(conn->buffer + + CHUNK_HEADER_CONSTANT_LEN + + conn->chunk_path_len); + + conn->chunk_size = conn->payload_size + 4 + 16 + + conn->chunk_path_len + + CHUNK_HEADER_CONSTANT_LEN; + + if (conn->name == NULL) { + conn->name = (char*)(malloc(conn->chunk_path_len + 1)); + } else if (conn->name_len < conn->chunk_path_len + 1) { + conn->name = (char*)(realloc(conn->name, + conn->chunk_path_len + 1)); + } + conn->name_len = conn->chunk_path_len + 1; + + memcpy(conn->name, conn->buffer + CHUNK_HEADER_CONSTANT_LEN, + conn->chunk_path_len); + conn->name[conn->chunk_path_len] = 0; + + if (conn->buffer_size < conn->chunk_size) { + conn->buffer = + (char *)(realloc(conn->buffer, conn->chunk_size)); + conn->buffer_size = conn->chunk_size; + } + } + + /* EOF chunk has no payload */ + if (conn->magic_verified && + conn->chunk_type == XB_CHUNK_TYPE_EOF && + conn->filled_size >= CHUNK_HEADER_CONSTANT_LEN + + conn->chunk_path_len) { + + if (conn->name == NULL) { + conn->name = (char*)(malloc(conn->chunk_path_len + 1)); + } else if (conn->name_len < conn->chunk_path_len + 1) { + conn->name = (char*)(realloc(conn->name, + conn->chunk_path_len + 1)); + } + conn->name_len = conn->chunk_path_len + 1; + + memcpy(conn->name, conn->buffer + CHUNK_HEADER_CONSTANT_LEN, + conn->chunk_path_len); + conn->name[conn->chunk_path_len] = 0; + } + + if (conn->filled_size > 0 && conn->filled_size == conn->chunk_size) { + ready_for_upload = true; + } + + /* start upload once recieved the size of the chunk */ + if (!conn->upload_started && ready_for_upload) { + conn->chunk_no = file_chunk_count[conn->name]++; + conn_upload_prepare(conn); + conn_upload_start(conn); + } +} + +static int init_input(global_io_info *io_global) +{ + ev_io_init(&io_global->input_event, input_cb, STDIN_FILENO, EV_READ); + io_global->input_event.data = io_global; + ev_io_start(io_global->loop, &io_global->input_event); + + return 0; +} + +/* Update the event timer after curl_multi library calls */ +static int multi_timer_cb(CURLM *multi, long timeout_ms, global_io_info *global) +{ + ev_timer_stop(global->loop, &global->timer_event); + if (timeout_ms > 0) { + double t = timeout_ms / 1000.0; + ev_timer_init(&global->timer_event, timer_cb, t, 0.); + ev_timer_start(global->loop, &global->timer_event); + } else { + timer_cb(global->loop, &global->timer_event, 0); + } + return 0; +} + +static +int swift_upload_parts(swift_auth_info *auth, const char *container, + const char *name) +{ + global_io_info io_global; + ulong i; +#if (OLD_CURL_MULTI) + long timeout; +#endif + CURLMcode rc; + int n_dirty_buffers; + + memset(&io_global, 0, sizeof(io_global)); + + io_global.loop = ev_default_loop(0); + init_input(&io_global); + io_global.multi = curl_multi_init(); + ev_timer_init(&io_global.timer_event, timer_cb, 0., 0.); + io_global.timer_event.data = &io_global; + io_global.connections = (connection_info **) + (calloc(opt_parallel, sizeof(connection_info))); + io_global.url = auth->url; + io_global.container = container; + io_global.backup_name = name; + io_global.token = auth->token; + for (i = 0; i < opt_parallel; i++) { + io_global.connections[i] = conn_new(&io_global, i); + } + + /* setup the generic multi interface options we want */ + curl_multi_setopt(io_global.multi, CURLMOPT_SOCKETFUNCTION, sock_cb); + curl_multi_setopt(io_global.multi, CURLMOPT_SOCKETDATA, &io_global); +#if !(OLD_CURL_MULTI) + curl_multi_setopt(io_global.multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb); + curl_multi_setopt(io_global.multi, CURLMOPT_TIMERDATA, &io_global); + do { + rc = curl_multi_socket_action(io_global.multi, + CURL_SOCKET_TIMEOUT, 0, + &io_global.still_running); + } while (rc == CURLM_CALL_MULTI_PERFORM); +#else + curl_multi_timeout(io_global.multi, &timeout); + if (timeout >= 0) { + multi_timer_cb(io_global.multi, timeout, &io_global); + } + do { + rc = curl_multi_socket_all(io_global.multi, &io_global.still_running); + } while(rc == CURLM_CALL_MULTI_PERFORM); +#endif + + ev_loop(io_global.loop, 0); + check_multi_info(&io_global); + curl_multi_cleanup(io_global.multi); + + n_dirty_buffers = 0; + for (i = 0; i < opt_parallel; i++) { + connection_info *conn = io_global.connections[i]; + if (conn && conn->upload_size != conn->filled_size) { + fprintf(stderr, "error: upload failed: %lu bytes left " + "in the buffer %s (uploaded = %d)\n", + (ulong)(conn->filled_size - conn->upload_size), + conn->name, conn->chunk_uploaded); + ++n_dirty_buffers; + } + } + + for (i = 0; i < opt_parallel; i++) { + if (io_global.connections[i] != NULL) { + conn_cleanup(io_global.connections[i]); + } + } + free(io_global.connections); + + if (n_dirty_buffers > 0) { + return(EXIT_FAILURE); + } + + return 0; +} + +struct download_buffer_info { + off_t offset; + size_t size; + size_t result_len; + char *buf; + curl_read_callback custom_header_callback; + void *custom_header_callback_data; +}; + +/*********************************************************************//** +Callback to parse header of GET request on swift contaier. */ +static +size_t fetch_buffer_header_cb(char *ptr, size_t size, size_t nmemb, + void *data) +{ + download_buffer_info *buffer_info = (download_buffer_info*)(data); + size_t buf_size; + char content_length_str[100]; + char *endptr; + + if (get_http_header("Content-Length: ", ptr, + content_length_str, sizeof(content_length_str))) { + + buf_size = strtoull(content_length_str, &endptr, 10); + + if (buffer_info->buf == NULL) { + buffer_info->buf = (char*)(malloc(buf_size)); + buffer_info->size = buf_size; + } + + if (buf_size > buffer_info->size) { + buffer_info->buf = (char*) + (realloc(buffer_info->buf, buf_size)); + buffer_info->size = buf_size; + } + + buffer_info->result_len = buf_size; + } + + if (buffer_info->custom_header_callback) { + buffer_info->custom_header_callback(ptr, size, nmemb, + buffer_info->custom_header_callback_data); + } + + return nmemb * size; +} + +/*********************************************************************//** +Write contents into string buffer */ +static +size_t +fetch_buffer_cb(char *buffer, size_t size, size_t nmemb, void *out_buffer) +{ + download_buffer_info *buffer_info = (download_buffer_info*)(out_buffer); + + assert(buffer_info->size >= buffer_info->offset + size * nmemb); + + memcpy(buffer_info->buf + buffer_info->offset, buffer, size * nmemb); + buffer_info->offset += size * nmemb; + + return size * nmemb; +} + + +/*********************************************************************//** +Downloads contents of URL into buffer. Caller is responsible for +deallocating the buffer. +@return pointer to a buffer or NULL */ +static +char * +swift_fetch_into_buffer(swift_auth_info *auth, const char *url, + char **buf, size_t *buf_size, size_t *result_len, + curl_read_callback header_callback, + void *header_callback_data) +{ + char auth_token[SWIFT_MAX_HDR_SIZE]; + download_buffer_info buffer_info; + struct curl_slist *slist = NULL; + long http_code; + CURL *curl; + CURLcode res; + + memset(&buffer_info, 0, sizeof(buffer_info)); + buffer_info.buf = *buf; + buffer_info.size = *buf_size; + buffer_info.custom_header_callback = header_callback; + buffer_info.custom_header_callback_data = header_callback_data; + + snprintf(auth_token, array_elements(auth_token), "X-Auth-Token: %s", + auth->token); + + curl = curl_easy_init(); + + if (curl != NULL) { + slist = curl_slist_append(slist, auth_token); + + curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fetch_buffer_cb); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer_info); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, + fetch_buffer_header_cb); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, + &buffer_info); + if (opt_cacert != NULL) + curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert); + if (opt_insecure) + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE); + + res = curl_easy_perform(curl); + + if (res != CURLE_OK) { + fprintf(stderr, + "error: curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); + goto cleanup; + } + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + if (http_code < 200 || http_code >= 300) { + fprintf(stderr, "error: request failed " + "with response code: %ld\n", http_code); + res = CURLE_LOGIN_DENIED; + goto cleanup; + } + } else { + res = CURLE_FAILED_INIT; + fprintf(stderr, "error: curl_easy_init() failed\n"); + goto cleanup; + } + +cleanup: + if (slist) { + curl_slist_free_all(slist); + } + if (curl) { + curl_easy_cleanup(curl); + } + + if (res == CURLE_OK) { + *buf = buffer_info.buf; + *buf_size = buffer_info.size; + *result_len = buffer_info.result_len; + return(buffer_info.buf); + } + + free(buffer_info.buf); + *buf = NULL; + *buf_size = 0; + *result_len = 0; + + return(NULL); +} + +static +container_list * +container_list_new() +{ + container_list *list = + (container_list *)(calloc(1, sizeof(container_list))); + + list->object_count = 1000; + list->objects = (object_info*) + (calloc(list->object_count, sizeof(object_info))); + + if (list->objects == NULL) { + fprintf(stderr, "error: out of memory\n"); + free(list); + return(NULL); + } + + return(list); +} + +static +void +container_list_free(container_list *list) +{ + free(list->content_json); + free(list->objects); + free(list); +} + +static +void +container_list_add_object(container_list *list, const char *name, + const char *hash, size_t bytes) +{ + const size_t object_count_step = 1000; + + if (list->idx >= list->object_count) { + list->objects = (object_info*) + realloc(list->objects, + (list->object_count + object_count_step) * + sizeof(object_info)); + memset(list->objects + list->object_count, 0, + object_count_step * sizeof(object_info)); + list->object_count += object_count_step; + } + assert(list->idx <= list->object_count); + strcpy(list->objects[list->idx].name, name); + strcpy(list->objects[list->idx].hash, hash); + list->objects[list->idx].bytes = bytes; + ++list->idx; +} + + +/*********************************************************************//** +Tokenize json string. Return array of tokens. Caller is responsoble for +deallocating the array. */ +jsmntok_t * +json_tokenise(char *json, size_t len, int initial_tokens) +{ + jsmn_parser parser; + jsmn_init(&parser); + + unsigned int n = initial_tokens; + jsmntok_t *tokens = (jsmntok_t *)(malloc(sizeof(jsmntok_t) * n)); + + int ret = jsmn_parse(&parser, json, len, tokens, n); + + while (ret == JSMN_ERROR_NOMEM) + { + n = n * 2 + 1; + tokens = (jsmntok_t*)(realloc(tokens, sizeof(jsmntok_t) * n)); + ret = jsmn_parse(&parser, json, len, tokens, n); + } + + if (ret == JSMN_ERROR_INVAL) { + fprintf(stderr, "error: invalid JSON string\n"); + + } + if (ret == JSMN_ERROR_PART) { + fprintf(stderr, "error: truncated JSON string\n"); + } + + return tokens; +} + +/*********************************************************************//** +Return true if token representation equal to given string. */ +static +bool +json_token_eq(const char *buf, jsmntok_t *t, const char *s) +{ + size_t len = strlen(s); + + assert(t->end > t->start); + + return((size_t)(t->end - t->start) == len && + (strncmp(buf + t->start, s, len) == 0)); +} + +/*********************************************************************//** +Copy given token as string. */ +static +bool +json_token_str(const char *buf, jsmntok_t *t, char *out, int out_size) +{ + size_t len = min(t->end - t->start, out_size - 1); + + memcpy(out, buf + t->start, len); + out[len] = 0; + + return(true); +} + +/*********************************************************************//** +Parse SWIFT container list response and fill output array with values +sorted by object name. */ +static +bool +swift_parse_container_list(container_list *list) +{ + enum {MAX_DEPTH=20}; + enum label_t {NONE, OBJECT}; + + char name[SWIFT_MAX_URL_SIZE]; + char hash[33]; + char bytes[30]; + char *response = list->content_json; + + struct stack_t { + jsmntok_t *t; + int n_items; + label_t label; + }; + + stack_t stack[MAX_DEPTH]; + jsmntok_t *tokens; + int level; + size_t count = 0; + + tokens = json_tokenise(list->content_json, list->content_length, 200); + + stack[0].t = &tokens[0]; + stack[0].label = NONE; + stack[0].n_items = 1; + level = 0; + + for (size_t i = 0, j = 1; j > 0; i++, j--) { + jsmntok_t *t = &tokens[i]; + + assert(t->start != -1 && t->end != -1); + assert(level >= 0); + + --stack[level].n_items; + + switch (t->type) { + case JSMN_ARRAY: + case JSMN_OBJECT: + if (level < MAX_DEPTH - 1) { + level++; + } + stack[level].t = t; + stack[level].label = NONE; + if (t->type == JSMN_ARRAY) { + stack[level].n_items = t->size; + j += t->size; + } else { + stack[level].n_items = t->size * 2; + j += t->size * 2; + } + break; + case JSMN_PRIMITIVE: + case JSMN_STRING: + if (stack[level].t->type == JSMN_OBJECT && + stack[level].n_items % 2 == 1) { + /* key */ + if (json_token_eq(response, t, "name")) { + json_token_str(response, &tokens[i + 1], + name, sizeof(name)); + } + if (json_token_eq(response, t, "hash")) { + json_token_str(response, &tokens[i + 1], + hash, sizeof(hash)); + } + if (json_token_eq(response, t, "bytes")) { + json_token_str(response, &tokens[i + 1], + bytes, sizeof(bytes)); + } + } + break; + } + + while (stack[level].n_items == 0 && level > 0) { + if (stack[level].t->type == JSMN_OBJECT + && level == 2) { + char *endptr; + container_list_add_object(list, name, hash, + strtoull(bytes, &endptr, 10)); + ++count; + } + --level; + } + } + + if (count == 0) { + list->final = true; + } + + free(tokens); + + return(true); +} + +/*********************************************************************//** +List swift container with given name. Return list of objects sorted by +object name. */ +static +container_list * +swift_list(swift_auth_info *auth, const char *container, const char *path) +{ + container_list *list; + char url[SWIFT_MAX_URL_SIZE]; + + list = container_list_new(); + + while (!list->final) { + + /* download the list in json format */ + snprintf(url, array_elements(url), + "%s/%s?format=json&limit=1000%s%s%s%s", + auth->url, container, path ? "&prefix=" : "", + path ? path : "", list->idx > 0 ? "&marker=" : "", + list->idx > 0 ? + list->objects[list->idx - 1].name : ""); + + list->content_json = swift_fetch_into_buffer(auth, url, + &list->content_json, &list->content_bufsize, + &list->content_length, NULL, NULL); + + if (list->content_json == NULL) { + container_list_free(list); + return(NULL); + } + + /* parse downloaded list */ + if (!swift_parse_container_list(list)) { + fprintf(stderr, "error: unable to parse " + "container list\n"); + container_list_free(list); + return(NULL); + } + } + + return(list); +} + + +/*********************************************************************//** +Return true if chunk is a part of backup with given name. */ +static +bool +chunk_belongs_to(const char *chunk_name, const char *backup_name) +{ + size_t backup_name_len = strlen(backup_name); + + return((strlen(chunk_name) > backup_name_len) + && (chunk_name[backup_name_len] == '/') + && strncmp(chunk_name, backup_name, backup_name_len) == 0); +} + +/*********************************************************************//** +Return true if chunk is in given list. */ +static +bool +chunk_in_list(const char *chunk_name, char **list, int list_size) +{ + size_t chunk_name_len; + + if (list_size == 0) { + return(true); + } + + chunk_name_len = strlen(chunk_name); + if (chunk_name_len < 20) { + return(false); + } + + for (int i = 0; i < list_size; i++) { + size_t item_len = strlen(list[i]); + + if ((strncmp(chunk_name - item_len + chunk_name_len - 21, + list[i], item_len) == 0) + && (chunk_name[chunk_name_len - 21] == '.') + && (chunk_name[chunk_name_len - item_len - 22] == '/')) { + return(true); + } + } + + return(false); +} + +static +int swift_download(swift_auth_info *auth, const char *container, + const char *name) +{ + container_list *list; + char *buf = NULL; + size_t buf_size = 0; + size_t result_len = 0; + + if ((list = swift_list(auth, container, name)) == NULL) { + return(CURLE_FAILED_INIT); + } + + for (size_t i = 0; i < list->idx; i++) { + const char *chunk_name = list->objects[i].name; + + if (chunk_belongs_to(chunk_name, name) + && chunk_in_list(chunk_name, file_list, file_list_size)) { + char url[SWIFT_MAX_URL_SIZE]; + + snprintf(url, sizeof(url), "%s/%s/%s", + auth->url, container, chunk_name); + + if ((buf = swift_fetch_into_buffer( + auth, url, &buf, &buf_size, &result_len, + NULL, NULL)) == NULL) { + fprintf(stderr, "error: failed to download " + "chunk %s\n", chunk_name); + container_list_free(list); + return(CURLE_FAILED_INIT); + } + + fwrite(buf, 1, result_len, stdout); + } + } + + free(buf); + + container_list_free(list); + + return(CURLE_OK); +} + + +/*********************************************************************//** +Delete backup with given name from given container. +@return true if backup deleted successfully */ +static +bool swift_delete(swift_auth_info *auth, const char *container, + const char *name) +{ + container_list *list; + + if ((list = swift_list(auth, container, name)) == NULL) { + return(CURLE_FAILED_INIT); + } + + for (size_t i = 0; i < list->object_count; i++) { + const char *chunk_name = list->objects[i].name; + + if (chunk_belongs_to(chunk_name, name)) { + char url[SWIFT_MAX_URL_SIZE]; + + snprintf(url, sizeof(url), "%s/%s/%s", + auth->url, container, chunk_name); + + fprintf(stderr, "delete %s\n", chunk_name); + if (!swift_delete_object(auth, url)) { + fprintf(stderr, "error: failed to delete " + "chunk %s\n", chunk_name); + container_list_free(list); + return(CURLE_FAILED_INIT); + } + } + } + + container_list_free(list); + + return(CURLE_OK); +} + +/*********************************************************************//** +Check if backup with given name exists. +@return true if backup exists */ +static +bool swift_backup_exists(swift_auth_info *auth, const char *container, + const char *backup_name) +{ + container_list *list; + + if ((list = swift_list(auth, container, backup_name)) == NULL) { + fprintf(stderr, "error: unable to list container %s\n", + container); + exit(EXIT_FAILURE); + } + + for (size_t i = 0; i < list->object_count; i++) { + if (chunk_belongs_to(list->objects[i].name, backup_name)) { + container_list_free(list); + return(true); + } + } + + container_list_free(list); + + return(false); +} + +/*********************************************************************//** +Fills auth_info with response from keystone response. +@return true is response parsed successfully */ +static +bool +swift_parse_keystone_response_v2(char *response, size_t response_length, + swift_auth_info *auth_info) +{ + enum {MAX_DEPTH=20}; + enum label_t {NONE, ACCESS, CATALOG, ENDPOINTS, TOKEN}; + + char filtered_url[SWIFT_MAX_URL_SIZE]; + char public_url[SWIFT_MAX_URL_SIZE]; + char region[SWIFT_MAX_URL_SIZE]; + char id[SWIFT_MAX_URL_SIZE]; + char token_id[SWIFT_MAX_URL_SIZE]; + char type[SWIFT_MAX_URL_SIZE]; + + struct stack_t { + jsmntok_t *t; + int n_items; + label_t label; + }; + + stack_t stack[MAX_DEPTH]; + jsmntok_t *tokens; + int level; + + tokens = json_tokenise(response, response_length, 200); + + stack[0].t = &tokens[0]; + stack[0].label = NONE; + stack[0].n_items = 1; + level = 0; + + for (size_t i = 0, j = 1; j > 0; i++, j--) { + jsmntok_t *t = &tokens[i]; + + assert(t->start != -1 && t->end != -1); + assert(level >= 0); + + --stack[level].n_items; + + switch (t->type) { + case JSMN_ARRAY: + case JSMN_OBJECT: + if (level < MAX_DEPTH - 1) { + level++; + } + stack[level].t = t; + stack[level].label = NONE; + if (t->type == JSMN_ARRAY) { + stack[level].n_items = t->size; + j += t->size; + } else { + stack[level].n_items = t->size * 2; + j += t->size * 2; + } + break; + case JSMN_PRIMITIVE: + case JSMN_STRING: + if (stack[level].t->type == JSMN_OBJECT && + stack[level].n_items % 2 == 1) { + /* key */ + if (json_token_eq(response, t, "access")) { + stack[level].label = ACCESS; + } + if (json_token_eq(response, t, + "serviceCatalog")) { + stack[level].label = CATALOG; + } + if (json_token_eq(response, t, "endpoints")) { + stack[level].label = ENDPOINTS; + } + if (json_token_eq(response, t, "token")) { + stack[level].label = TOKEN; + } + if (json_token_eq(response, t, "id")) { + json_token_str(response, &tokens[i + 1], + id, sizeof(id)); + } + if (json_token_eq(response, t, "id") + && stack[level - 1].label == TOKEN) { + json_token_str(response, &tokens[i + 1], + token_id, sizeof(token_id)); + } + if (json_token_eq(response, t, "region")) { + json_token_str(response, &tokens[i + 1], + region, sizeof(region)); + } + if (json_token_eq(response, t, "publicURL")) { + json_token_str(response, &tokens[i + 1], + public_url, sizeof(public_url)); + } + if (json_token_eq(response, t, "type")) { + json_token_str(response, &tokens[i + 1], + type, sizeof(type)); + } + } + break; + } + + while (stack[level].n_items == 0 && level > 0) { + if (stack[level].t->type == JSMN_OBJECT + && level == 6 + && stack[level - 1].t->type == JSMN_ARRAY + && stack[level - 2].label == ENDPOINTS) { + if (opt_swift_region == NULL + || strcmp(opt_swift_region, region) == 0) { + strncpy(filtered_url, public_url, + sizeof(filtered_url)); + } + } + if (stack[level].t->type == JSMN_OBJECT && + level == 4 && + stack[level - 1].t->type == JSMN_ARRAY && + stack[level - 2].label == CATALOG) { + if (strcmp(type, "object-store") == 0) { + strncpy(auth_info->url, filtered_url, + sizeof(auth_info->url)); + } + } + --level; + } + } + + free(tokens); + + strncpy(auth_info->token, token_id, sizeof(auth_info->token)); + + assert(level == 0); + + if (*auth_info->token == 0) { + fprintf(stderr, "error: can not receive token from response\n"); + return(false); + } + + if (*auth_info->url == 0) { + fprintf(stderr, "error: can not get URL from response\n"); + return(false); + } + + return(true); +} + +/*********************************************************************//** +Authenticate against Swift TempAuth. Fills swift_auth_info struct. +Uses creadentials privided as global variables. +@returns true if access is granted and token received. */ +static +bool +swift_keystone_auth_v2(const char *auth_url, swift_auth_info *info) +{ + char tenant_arg[SWIFT_MAX_URL_SIZE]; + char payload[SWIFT_MAX_URL_SIZE]; + struct curl_slist *slist = NULL; + download_buffer_info buf_info; + long http_code; + CURLcode res; + CURL *curl; + bool auth_res = false; + + memset(&buf_info, 0, sizeof(buf_info)); + + if (opt_swift_user == NULL) { + fprintf(stderr, "error: both --swift-user is required " + "for keystone authentication.\n"); + return(false); + } + + if (opt_swift_password == NULL) { + fprintf(stderr, "error: both --swift-password is required " + "for keystone authentication.\n"); + return(false); + } + + if (opt_swift_tenant != NULL && opt_swift_tenant_id != NULL) { + fprintf(stderr, "error: both --swift-tenant and " + "--swift-tenant-id specified for keystone " + "authentication.\n"); + return(false); + } + + if (opt_swift_tenant != NULL) { + snprintf(tenant_arg, sizeof(tenant_arg), ",\"%s\":\"%s\"", + "tenantName", opt_swift_tenant); + } else if (opt_swift_tenant_id != NULL) { + snprintf(tenant_arg, sizeof(tenant_arg), ",\"%s\":\"%s\"", + "tenantId", opt_swift_tenant_id); + } else { + *tenant_arg = 0; + } + + snprintf(payload, sizeof(payload), "{\"auth\": " + "{\"passwordCredentials\": {\"username\":\"%s\"," + "\"password\":\"%s\"}%s}}", + opt_swift_user, opt_swift_password, tenant_arg); + + curl = curl_easy_init(); + + if (curl != NULL) { + + slist = curl_slist_append(slist, + "Content-Type: application/json"); + slist = curl_slist_append(slist, + "Accept: application/json"); + + curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_URL, auth_url); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fetch_buffer_cb); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf_info); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, + fetch_buffer_header_cb); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, + &buf_info); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); + + if (opt_cacert != NULL) + curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert); + if (opt_insecure) + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE); + + res = curl_easy_perform(curl); + + if (res != CURLE_OK) { + fprintf(stderr, + "error: curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); + goto cleanup; + } + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + if (http_code < 200 || http_code >= 300) { + fprintf(stderr, "error: request failed " + "with response code: %ld\n", http_code); + res = CURLE_LOGIN_DENIED; + goto cleanup; + } + } else { + res = CURLE_FAILED_INIT; + fprintf(stderr, "error: curl_easy_init() failed\n"); + goto cleanup; + } + + if (!swift_parse_keystone_response_v2(buf_info.buf, + buf_info.size, info)) { + goto cleanup; + } + + auth_res = true; + +cleanup: + if (slist) { + curl_slist_free_all(slist); + } + if (curl) { + curl_easy_cleanup(curl); + } + + free(buf_info.buf); + + return(auth_res); +} + + +/*********************************************************************//** +Fills auth_info with response from keystone response. +@return true is response parsed successfully */ +static +bool +swift_parse_keystone_response_v3(char *response, size_t response_length, + swift_auth_info *auth_info) +{ + enum {MAX_DEPTH=20}; + enum label_t {NONE, TOKEN, CATALOG, ENDPOINTS}; + + char url[SWIFT_MAX_URL_SIZE]; + char filtered_url[SWIFT_MAX_URL_SIZE]; + char region[SWIFT_MAX_URL_SIZE]; + char interface[SWIFT_MAX_URL_SIZE]; + char type[SWIFT_MAX_URL_SIZE]; + + struct stack_t { + jsmntok_t *t; + int n_items; + label_t label; + }; + + stack_t stack[MAX_DEPTH]; + jsmntok_t *tokens; + int level; + + tokens = json_tokenise(response, response_length, 200); + + stack[0].t = &tokens[0]; + stack[0].label = NONE; + stack[0].n_items = 1; + level = 0; + + for (size_t i = 0, j = 1; j > 0; i++, j--) { + jsmntok_t *t = &tokens[i]; + + assert(t->start != -1 && t->end != -1); + assert(level >= 0); + + --stack[level].n_items; + + switch (t->type) { + case JSMN_ARRAY: + case JSMN_OBJECT: + if (level < MAX_DEPTH - 1) { + level++; + } + stack[level].t = t; + stack[level].label = NONE; + if (t->type == JSMN_ARRAY) { + stack[level].n_items = t->size; + j += t->size; + } else { + stack[level].n_items = t->size * 2; + j += t->size * 2; + } + break; + case JSMN_PRIMITIVE: + case JSMN_STRING: + if (stack[level].t->type == JSMN_OBJECT && + stack[level].n_items % 2 == 1) { + /* key */ + if (json_token_eq(response, t, "token")) { + stack[level].label = TOKEN; + fprintf(stderr, "token\n"); + } + if (json_token_eq(response, t, + "catalog")) { + stack[level].label = CATALOG; + fprintf(stderr, "catalog\n"); + } + if (json_token_eq(response, t, "endpoints")) { + stack[level].label = ENDPOINTS; + } + if (json_token_eq(response, t, "region")) { + json_token_str(response, &tokens[i + 1], + region, sizeof(region)); + } + if (json_token_eq(response, t, "url")) { + json_token_str(response, &tokens[i + 1], + url, sizeof(url)); + } + if (json_token_eq(response, t, "interface")) { + json_token_str(response, &tokens[i + 1], + interface, sizeof(interface)); + } + if (json_token_eq(response, t, "type")) { + json_token_str(response, &tokens[i + 1], + type, sizeof(type)); + } + } + break; + } + + while (stack[level].n_items == 0 && level > 0) { + if (stack[level].t->type == JSMN_OBJECT + && level == 6 + && stack[level - 1].t->type == JSMN_ARRAY + && stack[level - 2].label == ENDPOINTS) { + if ((opt_swift_region == NULL + || strcmp(opt_swift_region, region) == 0) + && strcmp(interface, "public") == 0) { + strncpy(filtered_url, url, + sizeof(filtered_url)); + } + } + if (stack[level].t->type == JSMN_OBJECT && + level == 4 && + stack[level - 1].t->type == JSMN_ARRAY && + stack[level - 2].label == CATALOG) { + if (strcmp(type, "object-store") == 0) { + strncpy(auth_info->url, filtered_url, + sizeof(auth_info->url)); + } + } + --level; + } + } + + free(tokens); + + assert(level == 0); + + if (*auth_info->url == 0) { + fprintf(stderr, "error: can not get URL from response\n"); + return(false); + } + + return(true); +} + +/*********************************************************************//** +Captures X-Subject-Token header. */ +static +size_t keystone_v3_header_cb(char *ptr, size_t size, size_t nmemb, void *data) +{ + swift_auth_info *info = (swift_auth_info*)(data); + + get_http_header("X-Subject-Token: ", ptr, + info->token, array_elements(info->token)); + + return nmemb * size; +} + +/*********************************************************************//** +Authenticate against Swift TempAuth. Fills swift_auth_info struct. +Uses creadentials privided as global variables. +@returns true if access is granted and token received. */ +static +bool +swift_keystone_auth_v3(const char *auth_url, swift_auth_info *info) +{ + char scope[SWIFT_MAX_URL_SIZE]; + char domain[SWIFT_MAX_URL_SIZE]; + char payload[SWIFT_MAX_URL_SIZE]; + struct curl_slist *slist = NULL; + download_buffer_info buf_info; + long http_code; + CURLcode res; + CURL *curl; + bool auth_res = false; + + memset(&buf_info, 0, sizeof(buf_info)); + buf_info.custom_header_callback = keystone_v3_header_cb; + buf_info.custom_header_callback_data = info; + + if (opt_swift_user == NULL) { + fprintf(stderr, "error: both --swift-user is required " + "for keystone authentication.\n"); + return(false); + } + + if (opt_swift_password == NULL) { + fprintf(stderr, "error: both --swift-password is required " + "for keystone authentication.\n"); + return(false); + } + + if (opt_swift_project_id != NULL && opt_swift_project != NULL) { + fprintf(stderr, "error: both --swift-project and " + "--swift-project-id specified for keystone " + "authentication.\n"); + return(false); + } + + if (opt_swift_domain_id != NULL && opt_swift_domain != NULL) { + fprintf(stderr, "error: both --swift-domain and " + "--swift-domain-id specified for keystone " + "authentication.\n"); + return(false); + } + + if (opt_swift_project_id != NULL && opt_swift_domain != NULL) { + fprintf(stderr, "error: both --swift-project-id and " + "--swift-domain specified for keystone " + "authentication.\n"); + return(false); + } + + if (opt_swift_project_id != NULL && opt_swift_domain_id != NULL) { + fprintf(stderr, "error: both --swift-project-id and " + "--swift-domain-id specified for keystone " + "authentication.\n"); + return(false); + } + + scope[0] = 0; domain[0] = 0; + + if (opt_swift_domain != NULL) { + snprintf(domain, sizeof(domain), + ",{\"domain\":{\"name\":\"%s\"}}", + opt_swift_domain); + } else if (opt_swift_domain_id != NULL) { + snprintf(domain, sizeof(domain), + ",{\"domain\":{\"id\":\"%s\"}}", + opt_swift_domain_id); + } + + if (opt_swift_project_id != NULL) { + snprintf(scope, sizeof(scope), + ",\"scope\":{\"project\":{\"id\":\"%s\"}}", + opt_swift_project_id); + } else if (opt_swift_project != NULL) { + snprintf(scope, sizeof(scope), + ",\"scope\":{\"project\":{\"name\":\"%s\"%s}}", + opt_swift_project_id, domain); + } + + snprintf(payload, sizeof(payload), "{\"auth\":{\"identity\":" + "{\"methods\":[\"password\"],\"password\":{\"user\":" + "{\"name\":\"%s\",\"password\":\"%s\"%s}}}%s}}", + opt_swift_user, opt_swift_password, + *scope ? "" : ",\"domain\":{\"id\":\"default\"}", + scope); + + curl = curl_easy_init(); + + if (curl != NULL) { + + slist = curl_slist_append(slist, + "Content-Type: application/json"); + slist = curl_slist_append(slist, + "Accept: application/json"); + + curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_URL, auth_url); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fetch_buffer_cb); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf_info); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, + fetch_buffer_header_cb); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, + &buf_info); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); + + if (opt_cacert != NULL) + curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert); + if (opt_insecure) + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE); + + res = curl_easy_perform(curl); + + if (res != CURLE_OK) { + fprintf(stderr, + "error: curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); + goto cleanup; + } + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + if (http_code < 200 || http_code >= 300) { + fprintf(stderr, "error: request failed " + "with response code: %ld\n", http_code); + res = CURLE_LOGIN_DENIED; + goto cleanup; + } + } else { + res = CURLE_FAILED_INIT; + fprintf(stderr, "error: curl_easy_init() failed\n"); + goto cleanup; + } + + if (!swift_parse_keystone_response_v3(buf_info.buf, + buf_info.size, info)) { + goto cleanup; + } + + auth_res = true; + +cleanup: + if (slist) { + curl_slist_free_all(slist); + } + if (curl) { + curl_easy_cleanup(curl); + } + + free(buf_info.buf); + + return(auth_res); +} + +int main(int argc, char **argv) +{ + swift_auth_info info; + char auth_url[SWIFT_MAX_URL_SIZE]; + + MY_INIT(argv[0]); + + /* handle_options in parse_args is destructive so + * we make a copy of our argument pointers so we can + * mask the sensitive values afterwards */ + char **mask_argv = (char **)malloc(sizeof(char *) * (argc - 1)); + memcpy(mask_argv, argv + 1, sizeof(char *) * (argc - 1)); + + if (parse_args(argc, argv)) { + return(EXIT_FAILURE); + } + + mask_args(argc, mask_argv); /* mask args on cmdline */ + + curl_global_init(CURL_GLOBAL_ALL); + + if (opt_swift_auth_version == NULL || *opt_swift_auth_version == '1') { + /* TempAuth */ + snprintf(auth_url, SWIFT_MAX_URL_SIZE, "%sauth/v%s/", + opt_swift_auth_url, opt_swift_auth_version ? + opt_swift_auth_version : "1.0"); + + if (!swift_temp_auth(auth_url, &info)) { + fprintf(stderr, "error: failed to authenticate\n"); + return(EXIT_FAILURE); + } + + } else if (*opt_swift_auth_version == '2') { + /* Keystone v2 */ + snprintf(auth_url, SWIFT_MAX_URL_SIZE, "%sv%s/tokens", + opt_swift_auth_url, opt_swift_auth_version); + + if (!swift_keystone_auth_v2(auth_url, &info)) { + fprintf(stderr, "error: failed to authenticate\n"); + return(EXIT_FAILURE); + } + + } else if (*opt_swift_auth_version == '3') { + /* Keystone v3 */ + snprintf(auth_url, SWIFT_MAX_URL_SIZE, "%sv%s/auth/tokens", + opt_swift_auth_url, opt_swift_auth_version); + + if (!swift_keystone_auth_v3(auth_url, &info)) { + fprintf(stderr, "error: failed to authenticate\n"); + exit(EXIT_FAILURE); + } + + } + + if (opt_swift_storage_url != NULL) { + snprintf(info.url, sizeof(info.url), "%s", + opt_swift_storage_url); + } + + fprintf(stderr, "Object store URL: %s\n", info.url); + + if (opt_mode == MODE_PUT) { + + if (swift_create_container(&info, opt_swift_container) != 0) { + fprintf(stderr, "error: failed to create " + "container %s\n", + opt_swift_container); + return(EXIT_FAILURE); + } + + if (swift_backup_exists(&info, opt_swift_container, opt_name)) { + fprintf(stderr, "error: backup named '%s' " + "already exists!\n", + opt_name); + return(EXIT_FAILURE); + } + + if (swift_upload_parts(&info, opt_swift_container, + opt_name) != 0) { + fprintf(stderr, "error: upload failed\n"); + return(EXIT_FAILURE); + } + + } else if (opt_mode == MODE_GET) { + + if (swift_download(&info, opt_swift_container, opt_name) + != CURLE_OK) { + fprintf(stderr, "error: download failed\n"); + return(EXIT_FAILURE); + } + + } else if (opt_mode == MODE_DELETE) { + + if (swift_delete(&info, opt_swift_container, opt_name) + != CURLE_OK) { + fprintf(stderr, "error: delete failed\n"); + return(EXIT_FAILURE); + } + + } else { + fprintf(stderr, "Unknown command supplied.\n"); + exit(EXIT_FAILURE); + } + + curl_global_cleanup(); + + return(EXIT_SUCCESS); +} diff --git a/extra/mariabackup/xbcrypt.c b/extra/mariabackup/xbcrypt.c new file mode 100644 index 00000000000..3da70e171f7 --- /dev/null +++ b/extra/mariabackup/xbcrypt.c @@ -0,0 +1,696 @@ +/****************************************************** +Copyright (c) 2013 Percona LLC and/or its affiliates. + +The xbcrypt utility: decrypt files in the XBCRYPT format. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#include <my_base.h> +#include <my_getopt.h> +#include "common.h" +#include "xbcrypt.h" +#include "xbcrypt_common.h" +#include "crc_glue.h" + +#if !defined(GCRYPT_VERSION_NUMBER) || (GCRYPT_VERSION_NUMBER < 0x010600) +GCRY_THREAD_OPTION_PTHREAD_IMPL; +#endif + +#define XBCRYPT_VERSION "1.1" + +typedef enum { + RUN_MODE_NONE, + RUN_MODE_ENCRYPT, + RUN_MODE_DECRYPT +} run_mode_t; + +const char *xbcrypt_encrypt_algo_names[] = +{ "NONE", "AES128", "AES192", "AES256", NullS}; +TYPELIB xbcrypt_encrypt_algo_typelib= +{array_elements(xbcrypt_encrypt_algo_names)-1,"", + xbcrypt_encrypt_algo_names, NULL}; + +static run_mode_t opt_run_mode = RUN_MODE_ENCRYPT; +static char *opt_input_file = NULL; +static char *opt_output_file = NULL; +static ulong opt_encrypt_algo; +static char *opt_encrypt_key_file = NULL; +static void *opt_encrypt_key = NULL; +static ulonglong opt_encrypt_chunk_size = 0; +static my_bool opt_verbose = FALSE; + +static uint encrypt_algos[] = { GCRY_CIPHER_NONE, + GCRY_CIPHER_AES128, + GCRY_CIPHER_AES192, + GCRY_CIPHER_AES256 }; +static int encrypt_algo = 0; +static int encrypt_mode = GCRY_CIPHER_MODE_CTR; +static uint encrypt_key_len = 0; +static size_t encrypt_iv_len = 0; + +static struct my_option my_long_options[] = +{ + {"help", '?', "Display this help and exit.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"decrypt", 'd', "Decrypt data input to output.", + 0, 0, 0, + GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"input", 'i', "Optional input file. If not specified, input" + " will be read from standard input.", + &opt_input_file, &opt_input_file, 0, + GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"output", 'o', "Optional output file. If not specified, output" + " will be written to standard output.", + &opt_output_file, &opt_output_file, 0, + GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"encrypt-algo", 'a', "Encryption algorithm.", + &opt_encrypt_algo, &opt_encrypt_algo, &xbcrypt_encrypt_algo_typelib, + GET_ENUM, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"encrypt-key", 'k', "Encryption key.", + &opt_encrypt_key, &opt_encrypt_key, 0, + GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"encrypt-key-file", 'f', "File which contains encryption key.", + &opt_encrypt_key_file, &opt_encrypt_key_file, 0, + GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"encrypt-chunk-size", 's', "Size of working buffer for encryption in" + " bytes. The default value is 64K.", + &opt_encrypt_chunk_size, &opt_encrypt_chunk_size, 0, + GET_ULL, REQUIRED_ARG, (1 << 16), 1024, ULONGLONG_MAX, 0, 0, 0}, + + {"verbose", 'v', "Display verbose status output.", + &opt_verbose, &opt_verbose, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + +static +int +get_options(int *argc, char ***argv); + +static +my_bool +get_one_option(int optid, const struct my_option *opt __attribute__((unused)), + char *argument __attribute__((unused))); + +static +void +print_version(void); + +static +void +usage(void); + +static +int +mode_decrypt(File filein, File fileout); + +static +int +mode_encrypt(File filein, File fileout); + +int +main(int argc, char **argv) +{ +#if !defined(GCRYPT_VERSION_NUMBER) || (GCRYPT_VERSION_NUMBER < 0x010600) + gcry_error_t gcry_error; +#endif + File filein = 0; + File fileout = 0; + + MY_INIT(argv[0]); + + crc_init(); + + if (get_options(&argc, &argv)) { + goto err; + } + + /* Acording to gcrypt docs (and my testing), setting up the threading + callbacks must be done first, so, lets give it a shot */ +#if !defined(GCRYPT_VERSION_NUMBER) || (GCRYPT_VERSION_NUMBER < 0x010600) + gcry_error = gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); + if (gcry_error) { + msg("%s: unable to set libgcrypt thread cbs - " + "%s : %s\n", my_progname, + gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + return 1; + } +#endif + + /* Version check should be the very first call because it + makes sure that important subsystems are intialized. */ + if (!gcry_control(GCRYCTL_ANY_INITIALIZATION_P)) { + const char *gcrypt_version; + gcrypt_version = gcry_check_version(NULL); + /* No other library has already initialized libgcrypt. */ + if (!gcrypt_version) { + msg("%s: failed to initialize libgcrypt\n", + my_progname); + return 1; + } else if (opt_verbose) { + msg("%s: using gcrypt %s\n", my_progname, + gcrypt_version); + } + } + gcry_control(GCRYCTL_DISABLE_SECMEM, 0); + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); + + /* Determine the algorithm */ + encrypt_algo = encrypt_algos[opt_encrypt_algo]; + + /* Set up the iv length */ + encrypt_iv_len = gcry_cipher_get_algo_blklen(encrypt_algo); + + /* Now set up the key */ + if (opt_encrypt_key == NULL && opt_encrypt_key_file == NULL) { + msg("%s: no encryption key or key file specified.\n", + my_progname); + return 1; + } else if (opt_encrypt_key && opt_encrypt_key_file) { + msg("%s: both encryption key and key file specified.\n", + my_progname); + return 1; + } else if (opt_encrypt_key_file) { + if (!xb_crypt_read_key_file(opt_encrypt_key_file, + &opt_encrypt_key, + &encrypt_key_len)) { + msg("%s: unable to read encryption key file \"%s\".\n", + opt_encrypt_key_file, my_progname); + return 1; + } + } else { + encrypt_key_len = strlen(opt_encrypt_key); + } + + if (opt_input_file) { + MY_STAT mystat; + + if (opt_verbose) + msg("%s: input file \"%s\".\n", my_progname, + opt_input_file); + + if (my_stat(opt_input_file, &mystat, MYF(MY_WME)) == NULL) { + goto err; + } + if (!MY_S_ISREG(mystat.st_mode)) { + msg("%s: \"%s\" is not a regular file, exiting.\n", + my_progname, opt_input_file); + goto err; + } + if ((filein = my_open(opt_input_file, O_RDONLY, MYF(MY_WME))) + < 0) { + msg("%s: failed to open \"%s\".\n", my_progname, + opt_input_file); + goto err; + } + } else { + if (opt_verbose) + msg("%s: input from standard input.\n", my_progname); + filein = fileno(stdin); + } + + if (opt_output_file) { + if (opt_verbose) + msg("%s: output file \"%s\".\n", my_progname, + opt_output_file); + + if ((fileout = my_create(opt_output_file, 0, + O_WRONLY|O_BINARY|O_EXCL|O_NOFOLLOW, + MYF(MY_WME))) < 0) { + msg("%s: failed to create output file \"%s\".\n", + my_progname, opt_output_file); + goto err; + } + } else { + if (opt_verbose) + msg("%s: output to standard output.\n", my_progname); + fileout = fileno(stdout); + } + + if (opt_run_mode == RUN_MODE_DECRYPT + && mode_decrypt(filein, fileout)) { + goto err; + } else if (opt_run_mode == RUN_MODE_ENCRYPT + && mode_encrypt(filein, fileout)) { + goto err; + } + + if (opt_input_file && filein) { + my_close(filein, MYF(MY_WME)); + } + if (opt_output_file && fileout) { + my_close(fileout, MYF(MY_WME)); + } + + my_cleanup_options(my_long_options); + + my_end(0); + + return EXIT_SUCCESS; +err: + if (opt_input_file && filein) { + my_close(filein, MYF(MY_WME)); + } + if (opt_output_file && fileout) { + my_close(fileout, MYF(MY_WME)); + } + + my_cleanup_options(my_long_options); + + my_end(0); + + exit(EXIT_FAILURE); + +} + + +static +size_t +my_xb_crypt_read_callback(void *userdata, void *buf, size_t len) +{ + File* file = (File *) userdata; + return xb_read_full(*file, buf, len); +} + +static +int +mode_decrypt(File filein, File fileout) +{ + xb_rcrypt_t *xbcrypt_file = NULL; + void *chunkbuf = NULL; + size_t chunksize; + size_t originalsize; + void *ivbuf = NULL; + size_t ivsize; + void *decryptbuf = NULL; + size_t decryptbufsize = 0; + ulonglong ttlchunksread = 0; + ulonglong ttlbytesread = 0; + xb_rcrypt_result_t result; + gcry_cipher_hd_t cipher_handle; + gcry_error_t gcry_error; + my_bool hash_appended; + + if (encrypt_algo != GCRY_CIPHER_NONE) { + gcry_error = gcry_cipher_open(&cipher_handle, + encrypt_algo, + encrypt_mode, 0); + if (gcry_error) { + msg("%s:decrypt: unable to open libgcrypt" + " cipher - %s : %s\n", my_progname, + gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + return 1; + } + + gcry_error = gcry_cipher_setkey(cipher_handle, + opt_encrypt_key, + encrypt_key_len); + if (gcry_error) { + msg("%s:decrypt: unable to set libgcrypt cipher" + "key - %s : %s\n", my_progname, + gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + goto err; + } + } + + /* Initialize the xb_crypt format reader */ + xbcrypt_file = xb_crypt_read_open(&filein, my_xb_crypt_read_callback); + if (xbcrypt_file == NULL) { + msg("%s:decrypt: xb_crypt_read_open() failed.\n", my_progname); + goto err; + } + + /* Walk the encrypted chunks, decrypting them and writing out */ + while ((result = xb_crypt_read_chunk(xbcrypt_file, &chunkbuf, + &originalsize, &chunksize, + &ivbuf, &ivsize, &hash_appended)) + == XB_CRYPT_READ_CHUNK) { + + if (encrypt_algo != GCRY_CIPHER_NONE) { + gcry_error = gcry_cipher_reset(cipher_handle); + if (gcry_error) { + msg("%s:decrypt: unable to reset libgcrypt" + " cipher - %s : %s\n", my_progname, + gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + goto err; + } + + if (ivsize) { + gcry_error = gcry_cipher_setctr(cipher_handle, + ivbuf, + ivsize); + } + if (gcry_error) { + msg("%s:decrypt: unable to set cipher iv - " + "%s : %s\n", my_progname, + gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + continue; + } + + if (decryptbufsize < originalsize) { + decryptbuf = my_realloc(decryptbuf, + originalsize, + MYF(MY_WME | MY_ALLOW_ZERO_PTR)); + decryptbufsize = originalsize; + } + + /* Try to decrypt it */ + gcry_error = gcry_cipher_decrypt(cipher_handle, + decryptbuf, + originalsize, + chunkbuf, + chunksize); + if (gcry_error) { + msg("%s:decrypt: unable to decrypt chunk - " + "%s : %s\n", my_progname, + gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + gcry_cipher_close(cipher_handle); + goto err; + } + + } else { + decryptbuf = chunkbuf; + } + + if (hash_appended) { + uchar hash[XB_CRYPT_HASH_LEN]; + + originalsize -= XB_CRYPT_HASH_LEN; + + /* ensure that XB_CRYPT_HASH_LEN is the correct length + of XB_CRYPT_HASH hashing algorithm output */ + xb_a(gcry_md_get_algo_dlen(XB_CRYPT_HASH) == + XB_CRYPT_HASH_LEN); + gcry_md_hash_buffer(XB_CRYPT_HASH, hash, decryptbuf, + originalsize); + if (memcmp(hash, (char *) decryptbuf + originalsize, + XB_CRYPT_HASH_LEN) != 0) { + msg("%s:%s invalid plaintext hash. " + "Wrong encrytion key specified?\n", + my_progname, __FUNCTION__); + result = XB_CRYPT_READ_ERROR; + goto err; + } + } + + /* Write it out */ + if (my_write(fileout, (const uchar *) decryptbuf, originalsize, + MYF(MY_WME | MY_NABP))) { + msg("%s:decrypt: unable to write output chunk.\n", + my_progname); + goto err; + } + ttlchunksread++; + ttlbytesread += chunksize; + if (opt_verbose) + msg("%s:decrypt: %llu chunks read, %llu bytes read\n.", + my_progname, ttlchunksread, ttlbytesread); + } + + xb_crypt_read_close(xbcrypt_file); + + if (encrypt_algo != GCRY_CIPHER_NONE) + gcry_cipher_close(cipher_handle); + + if (decryptbuf && decryptbufsize) + my_free(decryptbuf); + + if (opt_verbose) + msg("\n%s:decrypt: done\n", my_progname); + + return 0; +err: + if (xbcrypt_file) + xb_crypt_read_close(xbcrypt_file); + + if (encrypt_algo != GCRY_CIPHER_NONE) + gcry_cipher_close(cipher_handle); + + if (decryptbuf && decryptbufsize) + my_free(decryptbuf); + + return 1; +} + +static +ssize_t +my_xb_crypt_write_callback(void *userdata, const void *buf, size_t len) +{ + File* file = (File *) userdata; + + ssize_t ret = my_write(*file, buf, len, MYF(MY_WME)); + posix_fadvise(*file, 0, 0, POSIX_FADV_DONTNEED); + return ret; +} + +static +int +mode_encrypt(File filein, File fileout) +{ + size_t bytesread; + size_t chunkbuflen; + uchar *chunkbuf = NULL; + void *ivbuf = NULL; + size_t encryptbuflen = 0; + size_t encryptedlen = 0; + void *encryptbuf = NULL; + ulonglong ttlchunkswritten = 0; + ulonglong ttlbyteswritten = 0; + xb_wcrypt_t *xbcrypt_file = NULL; + gcry_cipher_hd_t cipher_handle; + gcry_error_t gcry_error; + + if (encrypt_algo != GCRY_CIPHER_NONE) { + gcry_error = gcry_cipher_open(&cipher_handle, + encrypt_algo, + encrypt_mode, 0); + if (gcry_error) { + msg("%s:encrypt: unable to open libgcrypt cipher - " + "%s : %s\n", my_progname, + gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + return 1; + } + + gcry_error = gcry_cipher_setkey(cipher_handle, + opt_encrypt_key, + encrypt_key_len); + if (gcry_error) { + msg("%s:encrypt: unable to set libgcrypt cipher key - " + "%s : %s\n", my_progname, + gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + goto err; + } + } + + posix_fadvise(filein, 0, 0, POSIX_FADV_SEQUENTIAL); + + xbcrypt_file = xb_crypt_write_open(&fileout, + my_xb_crypt_write_callback); + if (xbcrypt_file == NULL) { + msg("%s:encrypt: xb_crypt_write_open() failed.\n", + my_progname); + goto err; + } + + ivbuf = my_malloc(encrypt_iv_len, MYF(MY_FAE)); + + /* now read in data in chunk size, encrypt and write out */ + chunkbuflen = opt_encrypt_chunk_size + XB_CRYPT_HASH_LEN; + chunkbuf = (uchar *) my_malloc(chunkbuflen, MYF(MY_FAE)); + while ((bytesread = my_read(filein, chunkbuf, opt_encrypt_chunk_size, + MYF(MY_WME))) > 0) { + + size_t origbuflen = bytesread + XB_CRYPT_HASH_LEN; + + /* ensure that XB_CRYPT_HASH_LEN is the correct length + of XB_CRYPT_HASH hashing algorithm output */ + xb_a(XB_CRYPT_HASH_LEN == gcry_md_get_algo_dlen(XB_CRYPT_HASH)); + gcry_md_hash_buffer(XB_CRYPT_HASH, chunkbuf + bytesread, + chunkbuf, bytesread); + + if (encrypt_algo != GCRY_CIPHER_NONE) { + gcry_error = gcry_cipher_reset(cipher_handle); + + if (gcry_error) { + msg("%s:encrypt: unable to reset cipher - " + "%s : %s\n", my_progname, + gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + goto err; + } + + xb_crypt_create_iv(ivbuf, encrypt_iv_len); + gcry_error = gcry_cipher_setctr(cipher_handle, + ivbuf, + encrypt_iv_len); + + if (gcry_error) { + msg("%s:encrypt: unable to set cipher iv - " + "%s : %s\n", my_progname, + gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + continue; + } + + if (encryptbuflen < origbuflen) { + encryptbuf = my_realloc(encryptbuf, origbuflen, + MYF(MY_WME | MY_ALLOW_ZERO_PTR)); + encryptbuflen = origbuflen; + } + + gcry_error = gcry_cipher_encrypt(cipher_handle, + encryptbuf, + encryptbuflen, + chunkbuf, + origbuflen); + + encryptedlen = origbuflen; + + if (gcry_error) { + msg("%s:encrypt: unable to encrypt chunk - " + "%s : %s\n", my_progname, + gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + gcry_cipher_close(cipher_handle); + goto err; + } + } else { + encryptedlen = origbuflen; + encryptbuf = chunkbuf; + } + + if (xb_crypt_write_chunk(xbcrypt_file, encryptbuf, + bytesread + XB_CRYPT_HASH_LEN, + encryptedlen, ivbuf, encrypt_iv_len)) { + msg("%s:encrypt: abcrypt_write_chunk() failed.\n", + my_progname); + goto err; + } + + ttlchunkswritten++; + ttlbyteswritten += encryptedlen; + + if (opt_verbose) + msg("%s:encrypt: %llu chunks written, %llu bytes " + "written\n.", my_progname, ttlchunkswritten, + ttlbyteswritten); + } + + my_free(ivbuf); + my_free(chunkbuf); + + if (encryptbuf && encryptbuflen) + my_free(encryptbuf); + + xb_crypt_write_close(xbcrypt_file); + + if (encrypt_algo != GCRY_CIPHER_NONE) + gcry_cipher_close(cipher_handle); + + if (opt_verbose) + msg("\n%s:encrypt: done\n", my_progname); + + return 0; +err: + if (chunkbuf) + my_free(chunkbuf); + + if (encryptbuf && encryptbuflen) + my_free(encryptbuf); + + if (xbcrypt_file) + xb_crypt_write_close(xbcrypt_file); + + if (encrypt_algo != GCRY_CIPHER_NONE) + gcry_cipher_close(cipher_handle); + + return 1; +} + +static +int +get_options(int *argc, char ***argv) +{ + int ho_error; + + if ((ho_error= handle_options(argc, argv, my_long_options, + get_one_option))) { + exit(EXIT_FAILURE); + } + + return 0; +} + +static +my_bool +get_one_option(int optid, const struct my_option *opt __attribute__((unused)), + char *argument __attribute__((unused))) +{ + switch (optid) { + case 'd': + opt_run_mode = RUN_MODE_DECRYPT; + break; + case '?': + usage(); + exit(0); + } + + return FALSE; +} + +static +void +print_version(void) +{ + printf("%s Ver %s for %s (%s)\n", my_progname, XBCRYPT_VERSION, + SYSTEM_TYPE, MACHINE_TYPE); +} + +static +void +usage(void) +{ + print_version(); + puts("Copyright (C) 2011 Percona Inc."); + puts("This software comes with ABSOLUTELY NO WARRANTY. " + "This is free software,\nand you are welcome to modify and " + "redistribute it under the GPL license.\n"); + + puts("Encrypt or decrypt files in the XBCRYPT format.\n"); + + puts("Usage: "); + printf(" %s [OPTIONS...]" + " # read data from specified input, encrypting or decrypting " + " and writing the result to the specified output.\n", + my_progname); + puts("\nOptions:"); + my_print_help(my_long_options); +} diff --git a/extra/mariabackup/xbcrypt.h b/extra/mariabackup/xbcrypt.h new file mode 100644 index 00000000000..0e832266847 --- /dev/null +++ b/extra/mariabackup/xbcrypt.h @@ -0,0 +1,79 @@ +/****************************************************** +Copyright (c) 2011 Percona LLC and/or its affiliates. + +Encryption interface for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#ifndef XBCRYPT_H +#define XBCRYPT_H + +#include <my_base.h> +#include "common.h" + +#define XB_CRYPT_CHUNK_MAGIC1 "XBCRYP01" +#define XB_CRYPT_CHUNK_MAGIC2 "XBCRYP02" +#define XB_CRYPT_CHUNK_MAGIC3 "XBCRYP03" /* must be same size as ^^ */ +#define XB_CRYPT_CHUNK_MAGIC_CURRENT XB_CRYPT_CHUNK_MAGIC3 +#define XB_CRYPT_CHUNK_MAGIC_SIZE (sizeof(XB_CRYPT_CHUNK_MAGIC1)-1) + +#define XB_CRYPT_HASH GCRY_MD_SHA256 +#define XB_CRYPT_HASH_LEN 32 + +/****************************************************************************** +Write interface */ +typedef struct xb_wcrypt_struct xb_wcrypt_t; + +/* Callback on write for i/o, must return # of bytes written or -1 on error */ +typedef ssize_t xb_crypt_write_callback(void *userdata, + const void *buf, size_t len); + +xb_wcrypt_t *xb_crypt_write_open(void *userdata, + xb_crypt_write_callback *onwrite); + +/* Takes buffer, original length, encrypted length iv and iv length, formats + output buffer and calls write callback. + Returns 0 on success, 1 on error */ +int xb_crypt_write_chunk(xb_wcrypt_t *crypt, const void *buf, size_t olen, + size_t elen, const void *iv, size_t ivlen); + +/* Returns 0 on success, 1 on error */ +int xb_crypt_write_close(xb_wcrypt_t *crypt); + +/****************************************************************************** +Read interface */ +typedef struct xb_rcrypt_struct xb_rcrypt_t; + +/* Callback on read for i/o, must return # of bytes read or -1 on error */ +typedef size_t xb_crypt_read_callback(void *userdata, void *buf, size_t len); + +xb_rcrypt_t *xb_crypt_read_open(void *userdata, + xb_crypt_read_callback *onread); + +typedef enum { + XB_CRYPT_READ_CHUNK, + XB_CRYPT_READ_INCOMPLETE, + XB_CRYPT_READ_EOF, + XB_CRYPT_READ_ERROR +} xb_rcrypt_result_t; + +xb_rcrypt_result_t xb_crypt_read_chunk(xb_rcrypt_t *crypt, void **buf, + size_t *olen, size_t *elen, void **iv, + size_t *ivlen, my_bool *hash_appended); + +int xb_crypt_read_close(xb_rcrypt_t *crypt); + +#endif diff --git a/extra/mariabackup/xbcrypt_common.c b/extra/mariabackup/xbcrypt_common.c new file mode 100644 index 00000000000..0cdb54dc66d --- /dev/null +++ b/extra/mariabackup/xbcrypt_common.c @@ -0,0 +1,328 @@ +/****************************************************** +Copyright (c) 2013, 2017 Percona LLC and/or its affiliates. + +Encryption configuration file interface for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#include <my_base.h> +#include "common.h" +#include "xbcrypt.h" +#include "xbcrypt_common.h" + +/* Encryption options */ +char *ds_encrypt_key = NULL; +char *ds_encrypt_key_file = NULL; +ulong ds_encrypt_algo; + +static uint encrypt_key_len; +static uint encrypt_iv_len; + +static const uint encrypt_mode = GCRY_CIPHER_MODE_CTR; + +static uint encrypt_algos[] = { GCRY_CIPHER_NONE, GCRY_CIPHER_AES128, + GCRY_CIPHER_AES192, GCRY_CIPHER_AES256 }; +static uint encrypt_algo; + +#if !defined(GCRYPT_VERSION_NUMBER) || (GCRYPT_VERSION_NUMBER < 0x010600) +GCRY_THREAD_OPTION_PTHREAD_IMPL; +#endif + + +my_bool +xb_crypt_read_key_file(const char *filename, void** key, uint *keylength) +{ + FILE *fp; + + if (!(fp = my_fopen(filename, O_RDONLY, MYF(0)))) { + msg("%s:%s: unable to open config file \"%s\", errno(%d)\n", + my_progname, __FUNCTION__, filename, my_errno); + return FALSE; + } + + fseek(fp, 0 , SEEK_END); + *keylength = ftell(fp); + rewind(fp); + *key = my_malloc(*keylength, MYF(MY_FAE)); + *keylength = fread(*key, 1, *keylength, fp); + my_fclose(fp, MYF(0)); + return TRUE; +} + +void +xb_crypt_create_iv(void* ivbuf, size_t ivlen) +{ + gcry_create_nonce(ivbuf, ivlen); +} + +gcry_error_t +xb_crypt_init(uint *iv_len) +{ + gcry_error_t gcry_error; + + /* Acording to gcrypt docs (and my testing), setting up the threading + callbacks must be done first, so, lets give it a shot */ +#if !defined(GCRYPT_VERSION_NUMBER) || (GCRYPT_VERSION_NUMBER < 0x010600) + gcry_error = gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); + if (gcry_error) { + msg("encryption: unable to set libgcrypt thread cbs - " + "%s : %s\n", + gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + return gcry_error; + } +#endif + + /* Version check should be the very next call because it + makes sure that important subsystems are intialized. */ + if (!gcry_control(GCRYCTL_ANY_INITIALIZATION_P)) { + const char *gcrypt_version; + gcrypt_version = gcry_check_version(NULL); + /* No other library has already initialized libgcrypt. */ + if (!gcrypt_version) { + msg("encryption: failed to initialize libgcrypt\n"); + return 1; + } else { + msg("encryption: using gcrypt %s\n", gcrypt_version); + } + } + + /* Disable the gcry secure memory, not dealing with this for now */ + gcry_error = gcry_control(GCRYCTL_DISABLE_SECMEM, 0); + if (gcry_error) { + msg("encryption: unable to disable libgcrypt secmem - " + "%s : %s\n", + gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + return gcry_error; + } + + /* Finalize gcry initialization. */ + gcry_error = gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); + if (gcry_error) { + msg("encryption: unable to finish libgcrypt initialization - " + "%s : %s\n", + gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + return gcry_error; + } + + /* Determine the algorithm */ + encrypt_algo = encrypt_algos[ds_encrypt_algo]; + + /* Set up the iv length */ + encrypt_iv_len = gcry_cipher_get_algo_blklen(encrypt_algo); + xb_a(encrypt_iv_len > 0); + if (iv_len != NULL) { + *iv_len = encrypt_iv_len; + } + + /* Now set up the key */ + if (ds_encrypt_key == NULL && + ds_encrypt_key_file == NULL) { + msg("encryption: no encryption key or key file specified.\n"); + return gcry_error; + } else if (ds_encrypt_key && ds_encrypt_key_file) { + msg("encryption: both encryption key and key file specified.\n"); + return gcry_error; + } else if (ds_encrypt_key_file) { + if (!xb_crypt_read_key_file(ds_encrypt_key_file, + (void**)&ds_encrypt_key, + &encrypt_key_len)) { + msg("encryption: unable to read encryption key file" + " \"%s\".\n", ds_encrypt_key_file); + return gcry_error; + } + } else if (ds_encrypt_key) { + encrypt_key_len = strlen(ds_encrypt_key); + } else { + msg("encryption: no encryption key or key file specified.\n"); + return gcry_error; + } + + return 0; +} + +gcry_error_t +xb_crypt_cipher_open(gcry_cipher_hd_t *cipher_handle) +{ + if (encrypt_algo != GCRY_CIPHER_NONE) { + gcry_error_t gcry_error; + + gcry_error = gcry_cipher_open(cipher_handle, + encrypt_algo, + encrypt_mode, 0); + if (gcry_error) { + msg("encryption: unable to open libgcrypt" + " cipher - %s : %s\n", + gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + gcry_cipher_close(*cipher_handle); + return gcry_error; + } + + gcry_error = gcry_cipher_setkey(*cipher_handle, + ds_encrypt_key, + encrypt_key_len); + if (gcry_error) { + msg("encryption: unable to set libgcrypt" + " cipher key - %s : %s\n", + gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + gcry_cipher_close(*cipher_handle); + return gcry_error; + } + return gcry_error; + } + return 0; +} + +void +xb_crypt_cipher_close(gcry_cipher_hd_t cipher_handle) +{ + if (encrypt_algo != GCRY_CIPHER_NONE) + gcry_cipher_close(cipher_handle); +} + +gcry_error_t +xb_crypt_decrypt(gcry_cipher_hd_t cipher_handle, const uchar *from, + size_t from_len, uchar *to, size_t *to_len, + const uchar *iv, size_t iv_len, my_bool hash_appended) +{ + *to_len = from_len; + + if (encrypt_algo != GCRY_CIPHER_NONE) { + + gcry_error_t gcry_error; + + gcry_error = gcry_cipher_reset(cipher_handle); + if (gcry_error) { + msg("%s:encryption: unable to reset libgcrypt" + " cipher - %s : %s\n", my_progname, + gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + return gcry_error; + } + + if (iv_len > 0) { + gcry_error = gcry_cipher_setctr(cipher_handle, + iv, iv_len); + } + if (gcry_error) { + msg("%s:encryption: unable to set cipher iv - " + "%s : %s\n", my_progname, + gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + return gcry_error; + } + + /* Try to decrypt it */ + gcry_error = gcry_cipher_decrypt(cipher_handle, to, *to_len, + from, from_len); + if (gcry_error) { + msg("%s:encryption: unable to decrypt chunk - " + "%s : %s\n", my_progname, + gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + gcry_cipher_close(cipher_handle); + return gcry_error; + } + + if (hash_appended) { + uchar hash[XB_CRYPT_HASH_LEN]; + + *to_len -= XB_CRYPT_HASH_LEN; + + /* ensure that XB_CRYPT_HASH_LEN is the correct length + of XB_CRYPT_HASH hashing algorithm output */ + xb_ad(gcry_md_get_algo_dlen(XB_CRYPT_HASH) == + XB_CRYPT_HASH_LEN); + gcry_md_hash_buffer(XB_CRYPT_HASH, hash, to, + *to_len); + if (memcmp(hash, (char *) to + *to_len, + XB_CRYPT_HASH_LEN) != 0) { + msg("%s:%s invalid plaintext hash. " + "Wrong encrytion key specified?\n", + my_progname, __FUNCTION__); + return 1; + } + } + + } else { + memcpy(to, from, *to_len); + } + + return 0; +} + +gcry_error_t +xb_crypt_encrypt(gcry_cipher_hd_t cipher_handle, const uchar *from, + size_t from_len, uchar *to, size_t *to_len, uchar *iv) +{ + gcry_error_t gcry_error; + + /* ensure that XB_CRYPT_HASH_LEN is the correct length + of XB_CRYPT_HASH hashing algorithm output */ + xb_ad(gcry_md_get_algo_dlen(XB_CRYPT_HASH) == + XB_CRYPT_HASH_LEN); + + memcpy(to, from, from_len); + gcry_md_hash_buffer(XB_CRYPT_HASH, to + from_len, + from, from_len); + + *to_len = from_len; + + if (encrypt_algo != GCRY_CIPHER_NONE) { + + gcry_error = gcry_cipher_reset(cipher_handle); + if (gcry_error) { + msg("encrypt: unable to reset cipher - " + "%s : %s\n", + gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + return gcry_error; + } + + xb_crypt_create_iv(iv, encrypt_iv_len); + gcry_error = gcry_cipher_setctr(cipher_handle, iv, + encrypt_iv_len); + if (gcry_error) { + msg("encrypt: unable to set cipher ctr - " + "%s : %s\n", + gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + return gcry_error; + } + + gcry_error = gcry_cipher_encrypt(cipher_handle, to, + *to_len + XB_CRYPT_HASH_LEN, + to, + from_len + XB_CRYPT_HASH_LEN); + if (gcry_error) { + msg("encrypt: unable to encrypt buffer - " + "%s : %s\n", gcry_strsource(gcry_error), + gcry_strerror(gcry_error)); + return gcry_error; + } + } else { + memcpy(to, from, from_len + XB_CRYPT_HASH_LEN); + } + + *to_len += XB_CRYPT_HASH_LEN; + + return 0; +} +#endif
\ No newline at end of file diff --git a/extra/mariabackup/xbcrypt_common.h b/extra/mariabackup/xbcrypt_common.h new file mode 100644 index 00000000000..85d13c01fc4 --- /dev/null +++ b/extra/mariabackup/xbcrypt_common.h @@ -0,0 +1,64 @@ +/****************************************************** +Copyright (c) 2017 Percona LLC and/or its affiliates. + +Encryption datasink implementation for XtraBackup. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#include <my_base.h> +#if HAVE_GCRYPT +#if GCC_VERSION >= 4002 +/* Workaround to avoid "gcry_ac_* is deprecated" warnings in gcrypt.h */ +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +#include <gcrypt.h> + +extern char *ds_encrypt_key; +extern char *ds_encrypt_key_file; +extern int ds_encrypt_threads; +extern ulong ds_encrypt_algo; + +/****************************************************************************** +Utility interface */ +my_bool xb_crypt_read_key_file(const char *filename, + void** key, uint *keylength); + +void xb_crypt_create_iv(void* ivbuf, size_t ivlen); + +/* Initialize gcrypt and setup encryption key and IV lengths */ +gcry_error_t +xb_crypt_init(uint *iv_len); + +/* Setup gcrypt cipher */ +gcry_error_t +xb_crypt_cipher_open(gcry_cipher_hd_t *cipher_handle); + +/* Close gcrypt cipher */ +void +xb_crypt_cipher_close(gcry_cipher_hd_t cipher_handle); + +/* Decrypt buffer */ +gcry_error_t +xb_crypt_decrypt(gcry_cipher_hd_t cipher_handle, const uchar *from, + size_t from_len, uchar *to, size_t *to_len, const uchar *iv, + size_t iv_len, my_bool hash_appended); + +/* Encrypt buffer */ +gcry_error_t +xb_crypt_encrypt(gcry_cipher_hd_t cipher_handle, const uchar *from, + size_t from_len, uchar *to, size_t *to_len, uchar *iv); +#endif diff --git a/extra/mariabackup/xbcrypt_read.c b/extra/mariabackup/xbcrypt_read.c new file mode 100644 index 00000000000..41790c7035d --- /dev/null +++ b/extra/mariabackup/xbcrypt_read.c @@ -0,0 +1,252 @@ +/****************************************************** +Copyright (c) 2013 Percona LLC and/or its affiliates. + +The xbcrypt format reader implementation. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#include "xbcrypt.h" +#include "crc_glue.h" + +struct xb_rcrypt_struct { + void *userdata; + xb_crypt_read_callback *read; + void *buffer; + size_t bufsize; + void *ivbuffer; + size_t ivbufsize; + ulonglong offset; +}; + +xb_rcrypt_t * +xb_crypt_read_open(void *userdata, xb_crypt_read_callback *onread) +{ + xb_rcrypt_t *crypt; + + xb_ad(onread); + + crypt = (xb_rcrypt_t *) my_malloc(sizeof(xb_rcrypt_t), MYF(MY_FAE)); + + crypt->userdata = userdata; + crypt->read = onread; + crypt->buffer = NULL; + crypt->bufsize = 0; + crypt->offset = 0; + crypt->ivbuffer = NULL; + crypt->ivbufsize = 0; + return crypt; +} + +xb_rcrypt_result_t +xb_crypt_read_chunk(xb_rcrypt_t *crypt, void **buf, size_t *olen, size_t *elen, + void **iv, size_t *ivlen, my_bool *hash_appended) + +{ + uchar tmpbuf[XB_CRYPT_CHUNK_MAGIC_SIZE + 8 + 8 + 8 + 4]; + uchar *ptr; + ulonglong tmp; + ulong checksum, checksum_exp, version; + size_t bytesread; + xb_rcrypt_result_t result = XB_CRYPT_READ_CHUNK; + + if ((bytesread = crypt->read(crypt->userdata, tmpbuf, sizeof(tmpbuf))) + != sizeof(tmpbuf)) { + if (bytesread == 0) { + result = XB_CRYPT_READ_EOF; + goto err; + } else { + msg("%s:%s: unable to read chunk header data at " + "offset 0x%llx.\n", + my_progname, __FUNCTION__, crypt->offset); + result = XB_CRYPT_READ_ERROR; + goto err; + } + } + + ptr = tmpbuf; + + if (memcmp(ptr, XB_CRYPT_CHUNK_MAGIC3, + XB_CRYPT_CHUNK_MAGIC_SIZE) == 0) { + version = 3; + } else if (memcmp(ptr, XB_CRYPT_CHUNK_MAGIC2, + XB_CRYPT_CHUNK_MAGIC_SIZE) == 0) { + version = 2; + } else if (memcmp(ptr, XB_CRYPT_CHUNK_MAGIC1, + XB_CRYPT_CHUNK_MAGIC_SIZE) == 0) { + version = 1; + } else { + msg("%s:%s: wrong chunk magic at offset 0x%llx.\n", + my_progname, __FUNCTION__, crypt->offset); + result = XB_CRYPT_READ_ERROR; + goto err; + } + + ptr += XB_CRYPT_CHUNK_MAGIC_SIZE; + crypt->offset += XB_CRYPT_CHUNK_MAGIC_SIZE; + + tmp = uint8korr(ptr); /* reserved */ + ptr += 8; + crypt->offset += 8; + + tmp = uint8korr(ptr); /* original size */ + ptr += 8; + if (tmp > INT_MAX) { + msg("%s:%s: invalid original size at offset 0x%llx.\n", + my_progname, __FUNCTION__, crypt->offset); + result = XB_CRYPT_READ_ERROR; + goto err; + } + crypt->offset += 8; + *olen = (size_t)tmp; + + tmp = uint8korr(ptr); /* encrypted size */ + ptr += 8; + if (tmp > INT_MAX) { + msg("%s:%s: invalid encrypted size at offset 0x%llx.\n", + my_progname, __FUNCTION__, crypt->offset); + result = XB_CRYPT_READ_ERROR; + goto err; + } + crypt->offset += 8; + *elen = (size_t)tmp; + + checksum_exp = uint4korr(ptr); /* checksum */ + ptr += 4; + crypt->offset += 4; + + /* iv size */ + if (version == 1) { + *ivlen = 0; + *iv = 0; + } else { + if ((bytesread = crypt->read(crypt->userdata, tmpbuf, 8)) + != 8) { + if (bytesread == 0) { + result = XB_CRYPT_READ_EOF; + goto err; + } else { + msg("%s:%s: unable to read chunk iv size at " + "offset 0x%llx.\n", + my_progname, __FUNCTION__, crypt->offset); + result = XB_CRYPT_READ_ERROR; + goto err; + } + } + + tmp = uint8korr(tmpbuf); + if (tmp > INT_MAX) { + msg("%s:%s: invalid iv size at offset 0x%llx.\n", + my_progname, __FUNCTION__, crypt->offset); + result = XB_CRYPT_READ_ERROR; + goto err; + } + crypt->offset += 8; + *ivlen = (size_t)tmp; + } + + if (*ivlen > crypt->ivbufsize) { + crypt->ivbuffer = my_realloc(crypt->ivbuffer, *ivlen, + MYF(MY_WME | MY_ALLOW_ZERO_PTR)); + if (crypt->ivbuffer == NULL) { + msg("%s:%s: failed to increase iv buffer to " + "%llu bytes.\n", my_progname, __FUNCTION__, + (ulonglong)*ivlen); + result = XB_CRYPT_READ_ERROR; + goto err; + } + crypt->ivbufsize = *ivlen; + } + + if (*ivlen > 0) { + if (crypt->read(crypt->userdata, crypt->ivbuffer, *ivlen) + != *ivlen) { + msg("%s:%s: failed to read %lld bytes for chunk iv " + "at offset 0x%llx.\n", my_progname, __FUNCTION__, + (ulonglong)*ivlen, crypt->offset); + result = XB_CRYPT_READ_ERROR; + goto err; + } + *iv = crypt->ivbuffer; + } + + /* for version euqals 2 we need to read in the iv data but do not init + CTR with it */ + if (version == 2) { + *ivlen = 0; + *iv = 0; + } + + if (*olen > crypt->bufsize) { + crypt->buffer = my_realloc(crypt->buffer, *olen, + MYF(MY_WME | MY_ALLOW_ZERO_PTR)); + if (crypt->buffer == NULL) { + msg("%s:%s: failed to increase buffer to " + "%llu bytes.\n", my_progname, __FUNCTION__, + (ulonglong)*olen); + result = XB_CRYPT_READ_ERROR; + goto err; + } + crypt->bufsize = *olen; + } + + if (*elen > 0) { + if (crypt->read(crypt->userdata, crypt->buffer, *elen) + != *elen) { + msg("%s:%s: failed to read %lld bytes for chunk payload " + "at offset 0x%llx.\n", my_progname, __FUNCTION__, + (ulonglong)*elen, crypt->offset); + result = XB_CRYPT_READ_ERROR; + goto err; + } + } + + checksum = crc32_iso3309(0, crypt->buffer, *elen); + if (checksum != checksum_exp) { + msg("%s:%s invalid checksum at offset 0x%llx, " + "expected 0x%lx, actual 0x%lx.\n", my_progname, __FUNCTION__, + crypt->offset, checksum_exp, checksum); + result = XB_CRYPT_READ_ERROR; + goto err; + } + + crypt->offset += *elen; + *buf = crypt->buffer; + + *hash_appended = version > 2; + + goto exit; + +err: + *buf = NULL; + *olen = 0; + *elen = 0; + *ivlen = 0; + *iv = 0; +exit: + return result; +} + +int xb_crypt_read_close(xb_rcrypt_t *crypt) +{ + if (crypt->buffer) + my_free(crypt->buffer); + if (crypt->ivbuffer) + my_free(crypt->ivbuffer); + my_free(crypt); + + return 0; +} + diff --git a/extra/mariabackup/xbcrypt_write.c b/extra/mariabackup/xbcrypt_write.c new file mode 100644 index 00000000000..91dbfc4eb29 --- /dev/null +++ b/extra/mariabackup/xbcrypt_write.c @@ -0,0 +1,105 @@ +/****************************************************** +Copyright (c) 2013 Percona LLC and/or its affiliates. + +The xbcrypt format writer implementation. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#include "xbcrypt.h" +#include "crc_glue.h" + +struct xb_wcrypt_struct { + void *userdata; + xb_crypt_write_callback *write; +}; + +xb_wcrypt_t * +xb_crypt_write_open(void *userdata, xb_crypt_write_callback *onwrite) +{ + xb_wcrypt_t *crypt; + + xb_ad(onwrite); + + crypt = (xb_wcrypt_t *) my_malloc(sizeof(xb_wcrypt_t), MYF(MY_FAE)); + + crypt->userdata = userdata; + crypt->write = onwrite; + + return crypt; +} + +int xb_crypt_write_chunk(xb_wcrypt_t *crypt, const void *buf, size_t olen, + size_t elen, const void *iv, size_t ivlen) +{ + uchar tmpbuf[XB_CRYPT_CHUNK_MAGIC_SIZE + 8 + 8 + 8 + 4 + 8]; + uchar *ptr; + ulong checksum; + + xb_ad(olen <= INT_MAX); + if (olen > INT_MAX) + return 0; + + xb_ad(elen <= INT_MAX); + if (elen > INT_MAX) + return 0; + + xb_ad(ivlen <= INT_MAX); + if (ivlen > INT_MAX) + return 0; + + ptr = tmpbuf; + + memcpy(ptr, XB_CRYPT_CHUNK_MAGIC_CURRENT, XB_CRYPT_CHUNK_MAGIC_SIZE); + ptr += XB_CRYPT_CHUNK_MAGIC_SIZE; + + int8store(ptr, (ulonglong)0); /* reserved */ + ptr += 8; + + int8store(ptr, (ulonglong)olen); /* original size */ + ptr += 8; + + int8store(ptr, (ulonglong)elen); /* encrypted (actual) size */ + ptr += 8; + + checksum = crc32_iso3309(0, buf, elen); + int4store(ptr, checksum); /* checksum */ + ptr += 4; + + int8store(ptr, (ulonglong)ivlen); /* iv size */ + ptr += 8; + + xb_ad(ptr <= tmpbuf + sizeof(tmpbuf)); + + if (crypt->write(crypt->userdata, tmpbuf, ptr-tmpbuf) == -1) + return 1; + + if (crypt->write(crypt->userdata, iv, ivlen) == -1) + return 1; + + if (crypt->write(crypt->userdata, buf, elen) == -1) + return 1; + + return 0; +} + +int xb_crypt_write_close(xb_wcrypt_t *crypt) +{ + my_free(crypt); + + return 0; +} + + diff --git a/extra/mariabackup/xbstream.c b/extra/mariabackup/xbstream.c new file mode 100644 index 00000000000..2cc47ec7273 --- /dev/null +++ b/extra/mariabackup/xbstream.c @@ -0,0 +1,613 @@ +/****************************************************** +Copyright (c) 2011-2013 Percona LLC and/or its affiliates. + +The xbstream utility: serialize/deserialize files in the XBSTREAM format. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#include <mysql_version.h> +#include <my_base.h> +#include <my_getopt.h> +#include <hash.h> +#include <my_pthread.h> +#include "common.h" +#include "xbstream.h" +#include "xbcrypt_common.h" +#include "datasink.h" +#include "ds_decrypt.h" +#include "crc_glue.h" + +#define XBSTREAM_VERSION "1.0" +#define XBSTREAM_BUFFER_SIZE (10 * 1024 * 1024UL) + +#define START_FILE_HASH_SIZE 16 + +typedef enum { + RUN_MODE_NONE, + RUN_MODE_CREATE, + RUN_MODE_EXTRACT +} run_mode_t; + +const char *xbstream_encrypt_algo_names[] = +{ "NONE", "AES128", "AES192", "AES256", NullS}; +TYPELIB xbstream_encrypt_algo_typelib= +{array_elements(xbstream_encrypt_algo_names)-1,"", + xbstream_encrypt_algo_names, NULL}; + +/* Need the following definitions to avoid linking with ds_*.o and their link +dependencies */ +datasink_t datasink_archive; +datasink_t datasink_xbstream; +datasink_t datasink_compress; +datasink_t datasink_tmpfile; +datasink_t datasink_encrypt; +datasink_t datasink_buffer; + +static run_mode_t opt_mode; +static char * opt_directory = NULL; +static my_bool opt_verbose = 0; +static int opt_parallel = 1; +static ulong opt_encrypt_algo; +static char *opt_encrypt_key_file = NULL; +static void *opt_encrypt_key = NULL; +static int opt_encrypt_threads = 1; + +enum { + OPT_ENCRYPT_THREADS = 256 +}; + +static struct my_option my_long_options[] = +{ + {"help", '?', "Display this help and exit.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"create", 'c', "Stream the specified files to the standard output.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"extract", 'x', "Extract to disk files from the stream on the " + "standard input.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"directory", 'C', "Change the current directory to the specified one " + "before streaming or extracting.", &opt_directory, &opt_directory, 0, + GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"verbose", 'v', "Print verbose output.", &opt_verbose, &opt_verbose, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"parallel", 'p', "Number of worker threads for reading / writing.", + &opt_parallel, &opt_parallel, 0, GET_INT, REQUIRED_ARG, + 1, 1, INT_MAX, 0, 0, 0}, + {"decrypt", 'd', "Decrypt files ending with .xbcrypt.", + &opt_encrypt_algo, &opt_encrypt_algo, &xbstream_encrypt_algo_typelib, + GET_ENUM, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"encrypt-key", 'k', "Encryption key.", + &opt_encrypt_key, &opt_encrypt_key, 0, + GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"encrypt-key-file", 'f', "File which contains encryption key.", + &opt_encrypt_key_file, &opt_encrypt_key_file, 0, + GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"encrypt-threads", OPT_ENCRYPT_THREADS, + "Number of threads for parallel data encryption. " + "The default value is 1.", + &opt_encrypt_threads, &opt_encrypt_threads, + 0, GET_INT, REQUIRED_ARG, 1, 1, INT_MAX, 0, 0, 0}, + + {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + +typedef struct { + HASH *filehash; + xb_rstream_t *stream; + ds_ctxt_t *ds_ctxt; + ds_ctxt_t *ds_decrypt_ctxt; + pthread_mutex_t *mutex; +} extract_ctxt_t; + +typedef struct { + char *path; + uint pathlen; + my_off_t offset; + ds_file_t *file; + pthread_mutex_t mutex; +} file_entry_t; + +static int get_options(int *argc, char ***argv); +static int mode_create(int argc, char **argv); +static int mode_extract(int n_threads, int argc, char **argv); +static my_bool get_one_option(int optid, const struct my_option *opt, + char *argument); + +int +main(int argc, char **argv) +{ + MY_INIT(argv[0]); + + crc_init(); + + if (get_options(&argc, &argv)) { + goto err; + } + + if (opt_mode == RUN_MODE_NONE) { + msg("%s: either -c or -x must be specified.\n", my_progname); + goto err; + } + + /* Change the current directory if -C is specified */ + if (opt_directory && my_setwd(opt_directory, MYF(MY_WME))) { + goto err; + } + + if (opt_mode == RUN_MODE_CREATE && mode_create(argc, argv)) { + goto err; + } else if (opt_mode == RUN_MODE_EXTRACT && + mode_extract(opt_parallel, argc, argv)) { + goto err; + } + + my_cleanup_options(my_long_options); + + my_end(0); + + return EXIT_SUCCESS; +err: + my_cleanup_options(my_long_options); + + my_end(0); + + exit(EXIT_FAILURE); +} + +static +int +get_options(int *argc, char ***argv) +{ + int ho_error; + + if ((ho_error= handle_options(argc, argv, my_long_options, + get_one_option))) { + exit(EXIT_FAILURE); + } + + return 0; +} + +static +void +print_version(void) +{ + printf("%s Ver %s for %s (%s)\n", my_progname, XBSTREAM_VERSION, + SYSTEM_TYPE, MACHINE_TYPE); +} + +static +void +usage(void) +{ + print_version(); + puts("Copyright (C) 2011-2013 Percona LLC and/or its affiliates."); + puts("This software comes with ABSOLUTELY NO WARRANTY. " + "This is free software,\nand you are welcome to modify and " + "redistribute it under the GPL license.\n"); + + puts("Serialize/deserialize files in the XBSTREAM format.\n"); + + puts("Usage: "); + printf(" %s -c [OPTIONS...] FILES... # stream specified files to " + "standard output.\n", my_progname); + printf(" %s -x [OPTIONS...] # extract files from the stream" + "on the standard input.\n", my_progname); + + puts("\nOptions:"); + my_print_help(my_long_options); +} + +static +int +set_run_mode(run_mode_t mode) +{ + if (opt_mode != RUN_MODE_NONE) { + msg("%s: can't set specify both -c and -x.\n", my_progname); + return 1; + } + + opt_mode = mode; + + return 0; +} + +static +my_bool +get_one_option(int optid, const struct my_option *opt __attribute__((unused)), + char *argument __attribute__((unused))) +{ + switch (optid) { + case 'c': + if (set_run_mode(RUN_MODE_CREATE)) { + return TRUE; + } + break; + case 'x': + if (set_run_mode(RUN_MODE_EXTRACT)) { + return TRUE; + } + break; + case '?': + usage(); + exit(0); + } + + return FALSE; +} + +static +int +stream_one_file(File file, xb_wstream_file_t *xbfile) +{ + uchar *buf; + ssize_t bytes; + my_off_t offset; + + posix_fadvise(file, 0, 0, POSIX_FADV_SEQUENTIAL); + offset = my_tell(file, MYF(MY_WME)); + + buf = (uchar*)(my_malloc(XBSTREAM_BUFFER_SIZE, MYF(MY_FAE))); + + while ((bytes = (ssize_t)my_read(file, buf, XBSTREAM_BUFFER_SIZE, + MYF(MY_WME))) > 0) { + if (xb_stream_write_data(xbfile, buf, bytes)) { + msg("%s: xb_stream_write_data() failed.\n", + my_progname); + my_free(buf); + return 1; + } + posix_fadvise(file, offset, XBSTREAM_BUFFER_SIZE, + POSIX_FADV_DONTNEED); + offset += XBSTREAM_BUFFER_SIZE; + + } + + my_free(buf); + + if (bytes < 0) { + return 1; + } + + return 0; +} + +static +int +mode_create(int argc, char **argv) +{ + int i; + MY_STAT mystat; + xb_wstream_t *stream; + + if (argc < 1) { + msg("%s: no files are specified.\n", my_progname); + return 1; + } + + stream = xb_stream_write_new(); + if (stream == NULL) { + msg("%s: xb_stream_write_new() failed.\n", my_progname); + return 1; + } + + for (i = 0; i < argc; i++) { + char *filepath = argv[i]; + File src_file; + xb_wstream_file_t *file; + + if (my_stat(filepath, &mystat, MYF(MY_WME)) == NULL) { + goto err; + } + if (!MY_S_ISREG(mystat.st_mode)) { + msg("%s: %s is not a regular file, exiting.\n", + my_progname, filepath); + goto err; + } + + if ((src_file = my_open(filepath, O_RDONLY, MYF(MY_WME))) < 0) { + msg("%s: failed to open %s.\n", my_progname, filepath); + goto err; + } + + file = xb_stream_write_open(stream, filepath, &mystat, NULL, NULL); + if (file == NULL) { + goto err; + } + + if (opt_verbose) { + msg("%s\n", filepath); + } + + if (stream_one_file(src_file, file) || + xb_stream_write_close(file) || + my_close(src_file, MYF(MY_WME))) { + goto err; + } + } + + xb_stream_write_done(stream); + + return 0; +err: + xb_stream_write_done(stream); + + return 1; +} + +/************************************************************************ +Check if string ends with given suffix. +@return true if string ends with given suffix. */ +static +my_bool +ends_with(const char *str, const char *suffix) +{ + size_t suffix_len = strlen(suffix); + size_t str_len = strlen(str); + return(str_len >= suffix_len + && strcmp(str + str_len - suffix_len, suffix) == 0); +} + +static +file_entry_t * +file_entry_new(extract_ctxt_t *ctxt, const char *path, uint pathlen) +{ + file_entry_t *entry; + ds_file_t *file; + + entry = (file_entry_t *) my_malloc(sizeof(file_entry_t), + MYF(MY_WME | MY_ZEROFILL)); + if (entry == NULL) { + return NULL; + } + + entry->path = my_strndup(path, pathlen, MYF(MY_WME)); + if (entry->path == NULL) { + goto err; + } + entry->pathlen = pathlen; + + if (ctxt->ds_decrypt_ctxt && ends_with(path, ".xbcrypt")) { + file = ds_open(ctxt->ds_decrypt_ctxt, path, NULL); + } else { + file = ds_open(ctxt->ds_ctxt, path, NULL); + } + if (file == NULL) { + msg("%s: failed to create file.\n", my_progname); + goto err; + } + + if (opt_verbose) { + msg("%s\n", entry->path); + } + + entry->file = file; + + pthread_mutex_init(&entry->mutex, NULL); + + return entry; + +err: + if (entry->path != NULL) { + my_free(entry->path); + } + my_free(entry); + + return NULL; +} + +static +uchar * +get_file_entry_key(file_entry_t *entry, size_t *length, + my_bool not_used __attribute__((unused))) +{ + *length = entry->pathlen; + return (uchar *) entry->path; +} + +static +void +file_entry_free(file_entry_t *entry) +{ + pthread_mutex_destroy(&entry->mutex); + ds_close(entry->file); + my_free(entry->path); + my_free(entry); +} + +static +void * +extract_worker_thread_func(void *arg) +{ + xb_rstream_chunk_t chunk; + file_entry_t *entry; + xb_rstream_result_t res; + + extract_ctxt_t *ctxt = (extract_ctxt_t *) arg; + + my_thread_init(); + + memset(&chunk, 0, sizeof(chunk)); + + while (1) { + + pthread_mutex_lock(ctxt->mutex); + res = xb_stream_read_chunk(ctxt->stream, &chunk); + + if (res != XB_STREAM_READ_CHUNK) { + pthread_mutex_unlock(ctxt->mutex); + break; + } + + /* If unknown type and ignorable flag is set, skip this chunk */ + if (chunk.type == XB_CHUNK_TYPE_UNKNOWN && \ + !(chunk.flags & XB_STREAM_FLAG_IGNORABLE)) { + pthread_mutex_unlock(ctxt->mutex); + continue; + } + + /* See if we already have this file open */ + entry = (file_entry_t *) my_hash_search(ctxt->filehash, + (uchar *) chunk.path, + chunk.pathlen); + + if (entry == NULL) { + entry = file_entry_new(ctxt, + chunk.path, + chunk.pathlen); + if (entry == NULL) { + pthread_mutex_unlock(ctxt->mutex); + break; + } + if (my_hash_insert(ctxt->filehash, (uchar *) entry)) { + msg("%s: my_hash_insert() failed.\n", + my_progname); + pthread_mutex_unlock(ctxt->mutex); + break; + } + } + + pthread_mutex_lock(&entry->mutex); + + pthread_mutex_unlock(ctxt->mutex); + + res = xb_stream_validate_checksum(&chunk); + + if (res != XB_STREAM_READ_CHUNK) { + pthread_mutex_unlock(&entry->mutex); + break; + } + + if (chunk.type == XB_CHUNK_TYPE_EOF) { + pthread_mutex_unlock(&entry->mutex); + continue; + } + + if (entry->offset != chunk.offset) { + msg("%s: out-of-order chunk: real offset = 0x%llx, " + "expected offset = 0x%llx\n", my_progname, + chunk.offset, entry->offset); + pthread_mutex_unlock(&entry->mutex); + res = XB_STREAM_READ_ERROR; + break; + } + + if (ds_write(entry->file, chunk.data, chunk.length)) { + msg("%s: my_write() failed.\n", my_progname); + pthread_mutex_unlock(&entry->mutex); + res = XB_STREAM_READ_ERROR; + break; + } + + entry->offset += chunk.length; + + pthread_mutex_unlock(&entry->mutex); + } + + if (chunk.data) + my_free(chunk.data); + + my_thread_end(); + + return (void *)(res); +} + + +static +int +mode_extract(int n_threads, int argc __attribute__((unused)), + char **argv __attribute__((unused))) +{ + xb_rstream_t *stream = NULL; + HASH filehash; + ds_ctxt_t *ds_ctxt = NULL; + ds_ctxt_t *ds_decrypt_ctxt = NULL; + extract_ctxt_t ctxt; + int i; + pthread_t *tids = NULL; + void **retvals = NULL; + pthread_mutex_t mutex; + int ret = 0; + + if (my_hash_init(&filehash, &my_charset_bin, START_FILE_HASH_SIZE, + 0, 0, (my_hash_get_key) get_file_entry_key, + (my_hash_free_key) file_entry_free, MYF(0))) { + msg("%s: failed to initialize file hash.\n", my_progname); + return 1; + } + + if (pthread_mutex_init(&mutex, NULL)) { + msg("%s: failed to initialize mutex.\n", my_progname); + my_hash_free(&filehash); + return 1; + } + + /* If --directory is specified, it is already set as CWD by now. */ + ds_ctxt = ds_create(".", DS_TYPE_LOCAL); + if (ds_ctxt == NULL) { + ret = 1; + goto exit; + } + + + stream = xb_stream_read_new(); + if (stream == NULL) { + msg("%s: xb_stream_read_new() failed.\n", my_progname); + pthread_mutex_destroy(&mutex); + ret = 1; + goto exit; + } + + ctxt.stream = stream; + ctxt.filehash = &filehash; + ctxt.ds_ctxt = ds_ctxt; + ctxt.ds_decrypt_ctxt = ds_decrypt_ctxt; + ctxt.mutex = &mutex; + + tids = malloc(sizeof(pthread_t) * n_threads); + retvals = malloc(sizeof(void*) * n_threads); + + for (i = 0; i < n_threads; i++) + pthread_create(tids + i, NULL, extract_worker_thread_func, + &ctxt); + + for (i = 0; i < n_threads; i++) + pthread_join(tids[i], retvals + i); + + for (i = 0; i < n_threads; i++) { + if ((ulong)retvals[i] == XB_STREAM_READ_ERROR) { + ret = 1; + goto exit; + } + } + +exit: + pthread_mutex_destroy(&mutex); + + free(tids); + free(retvals); + + my_hash_free(&filehash); + if (ds_ctxt != NULL) { + ds_destroy(ds_ctxt); + } + if (ds_decrypt_ctxt) { + ds_destroy(ds_decrypt_ctxt); + } + xb_stream_read_done(stream); + + return ret; +} diff --git a/extra/mariabackup/xbstream.h b/extra/mariabackup/xbstream.h new file mode 100644 index 00000000000..ac1bf05e321 --- /dev/null +++ b/extra/mariabackup/xbstream.h @@ -0,0 +1,107 @@ +/****************************************************** +Copyright (c) 2011-2017 Percona LLC and/or its affiliates. + +The xbstream format interface. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#ifndef XBSTREAM_H +#define XBSTREAM_H + +#include <my_base.h> + +/* Magic value in a chunk header */ +#define XB_STREAM_CHUNK_MAGIC "XBSTCK01" + +/* Chunk flags */ +/* Chunk can be ignored if unknown version/format */ +#define XB_STREAM_FLAG_IGNORABLE 0x01 + +/* Magic + flags + type + path len */ +#define CHUNK_HEADER_CONSTANT_LEN ((sizeof(XB_STREAM_CHUNK_MAGIC) - 1) + \ + 1 + 1 + 4) +#define CHUNK_TYPE_OFFSET (sizeof(XB_STREAM_CHUNK_MAGIC) - 1 + 1) +#define PATH_LENGTH_OFFSET (sizeof(XB_STREAM_CHUNK_MAGIC) - 1 + 1 + 1) + +typedef struct xb_wstream_struct xb_wstream_t; + +typedef struct xb_wstream_file_struct xb_wstream_file_t; + +typedef enum { + XB_STREAM_FMT_NONE, + XB_STREAM_FMT_TAR, + XB_STREAM_FMT_XBSTREAM +} xb_stream_fmt_t; + +/************************************************************************ +Write interface. */ + +typedef ssize_t xb_stream_write_callback(xb_wstream_file_t *file, + void *userdata, + const void *buf, size_t len); + +xb_wstream_t *xb_stream_write_new(void); + +xb_wstream_file_t *xb_stream_write_open(xb_wstream_t *stream, const char *path, + MY_STAT *mystat, void *userdata, + xb_stream_write_callback *onwrite); + +int xb_stream_write_data(xb_wstream_file_t *file, const void *buf, size_t len); + +int xb_stream_write_close(xb_wstream_file_t *file); + +int xb_stream_write_done(xb_wstream_t *stream); + +/************************************************************************ +Read interface. */ + +typedef enum { + XB_STREAM_READ_CHUNK, + XB_STREAM_READ_EOF, + XB_STREAM_READ_ERROR +} xb_rstream_result_t; + +typedef enum { + XB_CHUNK_TYPE_UNKNOWN = '\0', + XB_CHUNK_TYPE_PAYLOAD = 'P', + XB_CHUNK_TYPE_EOF = 'E' +} xb_chunk_type_t; + +typedef struct xb_rstream_struct xb_rstream_t; + +typedef struct { + uchar flags; + xb_chunk_type_t type; + uint pathlen; + char path[FN_REFLEN]; + size_t length; + my_off_t offset; + my_off_t checksum_offset; + void *data; + ulong checksum; + size_t buflen; +} xb_rstream_chunk_t; + +xb_rstream_t *xb_stream_read_new(void); + +xb_rstream_result_t xb_stream_read_chunk(xb_rstream_t *stream, + xb_rstream_chunk_t *chunk); + +int xb_stream_read_done(xb_rstream_t *stream); + +int xb_stream_validate_checksum(xb_rstream_chunk_t *chunk); + +#endif diff --git a/extra/mariabackup/xbstream_read.c b/extra/mariabackup/xbstream_read.c new file mode 100644 index 00000000000..8d19242301b --- /dev/null +++ b/extra/mariabackup/xbstream_read.c @@ -0,0 +1,228 @@ +/****************************************************** +Copyright (c) 2011-2017 Percona LLC and/or its affiliates. + +The xbstream format reader implementation. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#include <mysql_version.h> +#include <my_base.h> +#include <zlib.h> +#include "common.h" +#include "xbstream.h" +#include "crc_glue.h" + +/* Allocate 1 MB for the payload buffer initially */ +#define INIT_BUFFER_LEN (1024 * 1024) + +#ifndef MY_OFF_T_MAX +#define MY_OFF_T_MAX (~(my_off_t)0UL) +#endif + +struct xb_rstream_struct { + my_off_t offset; + File fd; +}; + +xb_rstream_t * +xb_stream_read_new(void) +{ + xb_rstream_t *stream; + + stream = (xb_rstream_t *) my_malloc(sizeof(xb_rstream_t), MYF(MY_FAE)); + +#ifdef __WIN__ + setmode(fileno(stdin), _O_BINARY); +#endif + + stream->fd = my_fileno(stdin); + stream->offset = 0; + + return stream; +} + +static inline +xb_chunk_type_t +validate_chunk_type(uchar code) +{ + switch ((xb_chunk_type_t) code) { + case XB_CHUNK_TYPE_PAYLOAD: + case XB_CHUNK_TYPE_EOF: + return (xb_chunk_type_t) code; + default: + return XB_CHUNK_TYPE_UNKNOWN; + } +} + +int +xb_stream_validate_checksum(xb_rstream_chunk_t *chunk) +{ + ulong checksum; + + checksum = crc32_iso3309(0, chunk->data, (uint)chunk->length); + if (checksum != chunk->checksum) { + msg("xb_stream_read_chunk(): invalid checksum at offset " + "0x%llx: expected 0x%lx, read 0x%lx.\n", + (ulonglong) chunk->checksum_offset, chunk->checksum, + checksum); + return XB_STREAM_READ_ERROR; + } + + return XB_STREAM_READ_CHUNK; +} + +#define F_READ(buf,len) \ + do { \ + if (xb_read_full(fd, buf, len) < len) { \ + msg("xb_stream_read_chunk(): my_read() failed.\n"); \ + goto err; \ + } \ + } while (0) + +xb_rstream_result_t +xb_stream_read_chunk(xb_rstream_t *stream, xb_rstream_chunk_t *chunk) +{ + uchar tmpbuf[16]; + uchar *ptr = tmpbuf; + uint pathlen; + size_t tbytes; + ulonglong ullval; + File fd = stream->fd; + + xb_ad(sizeof(tmpbuf) >= CHUNK_HEADER_CONSTANT_LEN); + + /* This is the only place where we expect EOF, so read with + xb_read_full() rather than F_READ() */ + tbytes = xb_read_full(fd, ptr, CHUNK_HEADER_CONSTANT_LEN); + if (tbytes == 0) { + return XB_STREAM_READ_EOF; + } else if (tbytes < CHUNK_HEADER_CONSTANT_LEN) { + msg("xb_stream_read_chunk(): unexpected end of stream at " + "offset 0x%llx.\n", stream->offset); + goto err; + } + + ptr = tmpbuf; + + /* Chunk magic value */ + if (memcmp(tmpbuf, XB_STREAM_CHUNK_MAGIC, 8)) { + msg("xb_stream_read_chunk(): wrong chunk magic at offset " + "0x%llx.\n", (ulonglong) stream->offset); + goto err; + } + ptr += 8; + stream->offset += 8; + + /* Chunk flags */ + chunk->flags = *ptr++; + stream->offset++; + + /* Chunk type, ignore unknown ones if ignorable flag is set */ + chunk->type = validate_chunk_type(*ptr); + if (chunk->type == XB_CHUNK_TYPE_UNKNOWN && + !(chunk->flags & XB_STREAM_FLAG_IGNORABLE)) { + msg("xb_stream_read_chunk(): unknown chunk type 0x%lu at " + "offset 0x%llx.\n", (ulong) *ptr, + (ulonglong) stream->offset); + goto err; + } + ptr++; + stream->offset++; + + /* Path length */ + pathlen = uint4korr(ptr); + if (pathlen >= FN_REFLEN) { + msg("xb_stream_read_chunk(): path length (%lu) is too large at " + "offset 0x%llx.\n", (ulong) pathlen, stream->offset); + goto err; + } + chunk->pathlen = pathlen; + stream->offset +=4; + + xb_ad((ptr + 4 - tmpbuf) == CHUNK_HEADER_CONSTANT_LEN); + + /* Path */ + if (chunk->pathlen > 0) { + F_READ((uchar *) chunk->path, pathlen); + stream->offset += pathlen; + } + chunk->path[pathlen] = '\0'; + + if (chunk->type == XB_CHUNK_TYPE_EOF) { + return XB_STREAM_READ_CHUNK; + } + + /* Payload length */ + F_READ(tmpbuf, 16); + ullval = uint8korr(tmpbuf); + if (ullval > (ulonglong) SIZE_T_MAX) { + msg("xb_stream_read_chunk(): chunk length is too large at " + "offset 0x%llx: 0x%llx.\n", (ulonglong) stream->offset, + ullval); + goto err; + } + chunk->length = (size_t) ullval; + stream->offset += 8; + + /* Payload offset */ + ullval = uint8korr(tmpbuf + 8); + if (ullval > (ulonglong) MY_OFF_T_MAX) { + msg("xb_stream_read_chunk(): chunk offset is too large at " + "offset 0x%llx: 0x%llx.\n", (ulonglong) stream->offset, + ullval); + goto err; + } + chunk->offset = (my_off_t) ullval; + stream->offset += 8; + + /* Reallocate the buffer if needed */ + if (chunk->length > chunk->buflen) { + chunk->data = my_realloc(chunk->data, chunk->length, + MYF(MY_WME | MY_ALLOW_ZERO_PTR)); + if (chunk->data == NULL) { + msg("xb_stream_read_chunk(): failed to increase buffer " + "to %lu bytes.\n", (ulong) chunk->length); + goto err; + } + chunk->buflen = chunk->length; + } + + /* Checksum */ + F_READ(tmpbuf, 4); + chunk->checksum = uint4korr(tmpbuf); + chunk->checksum_offset = stream->offset; + + /* Payload */ + if (chunk->length > 0) { + F_READ(chunk->data, chunk->length); + stream->offset += chunk->length; + } + + stream->offset += 4; + + return XB_STREAM_READ_CHUNK; + +err: + return XB_STREAM_READ_ERROR; +} + +int +xb_stream_read_done(xb_rstream_t *stream) +{ + my_free(stream); + + return 0; +} diff --git a/extra/mariabackup/xbstream_write.c b/extra/mariabackup/xbstream_write.c new file mode 100644 index 00000000000..978be71e7dd --- /dev/null +++ b/extra/mariabackup/xbstream_write.c @@ -0,0 +1,294 @@ +/****************************************************** +Copyright (c) 2011-2017 Percona LLC and/or its affiliates. + +The xbstream format writer implementation. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#include <mysql_version.h> +#include <my_base.h> +#include <zlib.h> +#include "common.h" +#include "xbstream.h" +#include "crc_glue.h" + +/* Group writes smaller than this into a single chunk */ +#define XB_STREAM_MIN_CHUNK_SIZE (10 * 1024 * 1024) + +struct xb_wstream_struct { + pthread_mutex_t mutex; +}; + +struct xb_wstream_file_struct { + xb_wstream_t *stream; + char *path; + size_t path_len; + char chunk[XB_STREAM_MIN_CHUNK_SIZE]; + char *chunk_ptr; + size_t chunk_free; + my_off_t offset; + void *userdata; + xb_stream_write_callback *write; +}; + +static int xb_stream_flush(xb_wstream_file_t *file); +static int xb_stream_write_chunk(xb_wstream_file_t *file, + const void *buf, size_t len); +static int xb_stream_write_eof(xb_wstream_file_t *file); + +static +ssize_t +xb_stream_default_write_callback(xb_wstream_file_t *file __attribute__((unused)), + void *userdata __attribute__((unused)), + const void *buf, size_t len) +{ + if (my_write(my_fileno(stdout), buf, len, MYF(MY_WME | MY_NABP))) + return -1; + return len; +} + +xb_wstream_t * +xb_stream_write_new(void) +{ + xb_wstream_t *stream; + + stream = (xb_wstream_t *) my_malloc(sizeof(xb_wstream_t), MYF(MY_FAE)); + pthread_mutex_init(&stream->mutex, NULL); + + return stream;; +} + +xb_wstream_file_t * +xb_stream_write_open(xb_wstream_t *stream, const char *path, + MY_STAT *mystat __attribute__((unused)), + void *userdata, + xb_stream_write_callback *onwrite) +{ + xb_wstream_file_t *file; + size_t path_len; + + path_len = strlen(path); + + if (path_len > FN_REFLEN) { + msg("xb_stream_write_open(): file path is too long.\n"); + return NULL; + } + + file = (xb_wstream_file_t *) my_malloc(sizeof(xb_wstream_file_t) + + path_len + 1, MYF(MY_FAE)); + + file->path = (char *) (file + 1); +#ifdef _WIN32 + /* Normalize path on Windows, so we can restore elsewhere.*/ + { + int i; + for (i = 0; ; i++) { + file->path[i] = (path[i] == '\\') ? '/' : path[i]; + if (!path[i]) + break; + } + } +#else + memcpy(file->path, path, path_len + 1); +#endif + file->path_len = path_len; + + file->stream = stream; + file->offset = 0; + file->chunk_ptr = file->chunk; + file->chunk_free = XB_STREAM_MIN_CHUNK_SIZE; + if (onwrite) { +#ifdef __WIN__ + setmode(fileno(stdout), _O_BINARY); +#endif + file->userdata = userdata; + file->write = onwrite; + } else { + file->userdata = NULL; + file->write = xb_stream_default_write_callback; + } + + return file; +} + +int +xb_stream_write_data(xb_wstream_file_t *file, const void *buf, size_t len) +{ + if (len < file->chunk_free) { + memcpy(file->chunk_ptr, buf, len); + file->chunk_ptr += len; + file->chunk_free -= len; + + return 0; + } + + if (xb_stream_flush(file)) + return 1; + + return xb_stream_write_chunk(file, buf, len); +} + +int +xb_stream_write_close(xb_wstream_file_t *file) +{ + if (xb_stream_flush(file) || + xb_stream_write_eof(file)) { + my_free(file); + return 1; + } + + my_free(file); + + return 0; +} + +int +xb_stream_write_done(xb_wstream_t *stream) +{ + pthread_mutex_destroy(&stream->mutex); + + my_free(stream); + + return 0; +} + +static +int +xb_stream_flush(xb_wstream_file_t *file) +{ + if (file->chunk_ptr == file->chunk) { + return 0; + } + + if (xb_stream_write_chunk(file, file->chunk, + file->chunk_ptr - file->chunk)) { + return 1; + } + + file->chunk_ptr = file->chunk; + file->chunk_free = XB_STREAM_MIN_CHUNK_SIZE; + + return 0; +} + +static +int +xb_stream_write_chunk(xb_wstream_file_t *file, const void *buf, size_t len) +{ + /* Chunk magic + flags + chunk type + path_len + path + len + offset + + checksum */ + uchar tmpbuf[sizeof(XB_STREAM_CHUNK_MAGIC) - 1 + 1 + 1 + 4 + + FN_REFLEN + 8 + 8 + 4]; + uchar *ptr; + xb_wstream_t *stream = file->stream; + ulong checksum; + + /* Write xbstream header */ + ptr = tmpbuf; + + /* Chunk magic */ + memcpy(ptr, XB_STREAM_CHUNK_MAGIC, sizeof(XB_STREAM_CHUNK_MAGIC) - 1); + ptr += sizeof(XB_STREAM_CHUNK_MAGIC) - 1; + + *ptr++ = 0; /* Chunk flags */ + + *ptr++ = (uchar) XB_CHUNK_TYPE_PAYLOAD; /* Chunk type */ + + int4store(ptr, file->path_len); /* Path length */ + ptr += 4; + + memcpy(ptr, file->path, file->path_len); /* Path */ + ptr += file->path_len; + + int8store(ptr, len); /* Payload length */ + ptr += 8; + + checksum = crc32_iso3309(0, buf, (uint)len); /* checksum */ + + pthread_mutex_lock(&stream->mutex); + + int8store(ptr, file->offset); /* Payload offset */ + ptr += 8; + + int4store(ptr, checksum); + ptr += 4; + + xb_ad(ptr <= tmpbuf + sizeof(tmpbuf)); + + if (file->write(file, file->userdata, tmpbuf, ptr-tmpbuf) == -1) + goto err; + + + if (file->write(file, file->userdata, buf, len) == -1) /* Payload */ + goto err; + + file->offset+= len; + + pthread_mutex_unlock(&stream->mutex); + + return 0; + +err: + + pthread_mutex_unlock(&stream->mutex); + + return 1; +} + +static +int +xb_stream_write_eof(xb_wstream_file_t *file) +{ + /* Chunk magic + flags + chunk type + path_len + path */ + uchar tmpbuf[sizeof(XB_STREAM_CHUNK_MAGIC) - 1 + 1 + 1 + 4 + + FN_REFLEN]; + uchar *ptr; + xb_wstream_t *stream = file->stream; + + pthread_mutex_lock(&stream->mutex); + + /* Write xbstream header */ + ptr = tmpbuf; + + /* Chunk magic */ + memcpy(ptr, XB_STREAM_CHUNK_MAGIC, sizeof(XB_STREAM_CHUNK_MAGIC) - 1); + ptr += sizeof(XB_STREAM_CHUNK_MAGIC) - 1; + + *ptr++ = 0; /* Chunk flags */ + + *ptr++ = (uchar) XB_CHUNK_TYPE_EOF; /* Chunk type */ + + int4store(ptr, file->path_len); /* Path length */ + ptr += 4; + + memcpy(ptr, file->path, file->path_len); /* Path */ + ptr += file->path_len; + + xb_ad(ptr <= tmpbuf + sizeof(tmpbuf)); + + if (file->write(file, file->userdata, tmpbuf, + (ulonglong) (ptr - tmpbuf)) == -1) + goto err; + + pthread_mutex_unlock(&stream->mutex); + + return 0; +err: + + pthread_mutex_unlock(&stream->mutex); + + return 1; +} diff --git a/extra/mariabackup/xtrabackup.cc b/extra/mariabackup/xtrabackup.cc new file mode 100644 index 00000000000..4fe80819622 --- /dev/null +++ b/extra/mariabackup/xtrabackup.cc @@ -0,0 +1,7501 @@ +/****************************************************** +XtraBackup: hot backup tool for InnoDB +(c) 2009-2017 Percona LLC and/or its affiliates +Originally Created 3/3/2009 Yasufumi Kinoshita +Written by Alexey Kopytov, Aleksandr Kuzminsky, Stewart Smith, Vadim Tkachenko, +Yasufumi Kinoshita, Ignacio Nin and Baron Schwartz. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +******************************************************* + +This file incorporates work covered by the following copyright and +permission notice: + +Copyright (c) 2000, 2011, MySQL AB & Innobase Oy. All Rights Reserved. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 59 Temple +Place, Suite 330, Boston, MA 02111-1307 USA + +*******************************************************/ + +//#define XTRABACKUP_TARGET_IS_PLUGIN + +#include <mysql_version.h> +#include <my_base.h> +#include <my_getopt.h> +#include <mysql_com.h> +#include <my_default.h> +#include <mysqld.h> + +#include <fcntl.h> +#include <string.h> + +#ifdef __linux__ +# include <sys/prctl.h> +#include <sys/resource.h> +#endif + + +#include <btr0sea.h> +#include <dict0priv.h> +#include <dict0stats.h> +#include <lock0lock.h> +#include <log0recv.h> +#include <row0mysql.h> +#include <row0quiesce.h> +#include <srv0start.h> +#include <buf0dblwr.h> + +#include <list> +#include <sstream> +#include <set> +#include <mysql.h> + +#define G_PTR uchar* + +#include "common.h" +#include "datasink.h" + +#include "xb_regex.h" +#include "fil_cur.h" +#include "write_filt.h" +#include "xtrabackup.h" +#include "ds_buffer.h" +#include "ds_tmpfile.h" +#include "xbstream.h" +#include "changed_page_bitmap.h" +#include "read_filt.h" +#include "wsrep.h" +#include "innobackupex.h" +#include "backup_mysql.h" +#include "backup_copy.h" +#include "backup_mysql.h" +#include "xb0xb.h" +#include "encryption_plugin.h" +#include <sql_plugin.h> +#include <srv0srv.h> +#include <crc_glue.h> + +/* TODO: replace with appropriate macros used in InnoDB 5.6 */ +#define PAGE_ZIP_MIN_SIZE_SHIFT 10 +#define DICT_TF_ZSSIZE_SHIFT 1 +#define DICT_TF_FORMAT_ZIP 1 +#define DICT_TF_FORMAT_SHIFT 5 + +int sys_var_init(); + +my_bool innodb_inited= 0; + +/* === xtrabackup specific options === */ +char xtrabackup_real_target_dir[FN_REFLEN] = "./xtrabackup_backupfiles/"; +char *xtrabackup_target_dir= xtrabackup_real_target_dir; +my_bool xtrabackup_version = FALSE; +my_bool xtrabackup_backup = FALSE; +my_bool xtrabackup_stats = FALSE; +my_bool xtrabackup_prepare = FALSE; +my_bool xtrabackup_copy_back = FALSE; +my_bool xtrabackup_move_back = FALSE; +my_bool xtrabackup_decrypt_decompress = FALSE; +my_bool xtrabackup_print_param = FALSE; + +my_bool xtrabackup_export = FALSE; +my_bool xtrabackup_apply_log_only = FALSE; + +longlong xtrabackup_use_memory = 100*1024*1024L; +my_bool xtrabackup_create_ib_logfile = FALSE; + +long xtrabackup_throttle = 0; /* 0:unlimited */ +lint io_ticket; +os_event_t wait_throttle = NULL; +os_event_t log_copying_stop = NULL; + +char *xtrabackup_incremental = NULL; +lsn_t incremental_lsn; +lsn_t incremental_to_lsn; +lsn_t incremental_last_lsn; +xb_page_bitmap *changed_page_bitmap = NULL; + +char *xtrabackup_incremental_basedir = NULL; /* for --backup */ +char *xtrabackup_extra_lsndir = NULL; /* for --backup with --extra-lsndir */ +char *xtrabackup_incremental_dir = NULL; /* for --prepare */ + +char xtrabackup_real_incremental_basedir[FN_REFLEN]; +char xtrabackup_real_extra_lsndir[FN_REFLEN]; +char xtrabackup_real_incremental_dir[FN_REFLEN]; + +lsn_t xtrabackup_archived_to_lsn = 0; /* for --archived-to-lsn */ + +char *xtrabackup_tmpdir; + +char *xtrabackup_tables = NULL; +char *xtrabackup_tables_file = NULL; +char *xtrabackup_tables_exclude = NULL; + +typedef std::list<regex_t> regex_list_t; +static regex_list_t regex_include_list; +static regex_list_t regex_exclude_list; + +static hash_table_t* tables_include_hash = NULL; +static hash_table_t* tables_exclude_hash = NULL; + +char *xtrabackup_databases = NULL; +char *xtrabackup_databases_file = NULL; +char *xtrabackup_databases_exclude = NULL; +static hash_table_t* databases_include_hash = NULL; +static hash_table_t* databases_exclude_hash = NULL; + +static hash_table_t* inc_dir_tables_hash; + +struct xb_filter_entry_struct{ + char* name; + ibool has_tables; + hash_node_t name_hash; +}; +typedef struct xb_filter_entry_struct xb_filter_entry_t; + +static ulint thread_nr[SRV_MAX_N_IO_THREADS + 6]; +static os_thread_id_t thread_ids[SRV_MAX_N_IO_THREADS + 6]; + +lsn_t checkpoint_lsn_start; +lsn_t checkpoint_no_start; +lsn_t log_copy_scanned_lsn; +ibool log_copying = TRUE; +ibool log_copying_running = FALSE; +ibool io_watching_thread_running = FALSE; + +ibool xtrabackup_logfile_is_renamed = FALSE; + +int xtrabackup_parallel; + +char *xtrabackup_stream_str = NULL; +xb_stream_fmt_t xtrabackup_stream_fmt = XB_STREAM_FMT_NONE; +ibool xtrabackup_stream = FALSE; + +const char *xtrabackup_compress_alg = NULL; +ibool xtrabackup_compress = FALSE; +uint xtrabackup_compress_threads; +ulonglong xtrabackup_compress_chunk_size = 0; + +const char *xtrabackup_encrypt_algo_names[] = +{ "NONE", "AES128", "AES192", "AES256", NullS}; +TYPELIB xtrabackup_encrypt_algo_typelib= +{array_elements(xtrabackup_encrypt_algo_names)-1,"", + xtrabackup_encrypt_algo_names, NULL}; + +ibool xtrabackup_encrypt = FALSE; +ulong xtrabackup_encrypt_algo; +char *xtrabackup_encrypt_key = NULL; +char *xtrabackup_encrypt_key_file = NULL; +uint xtrabackup_encrypt_threads; +ulonglong xtrabackup_encrypt_chunk_size = 0; + +ulint xtrabackup_rebuild_threads = 1; + +/* sleep interval beetween log copy iterations in log copying thread +in milliseconds (default is 1 second) */ +ulint xtrabackup_log_copy_interval = 1000; +static ulong max_buf_pool_modified_pct; + +/* Ignored option (--log) for MySQL option compatibility */ +char* log_ignored_opt = NULL; + +/* === metadata of backup === */ +#define XTRABACKUP_METADATA_FILENAME "xtrabackup_checkpoints" +char metadata_type[30] = ""; /*[full-backuped|log-applied| + full-prepared|incremental]*/ +lsn_t metadata_from_lsn = 0; +lsn_t metadata_to_lsn = 0; +lsn_t metadata_last_lsn = 0; + +#define XB_LOG_FILENAME "xtrabackup_logfile" + +ds_file_t *dst_log_file = NULL; + +static char mysql_data_home_buff[2]; + +const char *defaults_group = "mysqld"; + +/* === static parameters in ha_innodb.cc */ + +#define HA_INNOBASE_ROWS_IN_TABLE 10000 /* to get optimization right */ +#define HA_INNOBASE_RANGE_COUNT 100 + +ulong innobase_large_page_size = 0; + +/* The default values for the following, type long or longlong, start-up +parameters are declared in mysqld.cc: */ + +long innobase_additional_mem_pool_size = 1*1024*1024L; +long innobase_buffer_pool_awe_mem_mb = 0; +long innobase_file_io_threads = 4; +long innobase_read_io_threads = 4; +long innobase_write_io_threads = 4; +long innobase_force_recovery = 0; +long innobase_log_buffer_size = 1024*1024L; +long innobase_log_files_in_group = 2; +long innobase_open_files = 300L; + +longlong innobase_page_size = (1LL << 14); /* 16KB */ +static ulong innobase_log_block_size = 512; +my_bool innobase_fast_checksum = FALSE; +char* innobase_doublewrite_file = NULL; +char* innobase_buffer_pool_filename = NULL; + +longlong innobase_buffer_pool_size = 8*1024*1024L; +longlong innobase_log_file_size = 48*1024*1024L; + +/* The default values for the following char* start-up parameters +are determined in innobase_init below: */ + +char* innobase_ignored_opt = NULL; +char* innobase_data_home_dir = NULL; +char* innobase_data_file_path = NULL; +char* innobase_log_arch_dir = NULL;/* unused */ +/* The following has a misleading name: starting from 4.0.5, this also +affects Windows: */ +char* innobase_unix_file_flush_method = NULL; + +/* Below we have boolean-valued start-up parameters, and their default +values */ + +ulong innobase_fast_shutdown = 1; +my_bool innobase_log_archive = FALSE;/* unused */ +my_bool innobase_use_doublewrite = TRUE; +my_bool innobase_use_checksums = TRUE; +my_bool innobase_use_large_pages = FALSE; +my_bool innobase_file_per_table = FALSE; +my_bool innobase_locks_unsafe_for_binlog = FALSE; +my_bool innobase_rollback_on_timeout = FALSE; +my_bool innobase_create_status_file = FALSE; +my_bool innobase_adaptive_hash_index = TRUE; + +static char *internal_innobase_data_file_path = NULL; + +/* The following counter is used to convey information to InnoDB +about server activity: in selects it is not sensible to call +srv_active_wake_master_thread after each fetch or search, we only do +it every INNOBASE_WAKE_INTERVAL'th step. */ + +#define INNOBASE_WAKE_INTERVAL 32 +ulong innobase_active_counter = 0; + + +static char *xtrabackup_debug_sync = NULL; + +my_bool xtrabackup_compact = FALSE; +my_bool xtrabackup_rebuild_indexes = FALSE; + +my_bool xtrabackup_incremental_force_scan = FALSE; + +/* The flushed lsn which is read from data files */ +lsn_t min_flushed_lsn= 0; +lsn_t max_flushed_lsn= 0; + +/* The size of archived log file */ +ib_int64_t xtrabackup_arch_file_size = 0ULL; +/* The minimal LSN of found archived log files */ +lsn_t xtrabackup_arch_first_file_lsn = 0ULL; +/* The maximum LSN of found archived log files */ +lsn_t xtrabackup_arch_last_file_lsn = 0ULL; + +ulong xb_open_files_limit= 0; +char *xb_plugin_dir; +char *xb_plugin_load; +my_bool xb_close_files= FALSE; + +/* Datasinks */ +ds_ctxt_t *ds_data = NULL; +ds_ctxt_t *ds_meta = NULL; +ds_ctxt_t *ds_redo = NULL; + +static bool innobackupex_mode = false; + +static long innobase_log_files_in_group_save; +static char *srv_log_group_home_dir_save; +static longlong innobase_log_file_size_save; + +/* String buffer used by --print-param to accumulate server options as they are +parsed from the defaults file */ +static std::ostringstream print_param_str; + +/* Set of specified parameters */ +std::set<std::string> param_set; + +static ulonglong global_max_value; + +extern "C" sig_handler handle_fatal_signal(int sig); + +my_bool opt_galera_info = FALSE; +my_bool opt_slave_info = FALSE; +my_bool opt_no_lock = FALSE; +my_bool opt_safe_slave_backup = FALSE; +my_bool opt_rsync = FALSE; +my_bool opt_force_non_empty_dirs = FALSE; +my_bool opt_noversioncheck = FALSE; +my_bool opt_no_backup_locks = FALSE; +my_bool opt_decompress = FALSE; +my_bool opt_remove_original = FALSE; + +static const char *binlog_info_values[] = {"off", "lockless", "on", "auto", + NullS}; +static TYPELIB binlog_info_typelib = {array_elements(binlog_info_values)-1, "", + binlog_info_values, NULL}; +ulong opt_binlog_info; + +char *opt_incremental_history_name = NULL; +char *opt_incremental_history_uuid = NULL; + +char *opt_user = NULL; +char *opt_password = NULL; +char *opt_host = NULL; +char *opt_defaults_group = NULL; +char *opt_socket = NULL; +uint opt_port = 0; +char *opt_login_path = NULL; +char *opt_log_bin = NULL; + +const char *query_type_names[] = { "ALL", "UPDATE", "SELECT", NullS}; + +TYPELIB query_type_typelib= {array_elements(query_type_names) - 1, "", + query_type_names, NULL}; + +ulong opt_lock_wait_query_type; +ulong opt_kill_long_query_type; + +ulong opt_decrypt_algo = 0; + +uint opt_kill_long_queries_timeout = 0; +uint opt_lock_wait_timeout = 0; +uint opt_lock_wait_threshold = 0; +uint opt_debug_sleep_before_unlock = 0; +uint opt_safe_slave_backup_timeout = 0; + +const char *opt_history = NULL; +my_bool opt_decrypt = FALSE; + +#if defined(HAVE_OPENSSL) +my_bool opt_ssl_verify_server_cert = FALSE; +#if !defined(HAVE_YASSL) +char *opt_server_public_key = NULL; +#endif +#endif + +/* Whether xtrabackup_binlog_info should be created on recovery */ +static bool recover_binlog_info; + +/* Simple datasink creation tracking...add datasinks in the reverse order you +want them destroyed. */ +#define XTRABACKUP_MAX_DATASINKS 10 +static ds_ctxt_t *datasinks[XTRABACKUP_MAX_DATASINKS]; +static uint actual_datasinks = 0; +static inline +void +xtrabackup_add_datasink(ds_ctxt_t *ds) +{ + xb_ad(actual_datasinks < XTRABACKUP_MAX_DATASINKS); + datasinks[actual_datasinks] = ds; actual_datasinks++; +} + +/* ======== Datafiles iterator ======== */ +datafiles_iter_t * +datafiles_iter_new(fil_system_t *f_system) +{ + datafiles_iter_t *it; + + it = static_cast<datafiles_iter_t *> + (ut_malloc(sizeof(datafiles_iter_t))); + it->mutex = os_mutex_create(); + + it->system = f_system; + it->space = NULL; + it->node = NULL; + it->started = FALSE; + + return it; +} + +fil_node_t * +datafiles_iter_next(datafiles_iter_t *it) +{ + fil_node_t *new_node; + + os_mutex_enter(it->mutex); + + if (it->node == NULL) { + if (it->started) + goto end; + it->started = TRUE; + } else { + it->node = UT_LIST_GET_NEXT(chain, it->node); + if (it->node != NULL) + goto end; + } + + it->space = (it->space == NULL) ? + UT_LIST_GET_FIRST(it->system->space_list) : + UT_LIST_GET_NEXT(space_list, it->space); + + while (it->space != NULL && + (it->space->purpose != FIL_TABLESPACE || + UT_LIST_GET_LEN(it->space->chain) == 0)) + it->space = UT_LIST_GET_NEXT(space_list, it->space); + if (it->space == NULL) + goto end; + + it->node = UT_LIST_GET_FIRST(it->space->chain); + +end: + new_node = it->node; + os_mutex_exit(it->mutex); + + return new_node; +} + +void +datafiles_iter_free(datafiles_iter_t *it) +{ + os_mutex_free(it->mutex); + ut_free(it); +} + +/* ======== Date copying thread context ======== */ + +typedef struct { + datafiles_iter_t *it; + uint num; + uint *count; + os_ib_mutex_t count_mutex; + os_thread_id_t id; +} data_thread_ctxt_t; + +/* ======== for option and variables ======== */ + +enum options_xtrabackup +{ + OPT_XTRA_TARGET_DIR = 1000, /* make sure it is larger + than OPT_MAX_CLIENT_OPTION */ + OPT_XTRA_BACKUP, + OPT_XTRA_STATS, + OPT_XTRA_PREPARE, + OPT_XTRA_EXPORT, + OPT_XTRA_APPLY_LOG_ONLY, + OPT_XTRA_PRINT_PARAM, + OPT_XTRA_USE_MEMORY, + OPT_XTRA_THROTTLE, + OPT_XTRA_LOG_COPY_INTERVAL, + OPT_XTRA_INCREMENTAL, + OPT_XTRA_INCREMENTAL_BASEDIR, + OPT_XTRA_EXTRA_LSNDIR, + OPT_XTRA_INCREMENTAL_DIR, + OPT_XTRA_ARCHIVED_TO_LSN, + OPT_XTRA_TABLES, + OPT_XTRA_TABLES_FILE, + OPT_XTRA_DATABASES, + OPT_XTRA_DATABASES_FILE, + OPT_XTRA_CREATE_IB_LOGFILE, + OPT_XTRA_PARALLEL, + OPT_XTRA_STREAM, + OPT_XTRA_COMPRESS, + OPT_XTRA_COMPRESS_THREADS, + OPT_XTRA_COMPRESS_CHUNK_SIZE, + OPT_XTRA_ENCRYPT, + OPT_XTRA_ENCRYPT_KEY, + OPT_XTRA_ENCRYPT_KEY_FILE, + OPT_XTRA_ENCRYPT_THREADS, + OPT_XTRA_ENCRYPT_CHUNK_SIZE, + OPT_LOG, + OPT_INNODB, + OPT_INNODB_CHECKSUMS, + OPT_INNODB_DATA_FILE_PATH, + OPT_INNODB_DATA_HOME_DIR, + OPT_INNODB_ADAPTIVE_HASH_INDEX, + OPT_INNODB_DOUBLEWRITE, + OPT_INNODB_FAST_SHUTDOWN, + OPT_INNODB_FILE_PER_TABLE, + OPT_INNODB_FLUSH_LOG_AT_TRX_COMMIT, + OPT_INNODB_FLUSH_METHOD, + OPT_INNODB_LOCKS_UNSAFE_FOR_BINLOG, + OPT_INNODB_LOG_ARCH_DIR, + OPT_INNODB_LOG_ARCHIVE, + OPT_INNODB_LOG_GROUP_HOME_DIR, + OPT_INNODB_MAX_DIRTY_PAGES_PCT, + OPT_INNODB_MAX_PURGE_LAG, + OPT_INNODB_ROLLBACK_ON_TIMEOUT, + OPT_INNODB_STATUS_FILE, + OPT_INNODB_ADDITIONAL_MEM_POOL_SIZE, + OPT_INNODB_AUTOEXTEND_INCREMENT, + OPT_INNODB_BUFFER_POOL_SIZE, + OPT_INNODB_COMMIT_CONCURRENCY, + OPT_INNODB_CONCURRENCY_TICKETS, + OPT_INNODB_FILE_IO_THREADS, + OPT_INNODB_IO_CAPACITY, + OPT_INNODB_READ_IO_THREADS, + OPT_INNODB_WRITE_IO_THREADS, + OPT_INNODB_USE_NATIVE_AIO, + OPT_INNODB_PAGE_SIZE, + OPT_INNODB_LOG_BLOCK_SIZE, + OPT_INNODB_FAST_CHECKSUM, + OPT_INNODB_EXTRA_UNDOSLOTS, + OPT_INNODB_DOUBLEWRITE_FILE, + OPT_INNODB_BUFFER_POOL_FILENAME, + OPT_INNODB_FORCE_RECOVERY, + OPT_INNODB_LOCK_WAIT_TIMEOUT, + OPT_INNODB_LOG_BUFFER_SIZE, + OPT_INNODB_LOG_FILE_SIZE, + OPT_INNODB_LOG_FILES_IN_GROUP, + OPT_INNODB_MIRRORED_LOG_GROUPS, + OPT_INNODB_OPEN_FILES, + OPT_INNODB_SYNC_SPIN_LOOPS, + OPT_INNODB_THREAD_CONCURRENCY, + OPT_INNODB_THREAD_SLEEP_DELAY, + OPT_XTRA_DEBUG_SYNC, + OPT_XTRA_COMPACT, + OPT_XTRA_REBUILD_INDEXES, + OPT_XTRA_REBUILD_THREADS, + OPT_INNODB_CHECKSUM_ALGORITHM, + OPT_INNODB_UNDO_DIRECTORY, + OPT_INNODB_UNDO_TABLESPACES, + OPT_INNODB_LOG_CHECKSUM_ALGORITHM, + OPT_XTRA_INCREMENTAL_FORCE_SCAN, + OPT_DEFAULTS_GROUP, + OPT_OPEN_FILES_LIMIT, + OPT_PLUGIN_DIR, + OPT_PLUGIN_LOAD, + OPT_INNODB_ENCRYPT_LOG, + OPT_CLOSE_FILES, + OPT_CORE_FILE, + + OPT_COPY_BACK, + OPT_MOVE_BACK, + OPT_GALERA_INFO, + OPT_SLAVE_INFO, + OPT_NO_LOCK, + OPT_SAFE_SLAVE_BACKUP, + OPT_RSYNC, + OPT_FORCE_NON_EMPTY_DIRS, + OPT_NO_VERSION_CHECK, + OPT_NO_BACKUP_LOCKS, + OPT_DECOMPRESS, + OPT_INCREMENTAL_HISTORY_NAME, + OPT_INCREMENTAL_HISTORY_UUID, + OPT_DECRYPT, + OPT_REMOVE_ORIGINAL, + OPT_LOCK_WAIT_QUERY_TYPE, + OPT_KILL_LONG_QUERY_TYPE, + OPT_HISTORY, + OPT_KILL_LONG_QUERIES_TIMEOUT, + OPT_LOCK_WAIT_TIMEOUT, + OPT_LOCK_WAIT_THRESHOLD, + OPT_DEBUG_SLEEP_BEFORE_UNLOCK, + OPT_SAFE_SLAVE_BACKUP_TIMEOUT, + OPT_BINLOG_INFO, + OPT_XB_SECURE_AUTH, + + OPT_SSL_SSL, + OPT_SSL_VERIFY_SERVER_CERT, + OPT_SERVER_PUBLIC_KEY, + + OPT_XTRA_TABLES_EXCLUDE, + OPT_XTRA_DATABASES_EXCLUDE, +}; + +struct my_option xb_client_options[] = +{ + {"version", 'v', "print xtrabackup version information", + (G_PTR *) &xtrabackup_version, (G_PTR *) &xtrabackup_version, 0, GET_BOOL, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"target-dir", OPT_XTRA_TARGET_DIR, "destination directory", (G_PTR*) &xtrabackup_target_dir, + (G_PTR*) &xtrabackup_target_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"backup", OPT_XTRA_BACKUP, "take backup to target-dir", + (G_PTR*) &xtrabackup_backup, (G_PTR*) &xtrabackup_backup, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"stats", OPT_XTRA_STATS, "calc statistic of datadir (offline mysqld is recommended)", + (G_PTR*) &xtrabackup_stats, (G_PTR*) &xtrabackup_stats, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"prepare", OPT_XTRA_PREPARE, "prepare a backup for starting mysql server on the backup.", + (G_PTR*) &xtrabackup_prepare, (G_PTR*) &xtrabackup_prepare, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"export", OPT_XTRA_EXPORT, "create files to import to another database when prepare.", + (G_PTR*) &xtrabackup_export, (G_PTR*) &xtrabackup_export, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"apply-log-only", OPT_XTRA_APPLY_LOG_ONLY, + "stop recovery process not to progress LSN after applying log when prepare.", + (G_PTR*) &xtrabackup_apply_log_only, (G_PTR*) &xtrabackup_apply_log_only, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"print-param", OPT_XTRA_PRINT_PARAM, "print parameter of mysqld needed for copyback.", + (G_PTR*) &xtrabackup_print_param, (G_PTR*) &xtrabackup_print_param, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"use-memory", OPT_XTRA_USE_MEMORY, "The value is used instead of buffer_pool_size", + (G_PTR*) &xtrabackup_use_memory, (G_PTR*) &xtrabackup_use_memory, + 0, GET_LL, REQUIRED_ARG, 100*1024*1024L, 1024*1024L, LONGLONG_MAX, 0, + 1024*1024L, 0}, + {"throttle", OPT_XTRA_THROTTLE, "limit count of IO operations (pairs of read&write) per second to IOS values (for '--backup')", + (G_PTR*) &xtrabackup_throttle, (G_PTR*) &xtrabackup_throttle, + 0, GET_LONG, REQUIRED_ARG, 0, 0, LONG_MAX, 0, 1, 0}, + {"log", OPT_LOG, "Ignored option for MySQL option compatibility", + (G_PTR*) &log_ignored_opt, (G_PTR*) &log_ignored_opt, 0, + GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, + {"log-copy-interval", OPT_XTRA_LOG_COPY_INTERVAL, "time interval between checks done by log copying thread in milliseconds (default is 1 second).", + (G_PTR*) &xtrabackup_log_copy_interval, (G_PTR*) &xtrabackup_log_copy_interval, + 0, GET_LONG, REQUIRED_ARG, 1000, 0, LONG_MAX, 0, 1, 0}, + {"extra-lsndir", OPT_XTRA_EXTRA_LSNDIR, "(for --backup): save an extra copy of the xtrabackup_checkpoints file in this directory.", + (G_PTR*) &xtrabackup_extra_lsndir, (G_PTR*) &xtrabackup_extra_lsndir, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"incremental-lsn", OPT_XTRA_INCREMENTAL, "(for --backup): copy only .ibd pages newer than specified LSN 'high:low'. ##ATTENTION##: If a wrong LSN value is specified, it is impossible to diagnose this, causing the backup to be unusable. Be careful!", + (G_PTR*) &xtrabackup_incremental, (G_PTR*) &xtrabackup_incremental, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"incremental-basedir", OPT_XTRA_INCREMENTAL_BASEDIR, "(for --backup): copy only .ibd pages newer than backup at specified directory.", + (G_PTR*) &xtrabackup_incremental_basedir, (G_PTR*) &xtrabackup_incremental_basedir, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"incremental-dir", OPT_XTRA_INCREMENTAL_DIR, "(for --prepare): apply .delta files and logfile in the specified directory.", + (G_PTR*) &xtrabackup_incremental_dir, (G_PTR*) &xtrabackup_incremental_dir, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"to-archived-lsn", OPT_XTRA_ARCHIVED_TO_LSN, + "Don't apply archived logs with bigger log sequence number.", + (G_PTR*) &xtrabackup_archived_to_lsn, (G_PTR*) &xtrabackup_archived_to_lsn, 0, + GET_LL, REQUIRED_ARG, 0, 0, LONGLONG_MAX, 0, 0, 0}, + {"tables", OPT_XTRA_TABLES, "filtering by regexp for table names.", + (G_PTR*) &xtrabackup_tables, (G_PTR*) &xtrabackup_tables, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"tables_file", OPT_XTRA_TABLES_FILE, "filtering by list of the exact database.table name in the file.", + (G_PTR*) &xtrabackup_tables_file, (G_PTR*) &xtrabackup_tables_file, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"databases", OPT_XTRA_DATABASES, "filtering by list of databases.", + (G_PTR*) &xtrabackup_databases, (G_PTR*) &xtrabackup_databases, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"databases_file", OPT_XTRA_TABLES_FILE, + "filtering by list of databases in the file.", + (G_PTR*) &xtrabackup_databases_file, (G_PTR*) &xtrabackup_databases_file, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"tables-exclude", OPT_XTRA_TABLES_EXCLUDE, "filtering by regexp for table names. " + "Operates the same way as --tables, but matched names are excluded from backup. " + "Note that this option has a higher priority than --tables.", + (G_PTR*) &xtrabackup_tables_exclude, (G_PTR*) &xtrabackup_tables_exclude, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"databases-exclude", OPT_XTRA_DATABASES_EXCLUDE, "Excluding databases based on name, " + "Operates the same way as --databases, but matched names are excluded from backup. " + "Note that this option has a higher priority than --databases.", + (G_PTR*) &xtrabackup_databases_exclude, (G_PTR*) &xtrabackup_databases_exclude, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"create-ib-logfile", OPT_XTRA_CREATE_IB_LOGFILE, "** not work for now** creates ib_logfile* also after '--prepare'. ### If you want create ib_logfile*, only re-execute this command in same options. ###", + (G_PTR*) &xtrabackup_create_ib_logfile, (G_PTR*) &xtrabackup_create_ib_logfile, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"stream", OPT_XTRA_STREAM, "Stream all backup files to the standard output " + "in the specified format." +#ifdef HAVE_LIBARCHIVE + "Supported formats are 'tar' and 'xbstream'." +#else + "Supported format is 'xbstream'." +#endif + , + (G_PTR*) &xtrabackup_stream_str, (G_PTR*) &xtrabackup_stream_str, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"compress", OPT_XTRA_COMPRESS, "Compress individual backup files using the " + "specified compression algorithm. Currently the only supported algorithm " + "is 'quicklz'. It is also the default algorithm, i.e. the one used when " + "--compress is used without an argument.", + (G_PTR*) &xtrabackup_compress_alg, (G_PTR*) &xtrabackup_compress_alg, 0, + GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, + + {"compress-threads", OPT_XTRA_COMPRESS_THREADS, + "Number of threads for parallel data compression. The default value is 1.", + (G_PTR*) &xtrabackup_compress_threads, (G_PTR*) &xtrabackup_compress_threads, + 0, GET_UINT, REQUIRED_ARG, 1, 1, UINT_MAX, 0, 0, 0}, + + {"compress-chunk-size", OPT_XTRA_COMPRESS_CHUNK_SIZE, + "Size of working buffer(s) for compression threads in bytes. The default value is 64K.", + (G_PTR*) &xtrabackup_compress_chunk_size, (G_PTR*) &xtrabackup_compress_chunk_size, + 0, GET_ULL, REQUIRED_ARG, (1 << 16), 1024, ULONGLONG_MAX, 0, 0, 0}, + + {"encrypt", OPT_XTRA_ENCRYPT, "Encrypt individual backup files using the " + "specified encryption algorithm.", + &xtrabackup_encrypt_algo, &xtrabackup_encrypt_algo, + &xtrabackup_encrypt_algo_typelib, GET_ENUM, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"encrypt-key", OPT_XTRA_ENCRYPT_KEY, "Encryption key to use.", + (G_PTR*) &xtrabackup_encrypt_key, (G_PTR*) &xtrabackup_encrypt_key, 0, + GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"encrypt-key-file", OPT_XTRA_ENCRYPT_KEY_FILE, "File which contains encryption key to use.", + (G_PTR*) &xtrabackup_encrypt_key_file, (G_PTR*) &xtrabackup_encrypt_key_file, 0, + GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"encrypt-threads", OPT_XTRA_ENCRYPT_THREADS, + "Number of threads for parallel data encryption. The default value is 1.", + (G_PTR*) &xtrabackup_encrypt_threads, (G_PTR*) &xtrabackup_encrypt_threads, + 0, GET_UINT, REQUIRED_ARG, 1, 1, UINT_MAX, 0, 0, 0}, + + {"encrypt-chunk-size", OPT_XTRA_ENCRYPT_CHUNK_SIZE, + "Size of working buffer(S) for encryption threads in bytes. The default value is 64K.", + (G_PTR*) &xtrabackup_encrypt_chunk_size, (G_PTR*) &xtrabackup_encrypt_chunk_size, + 0, GET_ULL, REQUIRED_ARG, (1 << 16), 1024, ULONGLONG_MAX, 0, 0, 0}, + + {"compact", OPT_XTRA_COMPACT, + "Create a compact backup by skipping secondary index pages.", + (G_PTR*) &xtrabackup_compact, (G_PTR*) &xtrabackup_compact, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"rebuild_indexes", OPT_XTRA_REBUILD_INDEXES, + "Rebuild secondary indexes in InnoDB tables after applying the log. " + "Only has effect with --prepare.", + (G_PTR*) &xtrabackup_rebuild_indexes, (G_PTR*) &xtrabackup_rebuild_indexes, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"rebuild_threads", OPT_XTRA_REBUILD_THREADS, + "Use this number of threads to rebuild indexes in a compact backup. " + "Only has effect with --prepare and --rebuild-indexes.", + (G_PTR*) &xtrabackup_rebuild_threads, (G_PTR*) &xtrabackup_rebuild_threads, + 0, GET_UINT, REQUIRED_ARG, 1, 1, UINT_MAX, 0, 0, 0}, + + {"incremental-force-scan", OPT_XTRA_INCREMENTAL_FORCE_SCAN, + "Perform a full-scan incremental backup even in the presence of changed " + "page bitmap data", + (G_PTR*)&xtrabackup_incremental_force_scan, + (G_PTR*)&xtrabackup_incremental_force_scan, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, + + + {"close_files", OPT_CLOSE_FILES, "do not keep files opened. Use at your own " + "risk.", (G_PTR*) &xb_close_files, (G_PTR*) &xb_close_files, 0, GET_BOOL, + NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"core-file", OPT_CORE_FILE, "Write core on fatal signals", 0, 0, 0, + GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + + + {"copy-back", OPT_COPY_BACK, "Copy all the files in a previously made " + "backup from the backup directory to their original locations.", + (uchar *) &xtrabackup_copy_back, (uchar *) &xtrabackup_copy_back, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"move-back", OPT_MOVE_BACK, "Move all the files in a previously made " + "backup from the backup directory to the actual datadir location. " + "Use with caution, as it removes backup files.", + (uchar *) &xtrabackup_move_back, (uchar *) &xtrabackup_move_back, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"galera-info", OPT_GALERA_INFO, "This options creates the " + "xtrabackup_galera_info file which contains the local node state at " + "the time of the backup. Option should be used when performing the " + "backup of Percona-XtraDB-Cluster. Has no effect when backup locks " + "are used to create the backup.", + (uchar *) &opt_galera_info, (uchar *) &opt_galera_info, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"slave-info", OPT_SLAVE_INFO, "This option is useful when backing " + "up a replication slave server. It prints the binary log position " + "and name of the master server. It also writes this information to " + "the \"xtrabackup_slave_info\" file as a \"CHANGE MASTER\" command. " + "A new slave for this master can be set up by starting a slave server " + "on this backup and issuing a \"CHANGE MASTER\" command with the " + "binary log position saved in the \"xtrabackup_slave_info\" file.", + (uchar *) &opt_slave_info, (uchar *) &opt_slave_info, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"no-lock", OPT_NO_LOCK, "Use this option to disable table lock " + "with \"FLUSH TABLES WITH READ LOCK\". Use it only if ALL your " + "tables are InnoDB and you DO NOT CARE about the binary log " + "position of the backup. This option shouldn't be used if there " + "are any DDL statements being executed or if any updates are " + "happening on non-InnoDB tables (this includes the system MyISAM " + "tables in the mysql database), otherwise it could lead to an " + "inconsistent backup. If you are considering to use --no-lock " + "because your backups are failing to acquire the lock, this could " + "be because of incoming replication events preventing the lock " + "from succeeding. Please try using --safe-slave-backup to " + "momentarily stop the replication slave thread, this may help " + "the backup to succeed and you then don't need to resort to " + "using this option.", + (uchar *) &opt_no_lock, (uchar *) &opt_no_lock, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"safe-slave-backup", OPT_SAFE_SLAVE_BACKUP, "Stop slave SQL thread " + "and wait to start backup until Slave_open_temp_tables in " + "\"SHOW STATUS\" is zero. If there are no open temporary tables, " + "the backup will take place, otherwise the SQL thread will be " + "started and stopped until there are no open temporary tables. " + "The backup will fail if Slave_open_temp_tables does not become " + "zero after --safe-slave-backup-timeout seconds. The slave SQL " + "thread will be restarted when the backup finishes.", + (uchar *) &opt_safe_slave_backup, + (uchar *) &opt_safe_slave_backup, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"rsync", OPT_RSYNC, "Uses the rsync utility to optimize local file " + "transfers. When this option is specified, innobackupex uses rsync " + "to copy all non-InnoDB files instead of spawning a separate cp for " + "each file, which can be much faster for servers with a large number " + "of databases or tables. This option cannot be used together with " + "--stream.", + (uchar *) &opt_rsync, (uchar *) &opt_rsync, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"force-non-empty-directories", OPT_FORCE_NON_EMPTY_DIRS, "This " + "option, when specified, makes --copy-back or --move-back transfer " + "files to non-empty directories. Note that no existing files will be " + "overwritten. If --copy-back or --nove-back has to copy a file from " + "the backup directory which already exists in the destination " + "directory, it will still fail with an error.", + (uchar *) &opt_force_non_empty_dirs, + (uchar *) &opt_force_non_empty_dirs, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"no-version-check", OPT_NO_VERSION_CHECK, "This option disables the " + "version check which is enabled by the --version-check option.", + (uchar *) &opt_noversioncheck, + (uchar *) &opt_noversioncheck, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"no-backup-locks", OPT_NO_BACKUP_LOCKS, "This option controls if " + "backup locks should be used instead of FLUSH TABLES WITH READ LOCK " + "on the backup stage. The option has no effect when backup locks are " + "not supported by the server. This option is enabled by default, " + "disable with --no-backup-locks.", + (uchar *) &opt_no_backup_locks, + (uchar *) &opt_no_backup_locks, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"decompress", OPT_DECOMPRESS, "Decompresses all files with the .qp " + "extension in a backup previously made with the --compress option.", + (uchar *) &opt_decompress, + (uchar *) &opt_decompress, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"user", 'u', "This option specifies the MySQL username used " + "when connecting to the server, if that's not the current user. " + "The option accepts a string argument. See mysql --help for details.", + (uchar*) &opt_user, (uchar*) &opt_user, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"host", 'H', "This option specifies the host to use when " + "connecting to the database server with TCP/IP. The option accepts " + "a string argument. See mysql --help for details.", + (uchar*) &opt_host, (uchar*) &opt_host, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"port", 'P', "This option specifies the port to use when " + "connecting to the database server with TCP/IP. The option accepts " + "a string argument. See mysql --help for details.", + &opt_port, &opt_port, 0, GET_UINT, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"password", 'p', "This option specifies the password to use " + "when connecting to the database. It accepts a string argument. " + "See mysql --help for details.", + 0, 0, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"socket", 'S', "This option specifies the socket to use when " + "connecting to the local database server with a UNIX domain socket. " + "The option accepts a string argument. See mysql --help for details.", + (uchar*) &opt_socket, (uchar*) &opt_socket, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"incremental-history-name", OPT_INCREMENTAL_HISTORY_NAME, + "This option specifies the name of the backup series stored in the " + "PERCONA_SCHEMA.xtrabackup_history history record to base an " + "incremental backup on. Xtrabackup will search the history table " + "looking for the most recent (highest innodb_to_lsn), successful " + "backup in the series and take the to_lsn value to use as the " + "starting lsn for the incremental backup. This will be mutually " + "exclusive with --incremental-history-uuid, --incremental-basedir " + "and --incremental-lsn. If no valid lsn can be found (no series by " + "that name, no successful backups by that name) xtrabackup will " + "return with an error. It is used with the --incremental option.", + (uchar*) &opt_incremental_history_name, + (uchar*) &opt_incremental_history_name, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"incremental-history-uuid", OPT_INCREMENTAL_HISTORY_UUID, + "This option specifies the UUID of the specific history record " + "stored in the PERCONA_SCHEMA.xtrabackup_history to base an " + "incremental backup on. --incremental-history-name, " + "--incremental-basedir and --incremental-lsn. If no valid lsn can be " + "found (no success record with that uuid) xtrabackup will return " + "with an error. It is used with the --incremental option.", + (uchar*) &opt_incremental_history_uuid, + (uchar*) &opt_incremental_history_uuid, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"decrypt", OPT_DECRYPT, "Decrypts all files with the .xbcrypt " + "extension in a backup previously made with --encrypt option.", + &opt_decrypt_algo, &opt_decrypt_algo, + &xtrabackup_encrypt_algo_typelib, GET_ENUM, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + + {"remove-original", OPT_REMOVE_ORIGINAL, "Remove .qp and .xbcrypt files " + "after decryption and decompression.", + (uchar *) &opt_remove_original, + (uchar *) &opt_remove_original, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + + {"ftwrl-wait-query-type", OPT_LOCK_WAIT_QUERY_TYPE, + "This option specifies which types of queries are allowed to complete " + "before innobackupex will issue the global lock. Default is all.", + (uchar*) &opt_lock_wait_query_type, + (uchar*) &opt_lock_wait_query_type, &query_type_typelib, + GET_ENUM, REQUIRED_ARG, QUERY_TYPE_ALL, 0, 0, 0, 0, 0}, + + {"kill-long-query-type", OPT_KILL_LONG_QUERY_TYPE, + "This option specifies which types of queries should be killed to " + "unblock the global lock. Default is \"all\".", + (uchar*) &opt_kill_long_query_type, + (uchar*) &opt_kill_long_query_type, &query_type_typelib, + GET_ENUM, REQUIRED_ARG, QUERY_TYPE_SELECT, 0, 0, 0, 0, 0}, + + {"history", OPT_HISTORY, + "This option enables the tracking of backup history in the " + "PERCONA_SCHEMA.xtrabackup_history table. An optional history " + "series name may be specified that will be placed with the history " + "record for the current backup being taken.", + NULL, NULL, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, + + {"kill-long-queries-timeout", OPT_KILL_LONG_QUERIES_TIMEOUT, + "This option specifies the number of seconds innobackupex waits " + "between starting FLUSH TABLES WITH READ LOCK and killing those " + "queries that block it. Default is 0 seconds, which means " + "innobackupex will not attempt to kill any queries.", + (uchar*) &opt_kill_long_queries_timeout, + (uchar*) &opt_kill_long_queries_timeout, 0, GET_UINT, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"ftwrl-wait-timeout", OPT_LOCK_WAIT_TIMEOUT, + "This option specifies time in seconds that innobackupex should wait " + "for queries that would block FTWRL before running it. If there are " + "still such queries when the timeout expires, innobackupex terminates " + "with an error. Default is 0, in which case innobackupex does not " + "wait for queries to complete and starts FTWRL immediately.", + (uchar*) &opt_lock_wait_timeout, + (uchar*) &opt_lock_wait_timeout, 0, GET_UINT, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"ftwrl-wait-threshold", OPT_LOCK_WAIT_THRESHOLD, + "This option specifies the query run time threshold which is used by " + "innobackupex to detect long-running queries with a non-zero value " + "of --ftwrl-wait-timeout. FTWRL is not started until such " + "long-running queries exist. This option has no effect if " + "--ftwrl-wait-timeout is 0. Default value is 60 seconds.", + (uchar*) &opt_lock_wait_threshold, + (uchar*) &opt_lock_wait_threshold, 0, GET_UINT, + REQUIRED_ARG, 60, 0, 0, 0, 0, 0}, + + {"debug-sleep-before-unlock", OPT_DEBUG_SLEEP_BEFORE_UNLOCK, + "This is a debug-only option used by the XtraBackup test suite.", + (uchar*) &opt_debug_sleep_before_unlock, + (uchar*) &opt_debug_sleep_before_unlock, 0, GET_UINT, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"safe-slave-backup-timeout", OPT_SAFE_SLAVE_BACKUP_TIMEOUT, + "How many seconds --safe-slave-backup should wait for " + "Slave_open_temp_tables to become zero. (default 300)", + (uchar*) &opt_safe_slave_backup_timeout, + (uchar*) &opt_safe_slave_backup_timeout, 0, GET_UINT, + REQUIRED_ARG, 300, 0, 0, 0, 0, 0}, + + {"binlog-info", OPT_BINLOG_INFO, + "This option controls how XtraBackup should retrieve server's binary log " + "coordinates corresponding to the backup. Possible values are OFF, ON, " + "LOCKLESS and AUTO. See the XtraBackup manual for more information", + &opt_binlog_info, &opt_binlog_info, + &binlog_info_typelib, GET_ENUM, OPT_ARG, BINLOG_INFO_AUTO, 0, 0, 0, 0, 0}, + + {"secure-auth", OPT_XB_SECURE_AUTH, "Refuse client connecting to server if it" + " uses old (pre-4.1.1) protocol.", &opt_secure_auth, + &opt_secure_auth, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, + +#include "sslopt-longopts.h" + + + { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + +uint xb_client_options_count = array_elements(xb_client_options); + +struct my_option xb_server_options[] = +{ + {"datadir", 'h', "Path to the database root.", (G_PTR*) &mysql_data_home, + (G_PTR*) &mysql_data_home, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"tmpdir", 't', + "Path for temporary files. Several paths may be specified, separated by a " +#if defined(__WIN__) || defined(OS2) || defined(__NETWARE__) + "semicolon (;)" +#else + "colon (:)" +#endif + ", in this case they are used in a round-robin fashion.", + (G_PTR*) &opt_mysql_tmpdir, + (G_PTR*) &opt_mysql_tmpdir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"parallel", OPT_XTRA_PARALLEL, + "Number of threads to use for parallel datafiles transfer. " + "The default value is 1.", + (G_PTR*) &xtrabackup_parallel, (G_PTR*) &xtrabackup_parallel, 0, GET_INT, + REQUIRED_ARG, 1, 1, INT_MAX, 0, 0, 0}, + + {"log", OPT_LOG, "Ignored option for MySQL option compatibility", + (G_PTR*) &log_ignored_opt, (G_PTR*) &log_ignored_opt, 0, + GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, + + {"log_bin", OPT_LOG, "Base name for the log sequence", + &opt_log_bin, &opt_log_bin, 0, GET_STR_ALLOC, OPT_ARG, 0, 0, 0, 0, 0, 0}, + + {"innodb", OPT_INNODB, "Ignored option for MySQL option compatibility", + (G_PTR*) &innobase_ignored_opt, (G_PTR*) &innobase_ignored_opt, 0, + GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, + + {"innodb_adaptive_hash_index", OPT_INNODB_ADAPTIVE_HASH_INDEX, + "Enable InnoDB adaptive hash index (enabled by default). " + "Disable with --skip-innodb-adaptive-hash-index.", + (G_PTR*) &innobase_adaptive_hash_index, + (G_PTR*) &innobase_adaptive_hash_index, + 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, + {"innodb_additional_mem_pool_size", OPT_INNODB_ADDITIONAL_MEM_POOL_SIZE, + "Size of a memory pool InnoDB uses to store data dictionary information and other internal data structures.", + (G_PTR*) &innobase_additional_mem_pool_size, + (G_PTR*) &innobase_additional_mem_pool_size, 0, GET_LONG, REQUIRED_ARG, + 1*1024*1024L, 512*1024L, LONG_MAX, 0, 1024, 0}, + {"innodb_autoextend_increment", OPT_INNODB_AUTOEXTEND_INCREMENT, + "Data file autoextend increment in megabytes", + (G_PTR*) &srv_auto_extend_increment, + (G_PTR*) &srv_auto_extend_increment, + 0, GET_ULONG, REQUIRED_ARG, 8L, 1L, 1000L, 0, 1L, 0}, + {"innodb_buffer_pool_size", OPT_INNODB_BUFFER_POOL_SIZE, + "The size of the memory buffer InnoDB uses to cache data and indexes of its tables.", + (G_PTR*) &innobase_buffer_pool_size, (G_PTR*) &innobase_buffer_pool_size, 0, + GET_LL, REQUIRED_ARG, 8*1024*1024L, 1024*1024L, LONGLONG_MAX, 0, + 1024*1024L, 0}, + {"innodb_checksums", OPT_INNODB_CHECKSUMS, "Enable InnoDB checksums validation (enabled by default). \ +Disable with --skip-innodb-checksums.", (G_PTR*) &innobase_use_checksums, + (G_PTR*) &innobase_use_checksums, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, + {"innodb_data_file_path", OPT_INNODB_DATA_FILE_PATH, + "Path to individual files and their sizes.", &innobase_data_file_path, + &innobase_data_file_path, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"innodb_data_home_dir", OPT_INNODB_DATA_HOME_DIR, + "The common part for InnoDB table spaces.", &innobase_data_home_dir, + &innobase_data_home_dir, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"innodb_doublewrite", OPT_INNODB_DOUBLEWRITE, "Enable InnoDB doublewrite buffer (enabled by default). \ +Disable with --skip-innodb-doublewrite.", (G_PTR*) &innobase_use_doublewrite, + (G_PTR*) &innobase_use_doublewrite, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, + {"innodb_io_capacity", OPT_INNODB_IO_CAPACITY, + "Number of IOPs the server can do. Tunes the background IO rate", + (G_PTR*) &srv_io_capacity, (G_PTR*) &srv_io_capacity, + 0, GET_ULONG, OPT_ARG, 200, 100, ~0UL, 0, 0, 0}, + {"innodb_file_io_threads", OPT_INNODB_FILE_IO_THREADS, + "Number of file I/O threads in InnoDB.", (G_PTR*) &innobase_file_io_threads, + (G_PTR*) &innobase_file_io_threads, 0, GET_LONG, REQUIRED_ARG, 4, 4, 64, 0, + 1, 0}, + {"innodb_read_io_threads", OPT_INNODB_READ_IO_THREADS, + "Number of background read I/O threads in InnoDB.", (G_PTR*) &innobase_read_io_threads, + (G_PTR*) &innobase_read_io_threads, 0, GET_LONG, REQUIRED_ARG, 4, 1, 64, 0, + 1, 0}, + {"innodb_write_io_threads", OPT_INNODB_WRITE_IO_THREADS, + "Number of background write I/O threads in InnoDB.", (G_PTR*) &innobase_write_io_threads, + (G_PTR*) &innobase_write_io_threads, 0, GET_LONG, REQUIRED_ARG, 4, 1, 64, 0, + 1, 0}, + {"innodb_file_per_table", OPT_INNODB_FILE_PER_TABLE, + "Stores each InnoDB table to an .ibd file in the database dir.", + (G_PTR*) &innobase_file_per_table, + (G_PTR*) &innobase_file_per_table, 0, GET_BOOL, NO_ARG, + FALSE, 0, 0, 0, 0, 0}, + + {"innodb_flush_method", OPT_INNODB_FLUSH_METHOD, + "With which method to flush data.", (G_PTR*) &innobase_unix_file_flush_method, + (G_PTR*) &innobase_unix_file_flush_method, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, + 0, 0, 0}, + +/* ####### Should we use this option? ####### */ + {"innodb_force_recovery", OPT_INNODB_FORCE_RECOVERY, + "Helps to save your data in case the disk image of the database becomes corrupt.", + (G_PTR*) &innobase_force_recovery, (G_PTR*) &innobase_force_recovery, 0, + GET_LONG, REQUIRED_ARG, 0, 0, 6, 0, 1, 0}, + + {"innodb_log_arch_dir", OPT_INNODB_LOG_ARCH_DIR, + "Where full logs should be archived.", (G_PTR*) &innobase_log_arch_dir, + (G_PTR*) &innobase_log_arch_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"innodb_log_buffer_size", OPT_INNODB_LOG_BUFFER_SIZE, + "The size of the buffer which InnoDB uses to write log to the log files on disk.", + (G_PTR*) &innobase_log_buffer_size, (G_PTR*) &innobase_log_buffer_size, 0, + GET_LONG, REQUIRED_ARG, 1024*1024L, 256*1024L, LONG_MAX, 0, 1024, 0}, + {"innodb_log_file_size", OPT_INNODB_LOG_FILE_SIZE, + "Size of each log file in a log group.", + (G_PTR*) &innobase_log_file_size, (G_PTR*) &innobase_log_file_size, 0, + GET_LL, REQUIRED_ARG, 48*1024*1024L, 1*1024*1024L, LONGLONG_MAX, 0, + 1024*1024L, 0}, + {"innodb_log_files_in_group", OPT_INNODB_LOG_FILES_IN_GROUP, + "Number of log files in the log group. InnoDB writes to the files in a " + "circular fashion. Value 3 is recommended here.", + &innobase_log_files_in_group, &innobase_log_files_in_group, + 0, GET_LONG, REQUIRED_ARG, 2, 2, 100, 0, 1, 0}, + {"innodb_log_group_home_dir", OPT_INNODB_LOG_GROUP_HOME_DIR, + "Path to InnoDB log files.", &srv_log_group_home_dir, + &srv_log_group_home_dir, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"innodb_max_dirty_pages_pct", OPT_INNODB_MAX_DIRTY_PAGES_PCT, + "Percentage of dirty pages allowed in bufferpool.", (G_PTR*) &srv_max_buf_pool_modified_pct, + (G_PTR*) &srv_max_buf_pool_modified_pct, 0, GET_ULONG, REQUIRED_ARG, 90, 0, 100, 0, 0, 0}, + {"innodb_open_files", OPT_INNODB_OPEN_FILES, + "How many files at the maximum InnoDB keeps open at the same time.", + (G_PTR*) &innobase_open_files, (G_PTR*) &innobase_open_files, 0, + GET_LONG, REQUIRED_ARG, 300L, 10L, LONG_MAX, 0, 1L, 0}, + {"innodb_use_native_aio", OPT_INNODB_USE_NATIVE_AIO, + "Use native AIO if supported on this platform.", + (G_PTR*) &srv_use_native_aio, + (G_PTR*) &srv_use_native_aio, 0, GET_BOOL, NO_ARG, + FALSE, 0, 0, 0, 0, 0}, + {"innodb_page_size", OPT_INNODB_PAGE_SIZE, + "The universal page size of the database.", + (G_PTR*) &innobase_page_size, (G_PTR*) &innobase_page_size, 0, + /* Use GET_LL to support numeric suffixes in 5.6 */ + GET_LL, REQUIRED_ARG, + (1LL << 14), (1LL << 12), (1LL << UNIV_PAGE_SIZE_SHIFT_MAX), 0, 1L, 0}, + {"innodb_log_block_size", OPT_INNODB_LOG_BLOCK_SIZE, + "The log block size of the transaction log file. " + "Changing for created log file is not supported. Use on your own risk!", + (G_PTR*) &innobase_log_block_size, (G_PTR*) &innobase_log_block_size, 0, + GET_ULONG, REQUIRED_ARG, 512, 512, 1 << UNIV_PAGE_SIZE_SHIFT_MAX, 0, 1L, 0}, + {"innodb_fast_checksum", OPT_INNODB_FAST_CHECKSUM, + "Change the algorithm of checksum for the whole of datapage to 4-bytes word based.", + (G_PTR*) &innobase_fast_checksum, + (G_PTR*) &innobase_fast_checksum, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"innodb_doublewrite_file", OPT_INNODB_DOUBLEWRITE_FILE, + "Path to special datafile for doublewrite buffer. (default is "": not used)", + (G_PTR*) &innobase_doublewrite_file, (G_PTR*) &innobase_doublewrite_file, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"innodb_buffer_pool_filename", OPT_INNODB_BUFFER_POOL_FILENAME, + "Filename to/from which to dump/load the InnoDB buffer pool", + (G_PTR*) &innobase_buffer_pool_filename, + (G_PTR*) &innobase_buffer_pool_filename, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + +#ifndef __WIN__ + {"debug-sync", OPT_XTRA_DEBUG_SYNC, + "Debug sync point. This is only used by the xtrabackup test suite", + (G_PTR*) &xtrabackup_debug_sync, + (G_PTR*) &xtrabackup_debug_sync, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#endif + + {"innodb_checksum_algorithm", OPT_INNODB_CHECKSUM_ALGORITHM, + "The algorithm InnoDB uses for page checksumming. [CRC32, STRICT_CRC32, " + "INNODB, STRICT_INNODB, NONE, STRICT_NONE]", &srv_checksum_algorithm, + &srv_checksum_algorithm, &innodb_checksum_algorithm_typelib, GET_ENUM, + REQUIRED_ARG, SRV_CHECKSUM_ALGORITHM_INNODB, 0, 0, 0, 0, 0}, + {"innodb_log_checksum_algorithm", OPT_INNODB_LOG_CHECKSUM_ALGORITHM, + "The algorithm InnoDB uses for log checksumming. [CRC32, STRICT_CRC32, " + "INNODB, STRICT_INNODB, NONE, STRICT_NONE]", &srv_log_checksum_algorithm, + &srv_log_checksum_algorithm, &innodb_checksum_algorithm_typelib, GET_ENUM, + REQUIRED_ARG, SRV_CHECKSUM_ALGORITHM_INNODB, 0, 0, 0, 0, 0}, + {"innodb_undo_directory", OPT_INNODB_UNDO_DIRECTORY, + "Directory where undo tablespace files live, this path can be absolute.", + &srv_undo_dir, &srv_undo_dir, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, + 0}, + + {"innodb_undo_tablespaces", OPT_INNODB_UNDO_TABLESPACES, + "Number of undo tablespaces to use.", + (G_PTR*)&srv_undo_tablespaces, (G_PTR*)&srv_undo_tablespaces, + 0, GET_ULONG, REQUIRED_ARG, 0, 0, 126, 0, 1, 0}, + + {"defaults_group", OPT_DEFAULTS_GROUP, "defaults group in config file (default \"mysqld\").", + (G_PTR*) &defaults_group, (G_PTR*) &defaults_group, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + + {"plugin-dir", OPT_PLUGIN_DIR, "Server plugin directory", + &xb_plugin_dir, &xb_plugin_dir, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, + + { "plugin-load", OPT_PLUGIN_LOAD, "encrypton plugin to load", + &xb_plugin_load, &xb_plugin_load, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, + + { "innodb-encrypt-log", OPT_INNODB_ENCRYPT_LOG, "encrypton plugin to load", + &srv_encrypt_log, &srv_encrypt_log, + 0, GET_BOOL, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, + + {"open_files_limit", OPT_OPEN_FILES_LIMIT, "the maximum number of file " + "descriptors to reserve with setrlimit().", + (G_PTR*) &xb_open_files_limit, (G_PTR*) &xb_open_files_limit, 0, GET_ULONG, + REQUIRED_ARG, 0, 0, UINT_MAX, 0, 1, 0}, + + { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + +uint xb_server_options_count = array_elements(xb_server_options); + +#ifndef __WIN__ +static int debug_sync_resumed; + +static void sigcont_handler(int sig); + +static void sigcont_handler(int sig __attribute__((unused))) +{ + debug_sync_resumed= 1; +} +#endif + +static inline +void +debug_sync_point(const char *name) +{ +#ifndef __WIN__ + FILE *fp; + pid_t pid; + char pid_path[FN_REFLEN]; + + if (xtrabackup_debug_sync == NULL) { + return; + } + + if (strcmp(xtrabackup_debug_sync, name)) { + return; + } + + pid = getpid(); + + snprintf(pid_path, sizeof(pid_path), "%s/xtrabackup_debug_sync", + xtrabackup_target_dir); + fp = fopen(pid_path, "w"); + if (fp == NULL) { + msg("xtrabackup: Error: cannot open %s\n", pid_path); + exit(EXIT_FAILURE); + } + fprintf(fp, "%u\n", (uint) pid); + fclose(fp); + + msg("xtrabackup: DEBUG: Suspending at debug sync point '%s'. " + "Resume with 'kill -SIGCONT %u'.\n", name, (uint) pid); + + debug_sync_resumed= 0; + kill(pid, SIGSTOP); + while (!debug_sync_resumed) { + sleep(1); + } + + /* On resume */ + msg("xtrabackup: DEBUG: removing the pid file.\n"); + my_delete(pid_path, MYF(MY_WME)); +#endif +} + +static const char *xb_client_default_groups[]= + { "xtrabackup", "client", 0, 0, 0 }; + +static const char *xb_server_default_groups[]= + { "xtrabackup", "mysqld", 0, 0, 0 }; + +static void print_version(void) +{ + msg("%s based on MariaDB server %s %s (%s) \n", + my_progname, MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE); +} + +static void usage(void) +{ + puts("Open source backup tool for InnoDB and XtraDB\n\ +\n\ +Copyright (C) 2009-2015 Percona LLC and/or its affiliates.\n\ +Portions Copyright (C) 2000, 2011, MySQL AB & Innobase Oy. All Rights Reserved.\n\ +\n\ +This program is free software; you can redistribute it and/or\n\ +modify it under the terms of the GNU General Public License\n\ +as published by the Free Software Foundation version 2\n\ +of the License.\n\ +\n\ +This program is distributed in the hope that it will be useful,\n\ +but WITHOUT ANY WARRANTY; without even the implied warranty of\n\ +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\ +GNU General Public License for more details.\n\ +\n\ +You can download full text of the license on http://www.gnu.org/licenses/gpl-2.0.txt\n"); + + printf("Usage: [%s [--defaults-file=#] --backup | %s [--defaults-file=#] --prepare] [OPTIONS]\n",my_progname,my_progname); + print_defaults("my", xb_server_default_groups); + my_print_help(xb_client_options); + my_print_help(xb_server_options); + my_print_variables(xb_server_options); + my_print_variables(xb_client_options); +} + +#define ADD_PRINT_PARAM_OPT(value) \ + { \ + print_param_str << opt->name << "=" << value << "\n"; \ + param_set.insert(opt->name); \ + } + +/************************************************************************ +Check if parameter is set in defaults file or via command line argument +@return true if parameter is set. */ +bool +check_if_param_set(const char *param) +{ + return param_set.find(param) != param_set.end(); +} + +my_bool +xb_get_one_option(int optid, + const struct my_option *opt __attribute__((unused)), + char *argument) +{ + switch(optid) { + case 'h': + strmake(mysql_real_data_home,argument, FN_REFLEN - 1); + mysql_data_home= mysql_real_data_home; + + ADD_PRINT_PARAM_OPT(mysql_real_data_home); + break; + + case 't': + + ADD_PRINT_PARAM_OPT(opt_mysql_tmpdir); + break; + + case OPT_INNODB_DATA_HOME_DIR: + + ADD_PRINT_PARAM_OPT(innobase_data_home_dir); + break; + + case OPT_INNODB_DATA_FILE_PATH: + + ADD_PRINT_PARAM_OPT(innobase_data_file_path); + break; + + case OPT_INNODB_LOG_GROUP_HOME_DIR: + + ADD_PRINT_PARAM_OPT(srv_log_group_home_dir); + break; + + case OPT_INNODB_LOG_FILES_IN_GROUP: + + ADD_PRINT_PARAM_OPT(innobase_log_files_in_group); + break; + + case OPT_INNODB_LOG_FILE_SIZE: + + ADD_PRINT_PARAM_OPT(innobase_log_file_size); + break; + + case OPT_INNODB_FLUSH_METHOD: + + ADD_PRINT_PARAM_OPT(innobase_unix_file_flush_method); + break; + + case OPT_INNODB_PAGE_SIZE: + + ADD_PRINT_PARAM_OPT(innobase_page_size); + break; + + case OPT_INNODB_FAST_CHECKSUM: + + ADD_PRINT_PARAM_OPT(!!innobase_fast_checksum); + break; + + case OPT_INNODB_LOG_BLOCK_SIZE: + + ADD_PRINT_PARAM_OPT(innobase_log_block_size); + break; + + case OPT_INNODB_DOUBLEWRITE_FILE: + + ADD_PRINT_PARAM_OPT(innobase_doublewrite_file); + break; + + case OPT_INNODB_UNDO_DIRECTORY: + + ADD_PRINT_PARAM_OPT(srv_undo_dir); + break; + + case OPT_INNODB_UNDO_TABLESPACES: + + ADD_PRINT_PARAM_OPT(srv_undo_tablespaces); + break; + + case OPT_INNODB_CHECKSUM_ALGORITHM: + + ut_a(srv_checksum_algorithm <= SRV_CHECKSUM_ALGORITHM_STRICT_NONE); + + ADD_PRINT_PARAM_OPT(innodb_checksum_algorithm_names[srv_checksum_algorithm]); + break; + + case OPT_INNODB_LOG_CHECKSUM_ALGORITHM: + + ut_a(srv_log_checksum_algorithm <= SRV_CHECKSUM_ALGORITHM_STRICT_NONE); + + ADD_PRINT_PARAM_OPT(innodb_checksum_algorithm_names[srv_log_checksum_algorithm]); + break; + + case OPT_INNODB_BUFFER_POOL_FILENAME: + + ADD_PRINT_PARAM_OPT(innobase_buffer_pool_filename); + break; + + case OPT_XTRA_TARGET_DIR: + strmake(xtrabackup_real_target_dir,argument, sizeof(xtrabackup_real_target_dir)-1); + xtrabackup_target_dir= xtrabackup_real_target_dir; + break; + case OPT_XTRA_STREAM: + if (!strcasecmp(argument, "tar")) + xtrabackup_stream_fmt = XB_STREAM_FMT_TAR; + else if (!strcasecmp(argument, "xbstream")) + xtrabackup_stream_fmt = XB_STREAM_FMT_XBSTREAM; + else + { + msg("Invalid --stream argument: %s\n", argument); + return 1; + } + xtrabackup_stream = TRUE; + break; + case OPT_XTRA_COMPRESS: + if (argument == NULL) + xtrabackup_compress_alg = "quicklz"; + else if (strcasecmp(argument, "quicklz")) + { + msg("Invalid --compress argument: %s\n", argument); + return 1; + } + xtrabackup_compress = TRUE; + break; + case OPT_XTRA_ENCRYPT: + if (argument == NULL) + { + msg("Missing --encrypt argument, must specify a valid encryption " + " algorithm.\n"); + return 1; + } + xtrabackup_encrypt = TRUE; + break; + case OPT_DECRYPT: + if (argument == NULL) { + msg("Missing --decrypt argument, must specify a " + "valid encryption algorithm.\n"); + return(1); + } + opt_decrypt = TRUE; + xtrabackup_decrypt_decompress = true; + break; + case OPT_DECOMPRESS: + opt_decompress = TRUE; + xtrabackup_decrypt_decompress = true; + break; + case (int) OPT_CORE_FILE: + test_flags |= TEST_CORE_ON_SIGNAL; + break; + case OPT_HISTORY: + if (argument) { + opt_history = argument; + } else { + opt_history = ""; + } + break; + case 'p': + if (argument) + { + char *start= argument; + my_free(opt_password); + opt_password= my_strdup(argument, MYF(MY_FAE)); + while (*argument) *argument++= 'x'; // Destroy argument + if (*start) + start[1]=0 ; + } + break; + + +#include "sslopt-case.h" + + case '?': + usage(); + exit(EXIT_SUCCESS); + break; + case 'v': + print_version(); + exit(EXIT_SUCCESS); + break; + default: + break; + } + return 0; +} + +/*********************************************************************** +Initializes log_block_size */ +static +ibool +xb_init_log_block_size(void) +{ + srv_log_block_size = 0; + if (innobase_log_block_size != 512) { + uint n_shift = (uint)get_bit_shift(innobase_log_block_size);; + + if (n_shift > 0) { + srv_log_block_size = (ulint)(1LL << n_shift); + msg("InnoDB: The log block size is set to %lu.\n", + srv_log_block_size); + } + } else { + srv_log_block_size = 512; + } + if (!srv_log_block_size) { + msg("InnoDB: Error: %lu is not valid value for " + "innodb_log_block_size.\n", innobase_log_block_size); + return FALSE; + } + + return TRUE; +} + +static my_bool +innodb_init_param(void) +{ + /* innobase_init */ + static char current_dir[3]; /* Set if using current lib */ + my_bool ret; + char *default_path; + srv_is_being_started = TRUE; + /* === some variables from mysqld === */ + memset((G_PTR) &mysql_tmpdir_list, 0, sizeof(mysql_tmpdir_list)); + + if (init_tmpdir(&mysql_tmpdir_list, opt_mysql_tmpdir)) + exit(EXIT_FAILURE); + xtrabackup_tmpdir = my_tmpdir(&mysql_tmpdir_list); + /* dummy for initialize all_charsets[] */ + get_charset_name(0); + + srv_page_size = 0; + srv_page_size_shift = 0; + + if (innobase_page_size != (1LL << 14)) { + int n_shift = (int)get_bit_shift((ulint) innobase_page_size); + + if (n_shift >= 12 && n_shift <= UNIV_PAGE_SIZE_SHIFT_MAX) { + srv_page_size_shift = n_shift; + srv_page_size = 1 << n_shift; + msg("InnoDB: The universal page size of the " + "database is set to %lu.\n", srv_page_size); + } else { + msg("InnoDB: Error: invalid value of " + "innobase_page_size: %lld", innobase_page_size); + exit(EXIT_FAILURE); + } + } else { + srv_page_size_shift = 14; + srv_page_size = (1 << srv_page_size_shift); + } + + if (!xb_init_log_block_size()) { + goto error; + } + + /* Check that values don't overflow on 32-bit systems. */ + if (sizeof(ulint) == 4) { + if (xtrabackup_use_memory > UINT_MAX32) { + msg("xtrabackup: use-memory can't be over 4GB" + " on 32-bit systems\n"); + } + + if (innobase_buffer_pool_size > UINT_MAX32) { + msg("xtrabackup: innobase_buffer_pool_size can't be " + "over 4GB on 32-bit systems\n"); + + goto error; + } + + if (innobase_log_file_size > UINT_MAX32) { + msg("xtrabackup: innobase_log_file_size can't be " + "over 4GB on 32-bit systemsi\n"); + + goto error; + } + } + + os_innodb_umask = (ulint)0664; + + /* First calculate the default path for innodb_data_home_dir etc., + in case the user has not given any value. + + Note that when using the embedded server, the datadirectory is not + necessarily the current directory of this program. */ + + /* It's better to use current lib, to keep paths short */ + current_dir[0] = FN_CURLIB; + current_dir[1] = FN_LIBCHAR; + current_dir[2] = 0; + default_path = current_dir; + + ut_a(default_path); + + /* Set InnoDB initialization parameters according to the values + read from MySQL .cnf file */ + + if (xtrabackup_backup || xtrabackup_stats) { + msg("xtrabackup: using the following InnoDB configuration:\n"); + } else { + msg("xtrabackup: using the following InnoDB configuration " + "for recovery:\n"); + } + + /*--------------- Data files -------------------------*/ + + /* The default dir for data files is the datadir of MySQL */ + + srv_data_home = ((xtrabackup_backup || xtrabackup_stats) && innobase_data_home_dir + ? innobase_data_home_dir : default_path); + msg("xtrabackup: innodb_data_home_dir = %s\n", srv_data_home); + + /* Set default InnoDB data file size to 10 MB and let it be + auto-extending. Thus users can use InnoDB in >= 4.0 without having + to specify any startup options. */ + + if (!innobase_data_file_path) { + innobase_data_file_path = (char*) "ibdata1:10M:autoextend"; + } + msg("xtrabackup: innodb_data_file_path = %s\n", + innobase_data_file_path); + + /* Since InnoDB edits the argument in the next call, we make another + copy of it: */ + + internal_innobase_data_file_path = strdup(innobase_data_file_path); + + ret = (my_bool) srv_parse_data_file_paths_and_sizes( + internal_innobase_data_file_path); + if (ret == FALSE) { + msg("xtrabackup: syntax error in innodb_data_file_path\n"); +mem_free_and_error: + free(internal_innobase_data_file_path); + internal_innobase_data_file_path = NULL; + goto error; + } + + if (xtrabackup_prepare) { + /* "--prepare" needs filenames only */ + ulint i; + + for (i=0; i < srv_n_data_files; i++) { + char *p; + + p = srv_data_file_names[i]; + while ((p = strchr(p, SRV_PATH_SEPARATOR)) != NULL) + { + p++; + srv_data_file_names[i] = p; + } + } + } + + /* -------------- Log files ---------------------------*/ + + /* The default dir for log files is the datadir of MySQL */ + + if (!((xtrabackup_backup || xtrabackup_stats) && + srv_log_group_home_dir)) { + srv_log_group_home_dir = default_path; + } + if (xtrabackup_prepare && xtrabackup_incremental_dir) { + srv_log_group_home_dir = xtrabackup_incremental_dir; + } + msg("xtrabackup: innodb_log_group_home_dir = %s\n", + srv_log_group_home_dir); + + srv_normalize_path_for_win(srv_log_group_home_dir); + + if (strchr(srv_log_group_home_dir, ';')) { + + msg("syntax error in innodb_log_group_home_dir, "); + + goto mem_free_and_error; + } + + srv_adaptive_flushing = FALSE; + srv_use_sys_malloc = TRUE; + srv_file_format = 1; /* Barracuda */ + srv_max_file_format_at_startup = UNIV_FORMAT_MIN; /* on */ + /* --------------------------------------------------*/ + + srv_file_flush_method_str = innobase_unix_file_flush_method; + + srv_n_log_files = (ulint) innobase_log_files_in_group; + srv_log_file_size = (ulint) innobase_log_file_size; + msg("xtrabackup: innodb_log_files_in_group = %ld\n", + srv_n_log_files); + msg("xtrabackup: innodb_log_file_size = %lld\n", + (long long int) srv_log_file_size); + + srv_log_archive_on = (ulint) innobase_log_archive; + srv_log_buffer_size = (ulint) innobase_log_buffer_size; + + /* We set srv_pool_size here in units of 1 kB. InnoDB internally + changes the value so that it becomes the number of database pages. */ + + //srv_buf_pool_size = (ulint) innobase_buffer_pool_size; + srv_buf_pool_size = (ulint) xtrabackup_use_memory; + + srv_mem_pool_size = (ulint) innobase_additional_mem_pool_size; + + srv_n_file_io_threads = (ulint) innobase_file_io_threads; + srv_n_read_io_threads = (ulint) innobase_read_io_threads; + srv_n_write_io_threads = (ulint) innobase_write_io_threads; + + srv_force_recovery = (ulint) innobase_force_recovery; + + srv_use_doublewrite_buf = (ibool) innobase_use_doublewrite; + + if (!innobase_use_checksums) { + + srv_checksum_algorithm = SRV_CHECKSUM_ALGORITHM_NONE; + } + + btr_search_enabled = (char) innobase_adaptive_hash_index; + btr_search_index_num = 1; + + os_use_large_pages = (ibool) innobase_use_large_pages; + os_large_page_size = (ulint) innobase_large_page_size; + + if (!innobase_log_arch_dir) { + static char default_dir[3] = "./"; + srv_arch_dir = default_dir; + } + row_rollback_on_timeout = (ibool) innobase_rollback_on_timeout; + + srv_file_per_table = (my_bool) innobase_file_per_table; + + srv_locks_unsafe_for_binlog = (ibool) innobase_locks_unsafe_for_binlog; + + srv_max_n_open_files = (ulint) innobase_open_files; + srv_innodb_status = (ibool) innobase_create_status_file; + + srv_print_verbose_log = 1; + + /* Store the default charset-collation number of this MySQL + installation */ + + /* We cannot treat characterset here for now!! */ + data_mysql_default_charset_coll = (ulint)default_charset_info->number; + + ut_a(DATA_MYSQL_LATIN1_SWEDISH_CHARSET_COLL == + my_charset_latin1.number); + ut_a(DATA_MYSQL_BINARY_CHARSET_COLL == my_charset_bin.number); + + /* Store the latin1_swedish_ci character ordering table to InnoDB. For + non-latin1_swedish_ci charsets we use the MySQL comparison functions, + and consequently we do not need to know the ordering internally in + InnoDB. */ + + ut_a(0 == strcmp(my_charset_latin1.name, "latin1_swedish_ci")); + srv_latin1_ordering = my_charset_latin1.sort_order; + + //innobase_commit_concurrency_init_default(); + + /* Since we in this module access directly the fields of a trx + struct, and due to different headers and flags it might happen that + mutex_t has a different size in this module and in InnoDB + modules, we check at run time that the size is the same in + these compilation modules. */ + + /* On 5.5+ srv_use_native_aio is TRUE by default. It is later reset + if it is not supported by the platform in + innobase_start_or_create_for_mysql(). As we don't call it in xtrabackup, + we have to duplicate checks from that function here. */ + +#ifdef __WIN__ + switch (os_get_os_version()) { + case OS_WIN95: + case OS_WIN31: + case OS_WINNT: + /* On Win 95, 98, ME, Win32 subsystem for Windows 3.1, + and NT use simulated aio. In NT Windows provides async i/o, + but when run in conjunction with InnoDB Hot Backup, it seemed + to corrupt the data files. */ + + srv_use_native_aio = FALSE; + break; + + case OS_WIN2000: + case OS_WINXP: + /* On 2000 and XP, async IO is available. */ + srv_use_native_aio = TRUE; + break; + + default: + /* Vista and later have both async IO and condition variables */ + srv_use_native_aio = TRUE; + srv_use_native_conditions = TRUE; + break; + } + +#elif defined(LINUX_NATIVE_AIO) + + if (srv_use_native_aio) { + ut_print_timestamp(stderr); + msg(" InnoDB: Using Linux native AIO\n"); + } +#else + /* Currently native AIO is supported only on windows and linux + and that also when the support is compiled in. In all other + cases, we ignore the setting of innodb_use_native_aio. */ + srv_use_native_aio = FALSE; + +#endif + + /* Assign the default value to srv_undo_dir if it's not specified, as + my_getopt does not support default values for string options. We also + ignore the option and override innodb_undo_directory on --prepare, + because separate undo tablespaces are copied to the root backup + directory. */ + + if (!srv_undo_dir || !xtrabackup_backup) { + my_free(srv_undo_dir); + srv_undo_dir = my_strdup(".", MYF(MY_FAE)); + } + + innodb_log_checksum_func_update(srv_log_checksum_algorithm); + + return(FALSE); + +error: + msg("xtrabackup: innodb_init_param(): Error occured.\n"); + return(TRUE); +} + +static my_bool +innodb_init(void) +{ + int err; + srv_is_being_started = TRUE; + err = innobase_start_or_create_for_mysql(); + + if (err != DB_SUCCESS) { + free(internal_innobase_data_file_path); + internal_innobase_data_file_path = NULL; + goto error; + } + + /* They may not be needed for now */ +// (void) hash_init(&innobase_open_tables,system_charset_info, 32, 0, 0, +// (hash_get_key) innobase_get_key, 0, 0); +// pthread_mutex_init(&innobase_share_mutex, MY_MUTEX_INIT_FAST); +// pthread_mutex_init(&prepare_commit_mutex, MY_MUTEX_INIT_FAST); +// pthread_mutex_init(&commit_threads_m, MY_MUTEX_INIT_FAST); +// pthread_mutex_init(&commit_cond_m, MY_MUTEX_INIT_FAST); +// pthread_cond_init(&commit_cond, NULL); + + innodb_inited= 1; + + return(FALSE); + +error: + msg("xtrabackup: innodb_init(): Error occured.\n"); + return(TRUE); +} + +static my_bool +innodb_end(void) +{ + srv_fast_shutdown = (ulint) innobase_fast_shutdown; + innodb_inited = 0; + + msg("xtrabackup: starting shutdown with innodb_fast_shutdown = %lu\n", + srv_fast_shutdown); + + if (innobase_shutdown_for_mysql() != DB_SUCCESS) { + goto error; + } + free(internal_innobase_data_file_path); + internal_innobase_data_file_path = NULL; + + /* They may not be needed for now */ +// hash_free(&innobase_open_tables); +// pthread_mutex_destroy(&innobase_share_mutex); +// pthread_mutex_destroy(&prepare_commit_mutex); +// pthread_mutex_destroy(&commit_threads_m); +// pthread_mutex_destroy(&commit_cond_m); +// pthread_cond_destroy(&commit_cond); + + return(FALSE); + +error: + msg("xtrabackup: innodb_end(): Error occured.\n"); + return(TRUE); +} + +/* ================= common ================= */ + +/*********************************************************************** +Read backup meta info. +@return TRUE on success, FALSE on failure. */ +static +my_bool +xtrabackup_read_metadata(char *filename) +{ + FILE *fp; + my_bool r = TRUE; + int t; + + fp = fopen(filename,"r"); + if(!fp) { + msg("xtrabackup: Error: cannot open %s\n", filename); + return(FALSE); + } + + if (fscanf(fp, "backup_type = %29s\n", metadata_type) + != 1) { + r = FALSE; + goto end; + } + /* Use UINT64PF instead of LSN_PF here, as we have to maintain the file + format. */ + if (fscanf(fp, "from_lsn = " UINT64PF "\n", &metadata_from_lsn) + != 1) { + r = FALSE; + goto end; + } + if (fscanf(fp, "to_lsn = " UINT64PF "\n", &metadata_to_lsn) + != 1) { + r = FALSE; + goto end; + } + if (fscanf(fp, "last_lsn = " UINT64PF "\n", &metadata_last_lsn) + != 1) { + metadata_last_lsn = 0; + } + /* Optional fields */ + + if (fscanf(fp, "recover_binlog_info = %d\n", &t) == 1) { + recover_binlog_info = (t == 1); + } +end: + fclose(fp); + + return(r); +} + +/*********************************************************************** +Print backup meta info to a specified buffer. */ +static +void +xtrabackup_print_metadata(char *buf, size_t buf_len) +{ + /* Use UINT64PF instead of LSN_PF here, as we have to maintain the file + format. */ + snprintf(buf, buf_len, + "backup_type = %s\n" + "from_lsn = " UINT64PF "\n" + "to_lsn = " UINT64PF "\n" + "last_lsn = " UINT64PF "\n" + "compact = %d\n" + "recover_binlog_info = %d\n", + metadata_type, + metadata_from_lsn, + metadata_to_lsn, + metadata_last_lsn, + MY_TEST(false), + MY_TEST(opt_binlog_info == BINLOG_INFO_LOCKLESS)); +} + +/*********************************************************************** +Stream backup meta info to a specified datasink. +@return TRUE on success, FALSE on failure. */ +static +my_bool +xtrabackup_stream_metadata(ds_ctxt_t *ds_ctxt) +{ + char buf[1024]; + size_t len; + ds_file_t *stream; + MY_STAT mystat; + my_bool rc = TRUE; + + xtrabackup_print_metadata(buf, sizeof(buf)); + + len = strlen(buf); + + mystat.st_size = len; + mystat.st_mtime = my_time(0); + + stream = ds_open(ds_ctxt, XTRABACKUP_METADATA_FILENAME, &mystat); + if (stream == NULL) { + msg("xtrabackup: Error: cannot open output stream " + "for %s\n", XTRABACKUP_METADATA_FILENAME); + return(FALSE); + } + + if (ds_write(stream, buf, len)) { + rc = FALSE; + } + + if (ds_close(stream)) { + rc = FALSE; + } + + return(rc); +} + +/*********************************************************************** +Write backup meta info to a specified file. +@return TRUE on success, FALSE on failure. */ +static +my_bool +xtrabackup_write_metadata(const char *filepath) +{ + char buf[1024]; + size_t len; + FILE *fp; + + xtrabackup_print_metadata(buf, sizeof(buf)); + + len = strlen(buf); + + fp = fopen(filepath, "w"); + if(!fp) { + msg("xtrabackup: Error: cannot open %s\n", filepath); + return(FALSE); + } + if (fwrite(buf, len, 1, fp) < 1) { + fclose(fp); + return(FALSE); + } + + fclose(fp); + + return(TRUE); +} + +/*********************************************************************** +Read meta info for an incremental delta. +@return TRUE on success, FALSE on failure. */ +static my_bool +xb_read_delta_metadata(const char *filepath, xb_delta_info_t *info) +{ + FILE* fp; + char key[51]; + char value[51]; + my_bool r = TRUE; + + /* set defaults */ + info->page_size = ULINT_UNDEFINED; + info->zip_size = ULINT_UNDEFINED; + info->space_id = ULINT_UNDEFINED; + + fp = fopen(filepath, "r"); + if (!fp) { + /* Meta files for incremental deltas are optional */ + return(TRUE); + } + + while (!feof(fp)) { + if (fscanf(fp, "%50s = %50s\n", key, value) == 2) { + if (strcmp(key, "page_size") == 0) { + info->page_size = strtoul(value, NULL, 10); + } else if (strcmp(key, "zip_size") == 0) { + info->zip_size = strtoul(value, NULL, 10); + } else if (strcmp(key, "space_id") == 0) { + info->space_id = strtoul(value, NULL, 10); + } + } + } + + fclose(fp); + + if (info->page_size == ULINT_UNDEFINED) { + msg("xtrabackup: page_size is required in %s\n", filepath); + r = FALSE; + } + if (info->space_id == ULINT_UNDEFINED) { + msg("xtrabackup: Warning: This backup was taken with XtraBackup 2.0.1 " + "or earlier, some DDL operations between full and incremental " + "backups may be handled incorrectly\n"); + } + + return(r); +} + +/*********************************************************************** +Write meta info for an incremental delta. +@return TRUE on success, FALSE on failure. */ +my_bool +xb_write_delta_metadata(const char *filename, const xb_delta_info_t *info) +{ + ds_file_t *f; + char buf[64]; + my_bool ret; + size_t len; + MY_STAT mystat; + + snprintf(buf, sizeof(buf), + "page_size = %lu\n" + "zip_size = %lu\n" + "space_id = %lu\n", + info->page_size, info->zip_size, info->space_id); + len = strlen(buf); + + mystat.st_size = len; + mystat.st_mtime = my_time(0); + + f = ds_open(ds_meta, filename, &mystat); + if (f == NULL) { + msg("xtrabackup: Error: cannot open output stream for %s\n", + filename); + return(FALSE); + } + + ret = (ds_write(f, buf, len) == 0); + + if (ds_close(f)) { + ret = FALSE; + } + + return(ret); +} + +/* ================= backup ================= */ +void +xtrabackup_io_throttling(void) +{ + if (xtrabackup_backup && xtrabackup_throttle && (io_ticket--) < 0) { + os_event_reset(wait_throttle); + os_event_wait(wait_throttle); + } +} + +static +my_bool regex_list_check_match( + const regex_list_t& list, + const char* name) +{ + regmatch_t tables_regmatch[1]; + for (regex_list_t::const_iterator i = list.begin(), end = list.end(); + i != end; ++i) { + const regex_t& regex = *i; + int regres = regexec(®ex, name, 1, tables_regmatch, 0); + + if (regres != REG_NOMATCH) { + return(TRUE); + } + } + return(FALSE); +} + +static +my_bool +find_filter_in_hashtable( + const char* name, + hash_table_t* table, + xb_filter_entry_t** result +) +{ + xb_filter_entry_t* found = NULL; + HASH_SEARCH(name_hash, table, ut_fold_string(name), + xb_filter_entry_t*, + found, (void) 0, + !strcmp(found->name, name)); + + if (found && result) { + *result = found; + } + return (found != NULL); +} + +/************************************************************************ +Checks if a given table name matches any of specifications given in +regex_list or tables_hash. + +@return TRUE on match or both regex_list and tables_hash are empty.*/ +static my_bool +check_if_table_matches_filters(const char *name, + const regex_list_t& regex_list, + hash_table_t* tables_hash) +{ + if (regex_list.empty() && !tables_hash) { + return(FALSE); + } + + if (regex_list_check_match(regex_list, name)) { + return(TRUE); + } + + if (tables_hash && find_filter_in_hashtable(name, tables_hash, NULL)) { + return(TRUE); + } + + return FALSE; +} + +enum skip_database_check_result { + DATABASE_SKIP, + DATABASE_SKIP_SOME_TABLES, + DATABASE_DONT_SKIP, + DATABASE_DONT_SKIP_UNLESS_EXPLICITLY_EXCLUDED, +}; + +/************************************************************************ +Checks if a database specified by name should be skipped from backup based on +the --databases, --databases_file or --databases_exclude options. + +@return TRUE if entire database should be skipped, + FALSE otherwise. +*/ +static +skip_database_check_result +check_if_skip_database( + const char* name /*!< in: path to the database */ +) +{ + /* There are some filters for databases, check them */ + xb_filter_entry_t* database = NULL; + + if (databases_exclude_hash && + find_filter_in_hashtable(name, databases_exclude_hash, + &database) && + !database->has_tables) { + /* Database is found and there are no tables specified, + skip entire db. */ + return DATABASE_SKIP; + } + + if (databases_include_hash) { + if (!find_filter_in_hashtable(name, databases_include_hash, + &database)) { + /* Database isn't found, skip the database */ + return DATABASE_SKIP; + } else if (database->has_tables) { + return DATABASE_SKIP_SOME_TABLES; + } else { + return DATABASE_DONT_SKIP_UNLESS_EXPLICITLY_EXCLUDED; + } + } + + return DATABASE_DONT_SKIP; +} + +/************************************************************************ +Checks if a database specified by path should be skipped from backup based on +the --databases, --databases_file or --databases_exclude options. + +@return TRUE if the table should be skipped. */ +my_bool +check_if_skip_database_by_path( + const char* path /*!< in: path to the db directory. */ +) +{ + if (databases_include_hash == NULL && + databases_exclude_hash == NULL) { + return(FALSE); + } + + const char* db_name = strrchr(path, SRV_PATH_SEPARATOR); + if (db_name == NULL) { + db_name = path; + } else { + ++db_name; + } + + return check_if_skip_database(db_name) == DATABASE_SKIP; +} + +/************************************************************************ +Checks if a table specified as a name in the form "database/name" (InnoDB 5.6) +or "./database/name.ibd" (InnoDB 5.5-) should be skipped from backup based on +the --tables or --tables-file options. + +@return TRUE if the table should be skipped. */ +my_bool +check_if_skip_table( +/******************/ + const char* name) /*!< in: path to the table */ +{ + char buf[FN_REFLEN]; + const char *dbname, *tbname; + const char *ptr; + char *eptr; + + if (regex_exclude_list.empty() && + regex_include_list.empty() && + tables_include_hash == NULL && + tables_exclude_hash == NULL && + databases_include_hash == NULL && + databases_exclude_hash == NULL) { + return(FALSE); + } + + dbname = NULL; + tbname = name; + while ((ptr = strchr(tbname, '/')) != NULL) { + dbname = tbname; + tbname = ptr + 1; + } + + if (dbname == NULL) { + return(FALSE); + } + + strncpy(buf, dbname, FN_REFLEN); + buf[tbname - 1 - dbname] = 0; + + const skip_database_check_result skip_database = + check_if_skip_database(buf); + if (skip_database == DATABASE_SKIP) { + return (TRUE); + } + + buf[FN_REFLEN - 1] = '\0'; + buf[tbname - 1 - dbname] = '.'; + + /* Check if there's a suffix in the table name. If so, truncate it. We + rely on the fact that a dot cannot be a part of a table name (it is + encoded by the server with the @NNNN syntax). */ + if ((eptr = strchr(&buf[tbname - dbname], '.')) != NULL) { + + *eptr = '\0'; + } + + /* For partitioned tables first try to match against the regexp + without truncating the #P#... suffix so we can backup individual + partitions with regexps like '^test[.]t#P#p5' */ + if (check_if_table_matches_filters(buf, regex_exclude_list, + tables_exclude_hash)) { + return(TRUE); + } + if (check_if_table_matches_filters(buf, regex_include_list, + tables_include_hash)) { + return(FALSE); + } + if ((eptr = strstr(buf, "#P#")) != NULL) { + *eptr = 0; + + if (check_if_table_matches_filters(buf, regex_exclude_list, + tables_exclude_hash)) { + return (TRUE); + } + if (check_if_table_matches_filters(buf, regex_include_list, + tables_include_hash)) { + return(FALSE); + } + } + + if (skip_database == DATABASE_DONT_SKIP_UNLESS_EXPLICITLY_EXCLUDED) { + /* Database is in include-list, and qualified name wasn't + found in any of exclusion filters.*/ + return (FALSE); + } + + if (skip_database == DATABASE_SKIP_SOME_TABLES || + !regex_include_list.empty() || + tables_include_hash) { + + /* Include lists are present, but qualified name + failed to match any.*/ + return(TRUE); + } + + return(FALSE); +} + +/*********************************************************************** +Reads the space flags from a given data file and returns the compressed +page size, or 0 if the space is not compressed. */ +ulint +xb_get_zip_size(os_file_t file) +{ + byte *buf; + byte *page; + ulint zip_size = ULINT_UNDEFINED; + ibool success; + ulint space; + + buf = static_cast<byte *>(ut_malloc(2 * UNIV_PAGE_SIZE)); + page = static_cast<byte *>(ut_align(buf, UNIV_PAGE_SIZE)); + + success = os_file_read(file, page, 0, UNIV_PAGE_SIZE); + if (!success) { + goto end; + } + + space = mach_read_from_4(page + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID); + zip_size = (space == 0 ) ? 0 : + dict_tf_get_zip_size(fsp_header_get_flags(page)); +end: + ut_free(buf); + + return(zip_size); +} + +const char* +xb_get_copy_action(const char *dflt) +{ + const char *action; + + if (xtrabackup_stream) { + if (xtrabackup_compress) { + if (xtrabackup_encrypt) { + action = "Compressing, encrypting and streaming"; + } else { + action = "Compressing and streaming"; + } + } else if (xtrabackup_encrypt) { + action = "Encrypting and streaming"; + } else { + action = "Streaming"; + } + } else { + if (xtrabackup_compress) { + if (xtrabackup_encrypt) { + action = "Compressing and encrypting"; + } else { + action = "Compressing"; + } + } else if (xtrabackup_encrypt) { + action = "Encrypting"; + } else { + action = dflt; + } + } + + return(action); +} + +/* TODO: We may tune the behavior (e.g. by fil_aio)*/ + +static +my_bool +xtrabackup_copy_datafile(fil_node_t* node, uint thread_n) +{ + char dst_name[FN_REFLEN]; + ds_file_t *dstfile = NULL; + xb_fil_cur_t cursor; + xb_fil_cur_result_t res; + xb_write_filt_t *write_filter = NULL; + xb_write_filt_ctxt_t write_filt_ctxt; + const char *action; + xb_read_filt_t *read_filter; + ibool is_system; + my_bool rc = FALSE; + + /* Get the name and the path for the tablespace. node->name always + contains the path (which may be absolute for remote tablespaces in + 5.6+). space->name contains the tablespace name in the form + "./database/table.ibd" (in 5.5-) or "database/table" (in 5.6+). For a + multi-node shared tablespace, space->name contains the name of the first + node, but that's irrelevant, since we only need node_name to match them + against filters, and the shared tablespace is always copied regardless + of the filters value. */ + + const char* const node_name = node->space->name; + const char* const node_path = node->name; + + is_system = !fil_is_user_tablespace_id(node->space->id); + + if (!is_system && check_if_skip_table(node_name)) { + msg("[%02u] Skipping %s.\n", thread_n, node_name); + return(FALSE); + } + + if (!changed_page_bitmap) { + read_filter = &rf_pass_through; + } + else { + read_filter = &rf_bitmap; + } + res = xb_fil_cur_open(&cursor, read_filter, node, thread_n); + if (res == XB_FIL_CUR_SKIP) { + goto skip; + } else if (res == XB_FIL_CUR_ERROR) { + goto error; + } + + strncpy(dst_name, cursor.rel_path, sizeof(dst_name)); + + /* Setup the page write filter */ + if (xtrabackup_incremental) { + write_filter = &wf_incremental; + } else { + write_filter = &wf_write_through; + } + + memset(&write_filt_ctxt, 0, sizeof(xb_write_filt_ctxt_t)); + ut_a(write_filter->process != NULL); + + if (write_filter->init != NULL && + !write_filter->init(&write_filt_ctxt, dst_name, &cursor)) { + msg("[%02u] xtrabackup: error: " + "failed to initialize page write filter.\n", thread_n); + goto error; + } + + dstfile = ds_open(ds_data, dst_name, &cursor.statinfo); + if (dstfile == NULL) { + msg("[%02u] xtrabackup: error: " + "cannot open the destination stream for %s\n", + thread_n, dst_name); + goto error; + } + + action = xb_get_copy_action(); + + if (xtrabackup_stream) { + msg_ts("[%02u] %s %s\n", thread_n, action, node_path); + } else { + msg_ts("[%02u] %s %s to %s\n", thread_n, action, + node_path, dstfile->path); + } + + /* The main copy loop */ + while ((res = xb_fil_cur_read(&cursor)) == XB_FIL_CUR_SUCCESS) { + if (!write_filter->process(&write_filt_ctxt, dstfile)) { + goto error; + } + } + + if (res == XB_FIL_CUR_ERROR) { + goto error; + } + + if (write_filter->finalize + && !write_filter->finalize(&write_filt_ctxt, dstfile)) { + goto error; + } + + /* close */ + msg_ts("[%02u] ...done\n", thread_n); + xb_fil_cur_close(&cursor); + if (ds_close(dstfile)) { + rc = TRUE; + } + if (write_filter && write_filter->deinit) { + write_filter->deinit(&write_filt_ctxt); + } + return(rc); + +error: + xb_fil_cur_close(&cursor); + if (dstfile != NULL) { + ds_close(dstfile); + } + if (write_filter && write_filter->deinit) { + write_filter->deinit(&write_filt_ctxt);; + } + msg("[%02u] xtrabackup: Error: " + "xtrabackup_copy_datafile() failed.\n", thread_n); + return(TRUE); /*ERROR*/ + +skip: + + if (dstfile != NULL) { + ds_close(dstfile); + } + if (write_filter && write_filter->deinit) { + write_filter->deinit(&write_filt_ctxt); + } + msg("[%02u] xtrabackup: Warning: We assume the " + "table was dropped during xtrabackup execution " + "and ignore the file.\n", thread_n); + msg("[%02u] xtrabackup: Warning: skipping tablespace %s.\n", + thread_n, node_name); + return(FALSE); +} + +static +void +xtrabackup_choose_lsn_offset(lsn_t start_lsn) +{ +#if SUPPORT_PERCONA_5_5 + ulint no, alt_no, expected_no; + ulint blocks_in_group; + lsn_t tmp_offset, end_lsn; + int lsn_chosen = 0; + log_group_t *group; + + start_lsn = ut_uint64_align_down(start_lsn, OS_FILE_LOG_BLOCK_SIZE); + end_lsn = start_lsn + RECV_SCAN_SIZE; + + group = UT_LIST_GET_FIRST(log_sys->log_groups); + + if (mysql_server_version < 50500 || mysql_server_version > 50600) { + /* only make sense for Percona Server 5.5 */ + return; + } + + if (server_flavor == FLAVOR_PERCONA_SERVER) { + /* it is Percona Server 5.5 */ + group->alt_offset_chosen = true; + group->lsn_offset = group->lsn_offset_alt; + return; + } + + if (group->lsn_offset_alt == group->lsn_offset || + group->lsn_offset_alt == (lsn_t) -1) { + /* we have only one option */ + return; + } + + no = alt_no = (ulint) -1; + lsn_chosen = 0; + + blocks_in_group = log_block_convert_lsn_to_no( + log_group_get_capacity(group)) - 1; + + /* read log block number from usual offset */ + if (group->lsn_offset < group->file_size * group->n_files && + (log_group_calc_lsn_offset(start_lsn, group) % + UNIV_PAGE_SIZE) % OS_MIN_LOG_BLOCK_SIZE == 0) { + log_group_read_log_seg(LOG_RECOVER, log_sys->buf, + group, start_lsn, end_lsn); + no = log_block_get_hdr_no(log_sys->buf); + } + + /* read log block number from Percona Server 5.5 offset */ + tmp_offset = group->lsn_offset; + group->lsn_offset = group->lsn_offset_alt; + + if (group->lsn_offset < group->file_size * group->n_files && + (log_group_calc_lsn_offset(start_lsn, group) % + UNIV_PAGE_SIZE) % OS_MIN_LOG_BLOCK_SIZE == 0) { + log_group_read_log_seg(LOG_RECOVER, log_sys->buf, + group, start_lsn, end_lsn); + alt_no = log_block_get_hdr_no(log_sys->buf); + } + + expected_no = log_block_convert_lsn_to_no(start_lsn); + + ut_a(!(no == expected_no && alt_no == expected_no)); + + group->lsn_offset = tmp_offset; + + if ((no <= expected_no && + ((expected_no - no) % blocks_in_group) == 0) || + ((expected_no | 0x40000000UL) - no) % blocks_in_group == 0) { + /* default offset looks ok */ + ++lsn_chosen; + } + + if ((alt_no <= expected_no && + ((expected_no - alt_no) % blocks_in_group) == 0) || + ((expected_no | 0x40000000UL) - alt_no) % blocks_in_group == 0) { + /* PS 5.5 style offset looks ok */ + ++lsn_chosen; + group->alt_offset_chosen = true; + group->lsn_offset = group->lsn_offset_alt; + } + + /* We are in trouble, because we can not make a + decision to choose one over the other. Die just + like a Buridan's ass */ + ut_a(lsn_chosen == 1); +#endif +} + +extern ibool log_block_checksum_is_ok_or_old_format(const byte* block); + +/*******************************************************//** +Scans log from a buffer and writes new log data to the outpud datasinc. +@return true if success */ +static +bool +xtrabackup_scan_log_recs( +/*===============*/ + log_group_t* group, /*!< in: log group */ + bool is_last, /*!< in: whether it is last segment + to copy */ + lsn_t start_lsn, /*!< in: buffer start lsn */ + lsn_t* contiguous_lsn, /*!< in/out: it is known that all log + groups contain contiguous log data up + to this lsn */ + lsn_t* group_scanned_lsn,/*!< out: scanning succeeded up to + this lsn */ + bool* finished) /*!< out: false if is not able to scan + any more in this log group */ +{ + lsn_t scanned_lsn; + ulint data_len; + ulint write_size; + const byte* log_block; + + ulint scanned_checkpoint_no = 0; + + *finished = false; + scanned_lsn = start_lsn; + log_block = log_sys->buf; + + while (log_block < log_sys->buf + RECV_SCAN_SIZE && !*finished) { + ulint no = log_block_get_hdr_no(log_block); + ulint scanned_no = log_block_convert_lsn_to_no(scanned_lsn); + ibool checksum_is_ok = + log_block_checksum_is_ok_or_old_format(log_block); + + if (no != scanned_no && checksum_is_ok) { + ulint blocks_in_group; + + blocks_in_group = log_block_convert_lsn_to_no( + log_group_get_capacity(group)) - 1; + + if ((no < scanned_no && + ((scanned_no - no) % blocks_in_group) == 0) || + no == 0 || + /* Log block numbers wrap around at 0x3FFFFFFF */ + ((scanned_no | 0x40000000UL) - no) % + blocks_in_group == 0) { + + /* old log block, do nothing */ + *finished = true; + break; + } + + msg("xtrabackup: error:" + " log block numbers mismatch:\n" + "xtrabackup: error: expected log block no. %lu," + " but got no. %lu from the log file.\n", + (ulong) scanned_no, (ulong) no); + + if ((no - scanned_no) % blocks_in_group == 0) { + msg("xtrabackup: error:" + " it looks like InnoDB log has wrapped" + " around before xtrabackup could" + " process all records due to either" + " log copying being too slow, or " + " log files being too small.\n"); + } + + return(false); + } else if (!checksum_is_ok) { + /* Garbage or an incompletely written log block */ + + msg("xtrabackup: warning: Log block checksum mismatch" + " (block no %lu at lsn " LSN_PF "): \n" + "expected %lu, calculated checksum %lu\n", + (ulong) no, + scanned_lsn, + (ulong) log_block_get_checksum(log_block), + (ulong) log_block_calc_checksum(log_block)); + msg("xtrabackup: warning: this is possible when the " + "log block has not been fully written by the " + "server, will retry later.\n"); + *finished = true; + break; + } + + if (log_block_get_flush_bit(log_block)) { + /* This block was a start of a log flush operation: + we know that the previous flush operation must have + been completed for all log groups before this block + can have been flushed to any of the groups. Therefore, + we know that log data is contiguous up to scanned_lsn + in all non-corrupt log groups. */ + + if (scanned_lsn > *contiguous_lsn) { + + *contiguous_lsn = scanned_lsn; + } + } + + data_len = log_block_get_data_len(log_block); + + if ( + (scanned_checkpoint_no > 0) + && (log_block_get_checkpoint_no(log_block) + < scanned_checkpoint_no) + && (scanned_checkpoint_no + - log_block_get_checkpoint_no(log_block) + > 0x80000000UL)) { + + /* Garbage from a log buffer flush which was made + before the most recent database recovery */ + + *finished = true; + break; + } + + scanned_lsn = scanned_lsn + data_len; + scanned_checkpoint_no = log_block_get_checkpoint_no(log_block); + + if (data_len < OS_FILE_LOG_BLOCK_SIZE) { + /* Log data for this group ends here */ + + *finished = true; + } else { + log_block += OS_FILE_LOG_BLOCK_SIZE; + } + } + + *group_scanned_lsn = scanned_lsn; + + /* ===== write log to 'xtrabackup_logfile' ====== */ + if (!*finished) { + write_size = RECV_SCAN_SIZE; + } else { + write_size = (ulint)(ut_uint64_align_up(scanned_lsn, + OS_FILE_LOG_BLOCK_SIZE) - start_lsn); + if (!is_last && scanned_lsn % OS_FILE_LOG_BLOCK_SIZE) { + write_size -= OS_FILE_LOG_BLOCK_SIZE; + } + } + + if (write_size == 0) { + return(true); + } + + if (srv_encrypt_log) { + log_encrypt_before_write(scanned_checkpoint_no, + log_sys->buf, write_size); + } + + if (ds_write(dst_log_file, log_sys->buf, write_size)) { + msg("xtrabackup: Error: " + "write to logfile failed\n"); + return(false); + } + + return(true); +} + +static my_bool +xtrabackup_copy_logfile(lsn_t from_lsn, my_bool is_last) +{ + /* definition from recv_recovery_from_checkpoint_start() */ + log_group_t* group; + lsn_t group_scanned_lsn; + lsn_t contiguous_lsn; + + ut_a(dst_log_file != NULL); + + /* read from checkpoint_lsn_start to current */ + contiguous_lsn = ut_uint64_align_down(from_lsn, OS_FILE_LOG_BLOCK_SIZE); + + /* TODO: We must check the contiguous_lsn still exists in log file.. */ + + group = UT_LIST_GET_FIRST(log_sys->log_groups); + + while (group) { + bool finished; + lsn_t start_lsn; + lsn_t end_lsn; + + /* reference recv_group_scan_log_recs() */ + finished = false; + + start_lsn = contiguous_lsn; + + while (!finished) { + + end_lsn = start_lsn + RECV_SCAN_SIZE; + + xtrabackup_io_throttling(); + + mutex_enter(&log_sys->mutex); + + log_group_read_log_seg(LOG_RECOVER, log_sys->buf, + group, start_lsn, end_lsn, false); + + if (!xtrabackup_scan_log_recs(group, is_last, + start_lsn, &contiguous_lsn, &group_scanned_lsn, + &finished)) { + goto error; + } + + mutex_exit(&log_sys->mutex); + + start_lsn = end_lsn; + + } + + group->scanned_lsn = group_scanned_lsn; + + msg_ts(">> log scanned up to (" LSN_PF ")\n", + group->scanned_lsn); + + group = UT_LIST_GET_NEXT(log_groups, group); + + /* update global variable*/ + log_copy_scanned_lsn = group_scanned_lsn; + + /* innodb_mirrored_log_groups must be 1, no other groups */ + ut_a(group == NULL); + + debug_sync_point("xtrabackup_copy_logfile_pause"); + + } + + + return(FALSE); + +error: + mutex_exit(&log_sys->mutex); + ds_close(dst_log_file); + msg("xtrabackup: Error: xtrabackup_copy_logfile() failed.\n"); + return(TRUE); +} + +static +#ifndef __WIN__ +void* +#else +ulint +#endif +log_copying_thread( + void* arg __attribute__((unused))) +{ + /* + Initialize mysys thread-specific memory so we can + use mysys functions in this thread. + */ + my_thread_init(); + + ut_a(dst_log_file != NULL); + + log_copying_running = TRUE; + + while(log_copying) { + os_event_reset(log_copying_stop); + os_event_wait_time_low(log_copying_stop, + xtrabackup_log_copy_interval * 1000ULL, + 0); + if (log_copying) { + if(xtrabackup_copy_logfile(log_copy_scanned_lsn, + FALSE)) { + + exit(EXIT_FAILURE); + } + } + } + + /* last copying */ + if(xtrabackup_copy_logfile(log_copy_scanned_lsn, TRUE)) { + + exit(EXIT_FAILURE); + } + + log_copying_running = FALSE; + my_thread_end(); + os_thread_exit(NULL); + + return(0); +} + +/* io throttle watching (rough) */ +static +#ifndef __WIN__ +void* +#else +ulint +#endif +io_watching_thread( + void* arg) +{ + (void)arg; + /* currently, for --backup only */ + ut_a(xtrabackup_backup); + + io_watching_thread_running = TRUE; + + while (log_copying) { + os_thread_sleep(1000000); /*1 sec*/ + io_ticket = xtrabackup_throttle; + os_event_set(wait_throttle); + } + + /* stop io throttle */ + xtrabackup_throttle = 0; + os_event_set(wait_throttle); + + io_watching_thread_running = FALSE; + + os_thread_exit(NULL); + + return(0); +} + +/************************************************************************ +I/o-handler thread function. */ +static + +#ifndef __WIN__ +void* +#else +ulint +#endif +io_handler_thread( +/*==============*/ + void* arg) +{ + ulint segment; + + + segment = *((ulint*)arg); + + while (srv_shutdown_state != SRV_SHUTDOWN_EXIT_THREADS) { + fil_aio_wait(segment); + } + + /* We count the number of threads in os_thread_exit(). A created + thread should always use that to exit and not use return() to exit. + The thread actually never comes here because it is exited in an + os_event_wait(). */ + + os_thread_exit(NULL); + +#ifndef __WIN__ + return(NULL); /* Not reached */ +#else + return(0); +#endif +} + +/************************************************************************** +Datafiles copying thread.*/ +static +os_thread_ret_t +data_copy_thread_func( +/*==================*/ + void *arg) /* thread context */ +{ + data_thread_ctxt_t *ctxt = (data_thread_ctxt_t *) arg; + uint num = ctxt->num; + fil_node_t* node; + + /* + Initialize mysys thread-specific memory so we can + use mysys functions in this thread. + */ + my_thread_init(); + + debug_sync_point("data_copy_thread_func"); + + while ((node = datafiles_iter_next(ctxt->it)) != NULL) { + + /* copy the datafile */ + if(xtrabackup_copy_datafile(node, num)) { + msg("[%02u] xtrabackup: Error: " + "failed to copy datafile.\n", num); + exit(EXIT_FAILURE); + } + } + + os_mutex_enter(ctxt->count_mutex); + (*ctxt->count)--; + os_mutex_exit(ctxt->count_mutex); + + my_thread_end(); + os_thread_exit(NULL); + OS_THREAD_DUMMY_RETURN; +} + +/************************************************************************ +Initialize the appropriate datasink(s). Both local backups and streaming in the +'xbstream' format allow parallel writes so we can write directly. + +Otherwise (i.e. when streaming in the 'tar' format) we need 2 separate datasinks +for the data stream (and don't allow parallel data copying) and for metainfo +files (including xtrabackup_logfile). The second datasink writes to temporary +files first, and then streams them in a serialized way when closed. */ +static void +xtrabackup_init_datasinks(void) +{ + if (xtrabackup_parallel > 1 && xtrabackup_stream && + xtrabackup_stream_fmt == XB_STREAM_FMT_TAR) { + msg("xtrabackup: warning: the --parallel option does not have " + "any effect when streaming in the 'tar' format. " + "You can use the 'xbstream' format instead.\n"); + xtrabackup_parallel = 1; + } + + /* Start building out the pipelines from the terminus back */ + if (xtrabackup_stream) { + /* All streaming goes to stdout */ + ds_data = ds_meta = ds_redo = ds_create(xtrabackup_target_dir, + DS_TYPE_STDOUT); + } else { + /* Local filesystem */ + ds_data = ds_meta = ds_redo = ds_create(xtrabackup_target_dir, + DS_TYPE_LOCAL); + } + + /* Track it for destruction */ + xtrabackup_add_datasink(ds_data); + + /* Stream formatting */ + if (xtrabackup_stream) { + ds_ctxt_t *ds; + if (xtrabackup_stream_fmt == XB_STREAM_FMT_TAR) { + ds = ds_create(xtrabackup_target_dir, DS_TYPE_ARCHIVE); + } else if (xtrabackup_stream_fmt == XB_STREAM_FMT_XBSTREAM) { + ds = ds_create(xtrabackup_target_dir, DS_TYPE_XBSTREAM); + } else { + /* bad juju... */ + ds = NULL; + } + + xtrabackup_add_datasink(ds); + + ds_set_pipe(ds, ds_data); + ds_data = ds; + + if (xtrabackup_stream_fmt != XB_STREAM_FMT_XBSTREAM) { + + /* 'tar' does not allow parallel streams */ + ds_redo = ds_meta = ds_create(xtrabackup_target_dir, + DS_TYPE_TMPFILE); + xtrabackup_add_datasink(ds_meta); + ds_set_pipe(ds_meta, ds); + } else { + ds_redo = ds_meta = ds_data; + } + } + + /* Encryption */ + if (xtrabackup_encrypt) { + ds_ctxt_t *ds; + + + + ds = ds_create(xtrabackup_target_dir, DS_TYPE_ENCRYPT); + xtrabackup_add_datasink(ds); + + ds_set_pipe(ds, ds_data); + if (ds_data != ds_meta) { + ds_data = ds; + ds = ds_create(xtrabackup_target_dir, DS_TYPE_ENCRYPT); + xtrabackup_add_datasink(ds); + + ds_set_pipe(ds, ds_meta); + ds_redo = ds_meta = ds; + } else { + ds_redo = ds_data = ds_meta = ds; + } + } + + /* Compression for ds_data and ds_redo */ + if (xtrabackup_compress) { + ds_ctxt_t *ds; + + /* Use a 1 MB buffer for compressed output stream */ + ds = ds_create(xtrabackup_target_dir, DS_TYPE_BUFFER); + ds_buffer_set_size(ds, 1024 * 1024); + xtrabackup_add_datasink(ds); + ds_set_pipe(ds, ds_data); + if (ds_data != ds_redo) { + ds_data = ds; + ds = ds_create(xtrabackup_target_dir, DS_TYPE_BUFFER); + ds_buffer_set_size(ds, 1024 * 1024); + xtrabackup_add_datasink(ds); + ds_set_pipe(ds, ds_redo); + ds_redo = ds; + } else { + ds_redo = ds_data = ds; + } + + ds = ds_create(xtrabackup_target_dir, DS_TYPE_COMPRESS); + xtrabackup_add_datasink(ds); + ds_set_pipe(ds, ds_data); + if (ds_data != ds_redo) { + ds_data = ds; + ds = ds_create(xtrabackup_target_dir, DS_TYPE_COMPRESS); + xtrabackup_add_datasink(ds); + ds_set_pipe(ds, ds_redo); + ds_redo = ds; + } else { + ds_redo = ds_data = ds; + } + } +} + +/************************************************************************ +Destroy datasinks. + +Destruction is done in the specific order to not violate their order in the +pipeline so that each datasink is able to flush data down the pipeline. */ +static void xtrabackup_destroy_datasinks(void) +{ + for (uint i = actual_datasinks; i > 0; i--) { + ds_destroy(datasinks[i-1]); + datasinks[i-1] = NULL; + } + ds_data = NULL; + ds_meta = NULL; + ds_redo = NULL; +} + +#define SRV_N_PENDING_IOS_PER_THREAD OS_AIO_N_PENDING_IOS_PER_THREAD +#define SRV_MAX_N_PENDING_SYNC_IOS 100 + +/************************************************************************ +@return TRUE if table should be opened. */ +static +ibool +xb_check_if_open_tablespace( + const char* db, + const char* table) +{ + char buf[FN_REFLEN]; + + snprintf(buf, sizeof(buf), "%s/%s", db, table); + + return !check_if_skip_table(buf); +} + +/************************************************************************ +Initializes the I/O and tablespace cache subsystems. */ +static +void +xb_fil_io_init(void) +/*================*/ +{ + srv_n_file_io_threads = srv_n_read_io_threads; + + os_aio_init(8 * SRV_N_PENDING_IOS_PER_THREAD, + srv_n_read_io_threads, + srv_n_write_io_threads, + SRV_MAX_N_PENDING_SYNC_IOS); + + fil_init(srv_file_per_table ? 50000 : 5000, LONG_MAX); + + fsp_init(); +} + +/**************************************************************************** +Populates the tablespace memory cache by scanning for and opening data files. +@returns DB_SUCCESS or error code.*/ +static +ulint +xb_load_tablespaces(void) +/*=====================*/ +{ + ulint i; + ibool create_new_db; + ulint err; + ulint sum_of_new_sizes; + lsn_t min_arch_logno, max_arch_logno; + + for (i = 0; i < srv_n_file_io_threads; i++) { + thread_nr[i] = i; + + os_thread_create(io_handler_thread, thread_nr + i, + thread_ids + i); + } + + os_thread_sleep(200000); /*0.2 sec*/ + + err = open_or_create_data_files(&create_new_db, + &min_arch_logno, &max_arch_logno, + &min_flushed_lsn, &max_flushed_lsn, + &sum_of_new_sizes); + if (err != DB_SUCCESS) { + msg("xtrabackup: Could not open or create data files.\n" + "xtrabackup: If you tried to add new data files, and it " + "failed here,\n" + "xtrabackup: you should now edit innodb_data_file_path in " + "my.cnf back\n" + "xtrabackup: to what it was, and remove the new ibdata " + "files InnoDB created\n" + "xtrabackup: in this failed attempt. InnoDB only wrote " + "those files full of\n" + "xtrabackup: zeros, but did not yet use them in any way. " + "But be careful: do not\n" + "xtrabackup: remove old data files which contain your " + "precious data!\n"); + return(err); + } + + /* create_new_db must not be TRUE.. */ + if (create_new_db) { + msg("xtrabackup: could not find data files at the " + "specified datadir\n"); + return(DB_ERROR); + } + + /* Add separate undo tablespaces to fil_system */ + + err = srv_undo_tablespaces_init(FALSE, + TRUE, + srv_undo_tablespaces, + &srv_undo_tablespaces_open); + if (err != DB_SUCCESS) { + return(err); + } + + /* It is important to call fil_load_single_table_tablespace() after + srv_undo_tablespaces_init(), because fil_is_user_tablespace_id() * + relies on srv_undo_tablespaces_open to be properly initialized */ + + msg("xtrabackup: Generating a list of tablespaces\n"); + + err = fil_load_single_table_tablespaces(xb_check_if_open_tablespace); + if (err != DB_SUCCESS) { + return(err); + } + + debug_sync_point("xtrabackup_load_tablespaces_pause"); + + return(DB_SUCCESS); +} + +/************************************************************************ +Initialize the tablespace memory cache and populate it by scanning for and +opening data files. +@returns DB_SUCCESS or error code.*/ +ulint +xb_data_files_init(void) +/*====================*/ +{ + xb_fil_io_init(); + + return(xb_load_tablespaces()); +} + +/************************************************************************ +Destroy the tablespace memory cache. */ +void +xb_data_files_close(void) +/*====================*/ +{ + ulint i; + + /* Shutdown the aio threads. This has been copied from + innobase_shutdown_for_mysql(). */ + + srv_shutdown_state = SRV_SHUTDOWN_EXIT_THREADS; + + for (i = 0; i < 1000; i++) { + os_aio_wake_all_threads_at_shutdown(); + + if (os_thread_count == 0) { + break; + } + os_thread_sleep(10000); + } + + if (i == 1000) { + msg("xtrabackup: Warning: %lu threads created by InnoDB" + " had not exited at shutdown!\n", + (ulong) os_thread_count); + } + + os_aio_free(); + + fil_close_all_files(); + + /* Free the double write data structures. */ + if (buf_dblwr) { + buf_dblwr_free(); + } + + /* Reset srv_file_io_threads to its default value to avoid confusing + warning on --prepare in innobase_start_or_create_for_mysql()*/ + srv_n_file_io_threads = 4; + + srv_shutdown_state = SRV_SHUTDOWN_NONE; +} + +/*********************************************************************** +Allocate and initialize the entry for databases and tables filtering +hash tables. If memory allocation is not successful, terminate program. +@return pointer to the created entry. */ +static +xb_filter_entry_t * +xb_new_filter_entry( +/*================*/ + const char* name) /*!< in: name of table/database */ +{ + xb_filter_entry_t *entry; + ulint namelen = strlen(name); + + ut_a(namelen <= NAME_LEN * 2 + 1); + + entry = static_cast<xb_filter_entry_t *> + (ut_malloc(sizeof(xb_filter_entry_t) + namelen + 1)); + memset(entry, '\0', sizeof(xb_filter_entry_t) + namelen + 1); + entry->name = ((char*)entry) + sizeof(xb_filter_entry_t); + strcpy(entry->name, name); + entry->has_tables = FALSE; + + return entry; +} + +/*********************************************************************** +Add entry to hash table. If hash table is NULL, allocate and initialize +new hash table */ +static +xb_filter_entry_t* +xb_add_filter( +/*========================*/ + const char* name, /*!< in: name of table/database */ + hash_table_t** hash) /*!< in/out: hash to insert into */ +{ + xb_filter_entry_t* entry; + + entry = xb_new_filter_entry(name); + + if (UNIV_UNLIKELY(*hash == NULL)) { + *hash = hash_create(1000); + } + HASH_INSERT(xb_filter_entry_t, + name_hash, *hash, + ut_fold_string(entry->name), + entry); + + return entry; +} + +/*********************************************************************** +Validate name of table or database. If name is invalid, program will +be finished with error code */ +static +void +xb_validate_name( +/*=============*/ + const char* name, /*!< in: name */ + size_t len) /*!< in: length of name */ +{ + const char* p; + + /* perform only basic validation. validate length and + path symbols */ + if (len > NAME_LEN) { + msg("xtrabackup: name `%s` is too long.\n", name); + exit(EXIT_FAILURE); + } + p = strpbrk(name, "/\\~"); + if (p && p - name < NAME_LEN) { + msg("xtrabackup: name `%s` is not valid.\n", name); + exit(EXIT_FAILURE); + } +} + +/*********************************************************************** +Register new filter entry which can be either database +or table name. */ +static +void +xb_register_filter_entry( +/*=====================*/ + const char* name, /*!< in: name */ + hash_table_t** databases_hash, + hash_table_t** tables_hash + ) +{ + const char* p; + size_t namelen; + xb_filter_entry_t* db_entry = NULL; + + namelen = strlen(name); + if ((p = strchr(name, '.')) != NULL) { + char dbname[NAME_LEN + 1]; + + xb_validate_name(name, p - name); + xb_validate_name(p + 1, namelen - (p - name)); + + strncpy(dbname, name, p - name); + dbname[p - name] = 0; + + if (*databases_hash) { + HASH_SEARCH(name_hash, (*databases_hash), + ut_fold_string(dbname), + xb_filter_entry_t*, + db_entry, (void) 0, + !strcmp(db_entry->name, dbname)); + } + if (!db_entry) { + db_entry = xb_add_filter(dbname, databases_hash); + } + db_entry->has_tables = TRUE; + xb_add_filter(name, tables_hash); + } else { + xb_validate_name(name, namelen); + + xb_add_filter(name, databases_hash); + } +} + +static +void +xb_register_include_filter_entry( + const char* name +) +{ + xb_register_filter_entry(name, &databases_include_hash, + &tables_include_hash); +} + +static +void +xb_register_exclude_filter_entry( + const char* name +) +{ + xb_register_filter_entry(name, &databases_exclude_hash, + &tables_exclude_hash); +} + +/*********************************************************************** +Register new table for the filter. */ +static +void +xb_register_table( +/*==============*/ + const char* name) /*!< in: name of table */ +{ + if (strchr(name, '.') == NULL) { + msg("xtrabackup: `%s` is not fully qualified name.\n", name); + exit(EXIT_FAILURE); + } + + xb_register_include_filter_entry(name); +} + +static +void +xb_add_regex_to_list( + const char* regex, /*!< in: regex */ + const char* error_context, /*!< in: context to error message */ + regex_list_t* list) /*! in: list to put new regex to */ +{ + char errbuf[100]; + int ret; + + regex_t compiled_regex; + ret = regcomp(&compiled_regex, regex, REG_EXTENDED); + + if (ret != 0) { + regerror(ret, &compiled_regex, errbuf, sizeof(errbuf)); + msg("xtrabackup: error: %s regcomp(%s): %s\n", + error_context, regex, errbuf); + exit(EXIT_FAILURE); + } + + list->push_back(compiled_regex); +} + +/*********************************************************************** +Register new regex for the include filter. */ +static +void +xb_register_include_regex( +/*==============*/ + const char* regex) /*!< in: regex */ +{ + xb_add_regex_to_list(regex, "tables", ®ex_include_list); +} + +/*********************************************************************** +Register new regex for the exclude filter. */ +static +void +xb_register_exclude_regex( +/*==============*/ + const char* regex) /*!< in: regex */ +{ + xb_add_regex_to_list(regex, "tables-exclude", ®ex_exclude_list); +} + +typedef void (*insert_entry_func_t)(const char*); + +/*********************************************************************** +Scan string and load filter entries from it. */ +static +void +xb_load_list_string( +/*================*/ + char* list, /*!< in: string representing a list */ + const char* delimiters, /*!< in: delimiters of entries */ + insert_entry_func_t ins) /*!< in: callback to add entry */ +{ + char* p; + char* saveptr; + + p = strtok_r(list, delimiters, &saveptr); + while (p) { + + ins(p); + + p = strtok_r(NULL, delimiters, &saveptr); + } +} + +/*********************************************************************** +Scan file and load filter entries from it. */ +static +void +xb_load_list_file( +/*==============*/ + const char* filename, /*!< in: name of file */ + insert_entry_func_t ins) /*!< in: callback to add entry */ +{ + char name_buf[NAME_LEN*2+2]; + FILE* fp; + + /* read and store the filenames */ + fp = fopen(filename, "r"); + if (!fp) { + msg("xtrabackup: cannot open %s\n", + filename); + exit(EXIT_FAILURE); + } + while (fgets(name_buf, sizeof(name_buf), fp) != NULL) { + char* p = strchr(name_buf, '\n'); + if (p) { + *p = '\0'; + } else { + msg("xtrabackup: `%s...` name is too long", name_buf); + exit(EXIT_FAILURE); + } + + ins(name_buf); + } + + fclose(fp); +} + + +static +void +xb_filters_init() +{ + if (xtrabackup_databases) { + xb_load_list_string(xtrabackup_databases, " \t", + xb_register_include_filter_entry); + } + + if (xtrabackup_databases_file) { + xb_load_list_file(xtrabackup_databases_file, + xb_register_include_filter_entry); + } + + if (xtrabackup_databases_exclude) { + xb_load_list_string(xtrabackup_databases_exclude, " \t", + xb_register_exclude_filter_entry); + } + + if (xtrabackup_tables) { + xb_load_list_string(xtrabackup_tables, ",", + xb_register_include_regex); + } + + if (xtrabackup_tables_file) { + xb_load_list_file(xtrabackup_tables_file, xb_register_table); + } + + if (xtrabackup_tables_exclude) { + xb_load_list_string(xtrabackup_tables_exclude, ",", + xb_register_exclude_regex); + } +} + +static +void +xb_filter_hash_free(hash_table_t* hash) +{ + ulint i; + + /* free the hash elements */ + for (i = 0; i < hash_get_n_cells(hash); i++) { + xb_filter_entry_t* table; + + table = static_cast<xb_filter_entry_t *> + (HASH_GET_FIRST(hash, i)); + + while (table) { + xb_filter_entry_t* prev_table = table; + + table = static_cast<xb_filter_entry_t *> + (HASH_GET_NEXT(name_hash, prev_table)); + + HASH_DELETE(xb_filter_entry_t, name_hash, hash, + ut_fold_string(prev_table->name), prev_table); + ut_free(prev_table); + } + } + + /* free hash */ + hash_table_free(hash); +} + +static void xb_regex_list_free(regex_list_t* list) +{ + while (list->size() > 0) { + xb_regfree(&list->front()); + list->pop_front(); + } +} + +/************************************************************************ +Destroy table filters for partial backup. */ +static +void +xb_filters_free() +{ + xb_regex_list_free(®ex_include_list); + xb_regex_list_free(®ex_exclude_list); + + if (tables_include_hash) { + xb_filter_hash_free(tables_include_hash); + } + + if (tables_exclude_hash) { + xb_filter_hash_free(tables_exclude_hash); + } + + if (databases_include_hash) { + xb_filter_hash_free(databases_include_hash); + } + + if (databases_exclude_hash) { + xb_filter_hash_free(databases_exclude_hash); + } +} + +/*********************************************************************//** +Creates or opens the log files and closes them. +@return DB_SUCCESS or error code */ +static +ulint +open_or_create_log_file( +/*====================*/ + ibool create_new_db, /*!< in: TRUE if we should create a + new database */ + ibool* log_file_created, /*!< out: TRUE if new log file + created */ + ibool log_file_has_been_opened,/*!< in: TRUE if a log file has been + opened before: then it is an error + to try to create another log file */ + ulint k, /*!< in: log group number */ + ulint i) /*!< in: log file number in group */ +{ + ibool ret; + os_offset_t size; + char name[10000]; + ulint dirnamelen; + + UT_NOT_USED(create_new_db); + UT_NOT_USED(log_file_has_been_opened); + UT_NOT_USED(k); + ut_ad(k == 0); + + *log_file_created = FALSE; + + srv_normalize_path_for_win(srv_log_group_home_dir); + + dirnamelen = strlen(srv_log_group_home_dir); + ut_a(dirnamelen < (sizeof name) - 10 - sizeof "ib_logfile"); + memcpy(name, srv_log_group_home_dir, dirnamelen); + + /* Add a path separator if needed. */ + if (dirnamelen && name[dirnamelen - 1] != SRV_PATH_SEPARATOR) { + name[dirnamelen++] = SRV_PATH_SEPARATOR; + } + + sprintf(name + dirnamelen, "%s%lu", "ib_logfile", (ulong) i); + + files[i] = os_file_create(innodb_file_log_key, name, + OS_FILE_OPEN, OS_FILE_NORMAL, + OS_LOG_FILE, &ret,0); + if (ret == FALSE) { + fprintf(stderr, "InnoDB: Error in opening %s\n", name); + + return(DB_ERROR); + } + + size = os_file_get_size(files[i]); + + if (size != srv_log_file_size * UNIV_PAGE_SIZE) { + + fprintf(stderr, + "InnoDB: Error: log file %s is" + " of different size " UINT64PF " bytes\n" + "InnoDB: than specified in the .cnf" + " file " UINT64PF " bytes!\n", + name, size, srv_log_file_size * UNIV_PAGE_SIZE); + + return(DB_ERROR); + } + + ret = os_file_close(files[i]); + ut_a(ret); + + if (i == 0) { + /* Create in memory the file space object + which is for this log group */ + + fil_space_create(name, + 2 * k + SRV_LOG_SPACE_FIRST_ID, 0, FIL_LOG, 0, 0); + } + + ut_a(fil_validate()); + + ut_a(fil_node_create(name, (ulint)srv_log_file_size, + 2 * k + SRV_LOG_SPACE_FIRST_ID, FALSE)); + if (i == 0) { + log_group_init(k, srv_n_log_files, + srv_log_file_size * UNIV_PAGE_SIZE, + 2 * k + SRV_LOG_SPACE_FIRST_ID, + SRV_LOG_SPACE_FIRST_ID + 1); /* dummy arch + space id */ + } + + return(DB_SUCCESS); +} + +/*********************************************************************//** +Normalizes init parameter values to use units we use inside InnoDB. +@return DB_SUCCESS or error code */ +static +void +xb_normalize_init_values(void) +/*==========================*/ +{ + ulint i; + + for (i = 0; i < srv_n_data_files; i++) { + srv_data_file_sizes[i] = srv_data_file_sizes[i] + * ((1024 * 1024) / UNIV_PAGE_SIZE); + } + + srv_last_file_size_max = srv_last_file_size_max + * ((1024 * 1024) / UNIV_PAGE_SIZE); + + srv_log_file_size = srv_log_file_size / UNIV_PAGE_SIZE; + + srv_log_buffer_size = srv_log_buffer_size / UNIV_PAGE_SIZE; + + srv_lock_table_size = 5 * (srv_buf_pool_size / UNIV_PAGE_SIZE); +} + +/*********************************************************************** +Set the open files limit. Based on set_max_open_files(). + +@return the resulting open files limit. May be less or more than the requested +value. */ +static uint +xb_set_max_open_files( +/*==================*/ + uint max_file_limit) /*!<in: open files limit */ +{ +#if defined(RLIMIT_NOFILE) + struct rlimit rlimit; + uint old_cur; + + if (getrlimit(RLIMIT_NOFILE, &rlimit)) { + + goto end; + } + + old_cur = (uint) rlimit.rlim_cur; + + if (rlimit.rlim_cur == RLIM_INFINITY) { + + rlimit.rlim_cur = max_file_limit; + } + + if (rlimit.rlim_cur >= max_file_limit) { + + max_file_limit = rlimit.rlim_cur; + goto end; + } + + rlimit.rlim_cur = rlimit.rlim_max = max_file_limit; + + if (setrlimit(RLIMIT_NOFILE, &rlimit)) { + + max_file_limit = old_cur; /* Use original value */ + } else { + + rlimit.rlim_cur = 0; /* Safety if next call fails */ + + (void) getrlimit(RLIMIT_NOFILE, &rlimit); + + if (rlimit.rlim_cur) { + + /* If call didn't fail */ + max_file_limit = (uint) rlimit.rlim_cur; + } + } + +end: + return(max_file_limit); +#else + return(0); +#endif +} + +void +xtrabackup_backup_func(void) +{ + MY_STAT stat_info; + lsn_t latest_cp; + uint i; + uint count; + os_ib_mutex_t count_mutex; + data_thread_ctxt_t *data_threads; + +#ifdef USE_POSIX_FADVISE + msg("xtrabackup: uses posix_fadvise().\n"); +#endif + + /* cd to datadir */ + + if (my_setwd(mysql_real_data_home,MYF(MY_WME))) + { + msg("xtrabackup: cannot my_setwd %s\n", mysql_real_data_home); + exit(EXIT_FAILURE); + } + msg("xtrabackup: cd to %s\n", mysql_real_data_home); + + msg("xtrabackup: open files limit requested %u, set to %u\n", + (uint) xb_open_files_limit, + xb_set_max_open_files(xb_open_files_limit)); + + mysql_data_home= mysql_data_home_buff; + mysql_data_home[0]=FN_CURLIB; // all paths are relative from here + mysql_data_home[1]=0; + + srv_n_purge_threads = 1; + srv_read_only_mode = TRUE; + + srv_backup_mode = TRUE; + srv_close_files = (bool)xb_close_files; + + if (srv_close_files) + msg("xtrabackup: warning: close-files specified. Use it " + "at your own risk. If there are DDL operations like table DROP TABLE " + "or RENAME TABLE during the backup, inconsistent backup will be " + "produced.\n"); + + /* initialize components */ + if(innodb_init_param()) + exit(EXIT_FAILURE); + + xb_normalize_init_values(); + + + if (srv_file_flush_method_str == NULL) { + /* These are the default options */ + srv_unix_file_flush_method = SRV_UNIX_FSYNC; + } else if (0 == ut_strcmp(srv_file_flush_method_str, "fsync")) { + srv_unix_file_flush_method = SRV_UNIX_FSYNC; + } else if (0 == ut_strcmp(srv_file_flush_method_str, "O_DSYNC")) { + srv_unix_file_flush_method = SRV_UNIX_O_DSYNC; + + } else if (0 == ut_strcmp(srv_file_flush_method_str, "O_DIRECT")) { + srv_unix_file_flush_method = SRV_UNIX_O_DIRECT; + msg("xtrabackup: using O_DIRECT\n"); + } else if (0 == ut_strcmp(srv_file_flush_method_str, "littlesync")) { + srv_unix_file_flush_method = SRV_UNIX_LITTLESYNC; + + } else if (0 == ut_strcmp(srv_file_flush_method_str, "nosync")) { + srv_unix_file_flush_method = SRV_UNIX_NOSYNC; + } else if (0 == ut_strcmp(srv_file_flush_method_str, "ALL_O_DIRECT")) { + srv_unix_file_flush_method = SRV_UNIX_ALL_O_DIRECT; + msg("xtrabackup: using ALL_O_DIRECT\n"); + } else if (0 == ut_strcmp(srv_file_flush_method_str, + "O_DIRECT_NO_FSYNC")) { + srv_unix_file_flush_method = SRV_UNIX_O_DIRECT_NO_FSYNC; + msg("xtrabackup: using O_DIRECT_NO_FSYNC\n"); + } else { + msg("xtrabackup: Unrecognized value %s for " + "innodb_flush_method\n", srv_file_flush_method_str); + exit(EXIT_FAILURE); + } + + /* We can only use synchronous unbuffered IO on Windows for now */ + if (srv_file_flush_method_str != NULL) { + msg("xtrabackupp: Warning: " + "ignoring innodb_flush_method = %s on Windows.\n", srv_file_flush_method_str); + } + +#ifdef _WIN32 + srv_win_file_flush_method = SRV_WIN_IO_UNBUFFERED; + srv_use_native_aio = FALSE; +#endif + + if (srv_buf_pool_size >= 1000 * 1024 * 1024) { + /* Here we still have srv_pool_size counted + in kilobytes (in 4.0 this was in bytes) + srv_boot() converts the value to + pages; if buffer pool is less than 1000 MB, + assume fewer threads. */ + srv_max_n_threads = 50000; + + } else if (srv_buf_pool_size >= 8 * 1024 * 1024) { + + srv_max_n_threads = 10000; + } else { + srv_max_n_threads = 1000; /* saves several MB of memory, + especially in 64-bit + computers */ + } + + srv_general_init(); + ut_crc32_init(); + crc_init(); + +#ifdef WITH_INNODB_DISALLOW_WRITES + srv_allow_writes_event = os_event_create(); + os_event_set(srv_allow_writes_event); +#endif + + xb_filters_init(); + + { + ibool log_file_created; + ibool log_created = FALSE; + ibool log_opened = FALSE; + ulint err; + ulint i; + + xb_fil_io_init(); + + log_init(); + + lock_sys_create(srv_lock_table_size); + + for (i = 0; i < srv_n_log_files; i++) { + err = open_or_create_log_file(FALSE, &log_file_created, + log_opened, 0, i); + if (err != DB_SUCCESS) { + + //return((int) err); + exit(EXIT_FAILURE); + } + + if (log_file_created) { + log_created = TRUE; + } else { + log_opened = TRUE; + } + if ((log_opened && log_created)) { + msg( + "xtrabackup: Error: all log files must be created at the same time.\n" + "xtrabackup: All log files must be created also in database creation.\n" + "xtrabackup: If you want bigger or smaller log files, shut down the\n" + "xtrabackup: database and make sure there were no errors in shutdown.\n" + "xtrabackup: Then delete the existing log files. Edit the .cnf file\n" + "xtrabackup: and start the database again.\n"); + + //return(DB_ERROR); + exit(EXIT_FAILURE); + } + } + + /* log_file_created must not be TRUE, if online */ + if (log_file_created) { + msg("xtrabackup: Something wrong with source files...\n"); + exit(EXIT_FAILURE); + } + + } + + /* create extra LSN dir if it does not exist. */ + if (xtrabackup_extra_lsndir + &&!my_stat(xtrabackup_extra_lsndir,&stat_info,MYF(0)) + && (my_mkdir(xtrabackup_extra_lsndir,0777,MYF(0)) < 0)) { + msg("xtrabackup: Error: cannot mkdir %d: %s\n", + my_errno, xtrabackup_extra_lsndir); + exit(EXIT_FAILURE); + } + + /* create target dir if not exist */ + if (!xtrabackup_stream_str && !my_stat(xtrabackup_target_dir,&stat_info,MYF(0)) + && (my_mkdir(xtrabackup_target_dir,0777,MYF(0)) < 0)){ + msg("xtrabackup: Error: cannot mkdir %d: %s\n", + my_errno, xtrabackup_target_dir); + exit(EXIT_FAILURE); + } + + { + fil_system_t* f_system = fil_system; + + /* definition from recv_recovery_from_checkpoint_start() */ + log_group_t* max_cp_group; + ulint max_cp_field; + byte* buf; + byte* log_hdr_buf_; + byte* log_hdr_buf; + ulint err; + + /* start back ground thread to copy newer log */ + os_thread_id_t log_copying_thread_id; + datafiles_iter_t *it; + + log_hdr_buf_ = static_cast<byte *> + (ut_malloc(LOG_FILE_HDR_SIZE + UNIV_PAGE_SIZE_MAX)); + log_hdr_buf = static_cast<byte *> + (ut_align(log_hdr_buf_, UNIV_PAGE_SIZE_MAX)); + + /* get current checkpoint_lsn */ + /* Look for the latest checkpoint from any of the log groups */ + + mutex_enter(&log_sys->mutex); + + err = recv_find_max_checkpoint(&max_cp_group, &max_cp_field); + + if (err != DB_SUCCESS) { + + ut_free(log_hdr_buf_); + exit(EXIT_FAILURE); + } + + log_group_read_checkpoint_info(max_cp_group, max_cp_field); + buf = log_sys->checkpoint_buf; + + checkpoint_lsn_start = mach_read_from_8(buf + LOG_CHECKPOINT_LSN); + checkpoint_no_start = mach_read_from_8(buf + LOG_CHECKPOINT_NO); + + mutex_exit(&log_sys->mutex); + +reread_log_header: + fil_io(OS_FILE_READ | OS_FILE_LOG, true, max_cp_group->space_id, + 0, + 0, 0, LOG_FILE_HDR_SIZE, + log_hdr_buf, max_cp_group, NULL); + + /* check consistency of log file header to copy */ + mutex_enter(&log_sys->mutex); + + err = recv_find_max_checkpoint(&max_cp_group, &max_cp_field); + + if (err != DB_SUCCESS) { + + ut_free(log_hdr_buf_); + exit(EXIT_FAILURE); + } + + log_group_read_checkpoint_info(max_cp_group, max_cp_field); + buf = log_sys->checkpoint_buf; + + if(checkpoint_no_start != mach_read_from_8(buf + LOG_CHECKPOINT_NO)) { + + checkpoint_lsn_start = mach_read_from_8(buf + LOG_CHECKPOINT_LSN); + checkpoint_no_start = mach_read_from_8(buf + LOG_CHECKPOINT_NO); + mutex_exit(&log_sys->mutex); + goto reread_log_header; + } + + mutex_exit(&log_sys->mutex); + + xtrabackup_init_datasinks(); + + if (!select_history()) { + exit(EXIT_FAILURE); + } + + /* open the log file */ + memset(&stat_info, 0, sizeof(MY_STAT)); + dst_log_file = ds_open(ds_redo, XB_LOG_FILENAME, &stat_info); + if (dst_log_file == NULL) { + msg("xtrabackup: error: failed to open the target stream for " + "'%s'.\n", XB_LOG_FILENAME); + ut_free(log_hdr_buf_); + exit(EXIT_FAILURE); + } + + /* label it */ + strcpy((char*) log_hdr_buf + LOG_FILE_WAS_CREATED_BY_HOT_BACKUP, + "xtrabkup "); + ut_sprintf_timestamp( + (char*) log_hdr_buf + (LOG_FILE_WAS_CREATED_BY_HOT_BACKUP + + (sizeof "xtrabkup ") - 1)); + + if (ds_write(dst_log_file, log_hdr_buf, LOG_FILE_HDR_SIZE)) { + msg("xtrabackup: error: write to logfile failed\n"); + ut_free(log_hdr_buf_); + exit(EXIT_FAILURE); + } + + ut_free(log_hdr_buf_); + + /* start flag */ + log_copying = TRUE; + + /* start io throttle */ + if(xtrabackup_throttle) { + os_thread_id_t io_watching_thread_id; + + io_ticket = xtrabackup_throttle; + wait_throttle = os_event_create(); + + os_thread_create(io_watching_thread, NULL, + &io_watching_thread_id); + } + + mutex_enter(&log_sys->mutex); + xtrabackup_choose_lsn_offset(checkpoint_lsn_start); + mutex_exit(&log_sys->mutex); + + /* copy log file by current position */ + if(xtrabackup_copy_logfile(checkpoint_lsn_start, FALSE)) + exit(EXIT_FAILURE); + + + log_copying_stop = os_event_create(); + os_thread_create(log_copying_thread, NULL, &log_copying_thread_id); + + /* Populate fil_system with tablespaces to copy */ + err = xb_load_tablespaces(); + if (err != DB_SUCCESS) { + msg("xtrabackup: error: xb_load_tablespaces() failed with" + "error code %lu\n", err); + exit(EXIT_FAILURE); + } + + /* FLUSH CHANGED_PAGE_BITMAPS call */ + if (!flush_changed_page_bitmaps()) { + exit(EXIT_FAILURE); + } + debug_sync_point("xtrabackup_suspend_at_start"); + + if (xtrabackup_incremental) { + if (!xtrabackup_incremental_force_scan) { + changed_page_bitmap = xb_page_bitmap_init(); + } + if (!changed_page_bitmap) { + msg("xtrabackup: using the full scan for incremental " + "backup\n"); + } else if (incremental_lsn != checkpoint_lsn_start) { + /* Do not print that bitmaps are used when dummy bitmap + is build for an empty LSN range. */ + msg("xtrabackup: using the changed page bitmap\n"); + } + } + + ut_a(xtrabackup_parallel > 0); + + if (xtrabackup_parallel > 1) { + msg("xtrabackup: Starting %u threads for parallel data " + "files transfer\n", xtrabackup_parallel); + } + + it = datafiles_iter_new(f_system); + if (it == NULL) { + msg("xtrabackup: Error: datafiles_iter_new() failed.\n"); + exit(EXIT_FAILURE); + } + + /* Create data copying threads */ + data_threads = (data_thread_ctxt_t *) + ut_malloc(sizeof(data_thread_ctxt_t) * xtrabackup_parallel); + count = xtrabackup_parallel; + count_mutex = os_mutex_create(); + + for (i = 0; i < (uint) xtrabackup_parallel; i++) { + data_threads[i].it = it; + data_threads[i].num = i+1; + data_threads[i].count = &count; + data_threads[i].count_mutex = count_mutex; + os_thread_create(data_copy_thread_func, data_threads + i, + &data_threads[i].id); + } + + /* Wait for threads to exit */ + while (1) { + os_thread_sleep(1000000); + os_mutex_enter(count_mutex); + if (count == 0) { + os_mutex_exit(count_mutex); + break; + } + os_mutex_exit(count_mutex); + } + + os_mutex_free(count_mutex); + ut_free(data_threads); + datafiles_iter_free(it); + + if (changed_page_bitmap) { + xb_page_bitmap_deinit(changed_page_bitmap); + } + } + + if (!backup_start()) { + exit(EXIT_FAILURE); + } + + /* read the latest checkpoint lsn */ + latest_cp = 0; + { + log_group_t* max_cp_group; + ulint max_cp_field; + ulint err; + + mutex_enter(&log_sys->mutex); + + err = recv_find_max_checkpoint(&max_cp_group, &max_cp_field); + + if (err != DB_SUCCESS) { + msg("xtrabackup: Error: recv_find_max_checkpoint() failed.\n"); + mutex_exit(&log_sys->mutex); + goto skip_last_cp; + } + + log_group_read_checkpoint_info(max_cp_group, max_cp_field); + + xtrabackup_choose_lsn_offset(checkpoint_lsn_start); + + latest_cp = mach_read_from_8(log_sys->checkpoint_buf + + LOG_CHECKPOINT_LSN); + + mutex_exit(&log_sys->mutex); + + msg("xtrabackup: The latest check point (for incremental): " + "'" LSN_PF "'\n", latest_cp); + } +skip_last_cp: + /* stop log_copying_thread */ + log_copying = FALSE; + os_event_set(log_copying_stop); + msg("xtrabackup: Stopping log copying thread.\n"); + while (log_copying_running) { + msg("."); + os_thread_sleep(200000); /*0.2 sec*/ + } + msg("\n"); + + os_event_free(log_copying_stop); + if (ds_close(dst_log_file)) { + exit(EXIT_FAILURE); + } + + if(!xtrabackup_incremental) { + strcpy(metadata_type, "full-backuped"); + metadata_from_lsn = 0; + } else { + strcpy(metadata_type, "incremental"); + metadata_from_lsn = incremental_lsn; + } + metadata_to_lsn = latest_cp; + metadata_last_lsn = log_copy_scanned_lsn; + + if (!xtrabackup_stream_metadata(ds_meta)) { + msg("xtrabackup: Error: failed to stream metadata.\n"); + exit(EXIT_FAILURE); + } + if (xtrabackup_extra_lsndir) { + char filename[FN_REFLEN]; + + sprintf(filename, "%s/%s", xtrabackup_extra_lsndir, + XTRABACKUP_METADATA_FILENAME); + if (!xtrabackup_write_metadata(filename)) { + msg("xtrabackup: Error: failed to write metadata " + "to '%s'.\n", filename); + exit(EXIT_FAILURE); + } + + } + + if (!backup_finish()) { + exit(EXIT_FAILURE); + } + + xtrabackup_destroy_datasinks(); + + if (wait_throttle) { + /* wait for io_watching_thread completion */ + while (io_watching_thread_running) { + os_thread_sleep(1000000); + } + os_event_free(wait_throttle); + wait_throttle = NULL; + } + + msg("xtrabackup: Transaction log of lsn (" LSN_PF ") to (" LSN_PF + ") was copied.\n", checkpoint_lsn_start, log_copy_scanned_lsn); + xb_filters_free(); + + xb_data_files_close(); + + /* Make sure that the latest checkpoint made it to xtrabackup_logfile */ + if (latest_cp > log_copy_scanned_lsn) { + msg("xtrabackup: error: last checkpoint LSN (" LSN_PF + ") is larger than last copied LSN (" LSN_PF ").\n", + latest_cp, log_copy_scanned_lsn); + exit(EXIT_FAILURE); + } +} + +/* ================= stats ================= */ +static my_bool +xtrabackup_stats_level( + dict_index_t* index, + ulint level) +{ + ulint space; + page_t* page; + + rec_t* node_ptr; + + ulint right_page_no; + + page_cur_t cursor; + + mtr_t mtr; + mem_heap_t* heap = mem_heap_create(256); + + ulint* offsets = NULL; + + ulonglong n_pages, n_pages_extern; + ulonglong sum_data, sum_data_extern; + ulonglong n_recs; + ulint page_size; + buf_block_t* block; + ulint zip_size; + + n_pages = sum_data = n_recs = 0; + n_pages_extern = sum_data_extern = 0; + + + if (level == 0) + fprintf(stdout, " leaf pages: "); + else + fprintf(stdout, " level %lu pages: ", level); + + mtr_start(&mtr); + + mtr_x_lock(&(index->lock), &mtr); + block = btr_root_block_get(index, RW_X_LATCH, &mtr); + page = buf_block_get_frame(block); + + space = page_get_space_id(page); + zip_size = fil_space_get_zip_size(space); + + while (level != btr_page_get_level(page, &mtr)) { + + ut_a(space == buf_block_get_space(block)); + ut_a(space == page_get_space_id(page)); + ut_a(!page_is_leaf(page)); + + page_cur_set_before_first(block, &cursor); + page_cur_move_to_next(&cursor); + + node_ptr = page_cur_get_rec(&cursor); + offsets = rec_get_offsets(node_ptr, index, offsets, + ULINT_UNDEFINED, &heap); + block = btr_node_ptr_get_child(node_ptr, index, offsets, &mtr); + page = buf_block_get_frame(block); + } + +loop: + mem_heap_empty(heap); + offsets = NULL; + mtr_x_lock(&(index->lock), &mtr); + + right_page_no = btr_page_get_next(page, &mtr); + + + /*=================================*/ + //fprintf(stdout, "%lu ", (ulint) buf_frame_get_page_no(page)); + + n_pages++; + sum_data += page_get_data_size(page); + n_recs += page_get_n_recs(page); + + + if (level == 0) { + page_cur_t cur; + ulint n_fields; + ulint i; + mem_heap_t* local_heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* local_offsets = offsets_; + + *offsets_ = (sizeof offsets_) / sizeof *offsets_; + + page_cur_set_before_first(block, &cur); + page_cur_move_to_next(&cur); + + for (;;) { + if (page_cur_is_after_last(&cur)) { + break; + } + + local_offsets = rec_get_offsets(cur.rec, index, local_offsets, + ULINT_UNDEFINED, &local_heap); + n_fields = rec_offs_n_fields(local_offsets); + + for (i = 0; i < n_fields; i++) { + if (rec_offs_nth_extern(local_offsets, i)) { + page_t* local_page; + ulint space_id; + ulint page_no; + ulint offset; + byte* blob_header; + ulint part_len; + mtr_t local_mtr; + ulint local_len; + byte* data; + buf_block_t* local_block; + + data = rec_get_nth_field(cur.rec, local_offsets, i, &local_len); + + ut_a(local_len >= BTR_EXTERN_FIELD_REF_SIZE); + local_len -= BTR_EXTERN_FIELD_REF_SIZE; + + space_id = mach_read_from_4(data + local_len + BTR_EXTERN_SPACE_ID); + page_no = mach_read_from_4(data + local_len + BTR_EXTERN_PAGE_NO); + offset = mach_read_from_4(data + local_len + BTR_EXTERN_OFFSET); + + if (offset != FIL_PAGE_DATA) + msg("\nWarning: several record may share same external page.\n"); + + for (;;) { + mtr_start(&local_mtr); + + local_block = btr_block_get(space_id, zip_size, page_no, RW_S_LATCH, index, &local_mtr); + local_page = buf_block_get_frame(local_block); + blob_header = local_page + offset; +#define BTR_BLOB_HDR_PART_LEN 0 +#define BTR_BLOB_HDR_NEXT_PAGE_NO 4 + //part_len = btr_blob_get_part_len(blob_header); + part_len = mach_read_from_4(blob_header + BTR_BLOB_HDR_PART_LEN); + + //page_no = btr_blob_get_next_page_no(blob_header); + page_no = mach_read_from_4(blob_header + BTR_BLOB_HDR_NEXT_PAGE_NO); + + offset = FIL_PAGE_DATA; + + + + + /*=================================*/ + //fprintf(stdout, "[%lu] ", (ulint) buf_frame_get_page_no(page)); + + n_pages_extern++; + sum_data_extern += part_len; + + + mtr_commit(&local_mtr); + + if (page_no == FIL_NULL) + break; + } + } + } + + page_cur_move_to_next(&cur); + } + } + + + + + mtr_commit(&mtr); + if (right_page_no != FIL_NULL) { + mtr_start(&mtr); + block = btr_block_get(space, zip_size, right_page_no, + RW_X_LATCH, index, &mtr); + page = buf_block_get_frame(block); + goto loop; + } + mem_heap_free(heap); + + if (zip_size) { + page_size = zip_size; + } else { + page_size = UNIV_PAGE_SIZE; + } + + if (level == 0) + fprintf(stdout, "recs=%llu, ", n_recs); + + fprintf(stdout, "pages=%llu, data=%llu bytes, data/pages=%lld%%", + n_pages, sum_data, + ((sum_data * 100)/ page_size)/n_pages); + + + if (level == 0 && n_pages_extern) { + putc('\n', stdout); + /* also scan blob pages*/ + fprintf(stdout, " external pages: "); + + fprintf(stdout, "pages=%llu, data=%llu bytes, data/pages=%lld%%", + n_pages_extern, sum_data_extern, + ((sum_data_extern * 100)/ page_size)/n_pages_extern); + } + + putc('\n', stdout); + + if (level > 0) { + xtrabackup_stats_level(index, level - 1); + } + + return(TRUE); +} + +static void +xtrabackup_stats_func(int argc, char **argv) +{ + ulint n; + + /* cd to datadir */ + + if (my_setwd(mysql_real_data_home,MYF(MY_WME))) + { + msg("xtrabackup: cannot my_setwd %s\n", mysql_real_data_home); + exit(EXIT_FAILURE); + } + msg("xtrabackup: cd to %s\n", mysql_real_data_home); + encryption_plugin_prepare_init(argc, argv); + mysql_data_home= mysql_data_home_buff; + mysql_data_home[0]=FN_CURLIB; // all paths are relative from here + mysql_data_home[1]=0; + + srv_n_purge_threads = 1; + /* set read only */ + srv_read_only_mode = TRUE; + + /* initialize components */ + if(innodb_init_param()) + exit(EXIT_FAILURE); + + /* Check if the log files have been created, otherwise innodb_init() + will crash when called with srv_read_only == TRUE */ + for (n = 0; n < srv_n_log_files; n++) { + char logname[FN_REFLEN]; + ibool exists; + os_file_type_t type; + + snprintf(logname, sizeof(logname), "%s%c%s%lu", + srv_log_group_home_dir, SRV_PATH_SEPARATOR, + "ib_logfile", (ulong) n); + srv_normalize_path_for_win(logname); + + if (!os_file_status(logname, &exists, &type) || !exists || + type != OS_FILE_TYPE_FILE) { + msg("xtrabackup: Error: " + "Cannot find log file %s.\n", logname); + msg("xtrabackup: Error: " + "to use the statistics feature, you need a " + "clean copy of the database including " + "correctly sized log files, so you need to " + "execute with --prepare twice to use this " + "functionality on a backup.\n"); + exit(EXIT_FAILURE); + } + } + + msg("xtrabackup: Starting 'read-only' InnoDB instance to gather " + "index statistics.\n" + "xtrabackup: Using %lld bytes for buffer pool (set by " + "--use-memory parameter)\n", xtrabackup_use_memory); + + if(innodb_init()) + exit(EXIT_FAILURE); + + xb_filters_init(); + + fprintf(stdout, "\n\n<INDEX STATISTICS>\n"); + + /* gather stats */ + + { + dict_table_t* sys_tables; + dict_index_t* sys_index; + dict_table_t* table; + btr_pcur_t pcur; + rec_t* rec; + byte* field; + ulint len; + mtr_t mtr; + + /* Enlarge the fatal semaphore wait timeout during the InnoDB table + monitor printout */ + + os_increment_counter_by_amount(server_mutex, + srv_fatal_semaphore_wait_threshold, + 72000); + + mutex_enter(&(dict_sys->mutex)); + + mtr_start(&mtr); + + sys_tables = dict_table_get_low("SYS_TABLES"); + sys_index = UT_LIST_GET_FIRST(sys_tables->indexes); + + btr_pcur_open_at_index_side(TRUE, sys_index, BTR_SEARCH_LEAF, &pcur, + TRUE, 0, &mtr); +loop: + btr_pcur_move_to_next_user_rec(&pcur, &mtr); + + rec = btr_pcur_get_rec(&pcur); + + if (!btr_pcur_is_on_user_rec(&pcur)) + { + /* end of index */ + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + + mutex_exit(&(dict_sys->mutex)); + + /* Restore the fatal semaphore wait timeout */ + os_increment_counter_by_amount(server_mutex, + srv_fatal_semaphore_wait_threshold, + -72000); + + goto end; + } + + field = rec_get_nth_field_old(rec, 0, &len); + + if (!rec_get_deleted_flag(rec, 0)) { + + /* We found one */ + + char* table_name = mem_strdupl((char*) field, len); + + btr_pcur_store_position(&pcur, &mtr); + + mtr_commit(&mtr); + + table = dict_table_get_low(table_name); + mem_free(table_name); + + if (table && check_if_skip_table(table->name)) + goto skip; + + + if (table == NULL) { + fputs("InnoDB: Failed to load table ", stderr); + ut_print_namel(stderr, NULL, TRUE, (char*) field, len); + putc('\n', stderr); + } else { + dict_index_t* index; + + /* The table definition was corrupt if there + is no index */ + + if (dict_table_get_first_index(table)) { + dict_stats_update_transient(table); + } + + //dict_table_print_low(table); + + index = UT_LIST_GET_FIRST(table->indexes); + while (index != NULL) { +{ + ib_int64_t n_vals; + + if (index->n_user_defined_cols > 0) { + n_vals = index->stat_n_diff_key_vals[ + index->n_user_defined_cols]; + } else { + n_vals = index->stat_n_diff_key_vals[1]; + } + + fprintf(stdout, + " table: %s, index: %s, space id: %lu, root page: %lu" + ", zip size: %lu" + "\n estimated statistics in dictionary:\n" + " key vals: %lu, leaf pages: %lu, size pages: %lu\n" + " real statistics:\n", + table->name, index->name, + (ulong) index->space, + (ulong) index->page, + (ulong) fil_space_get_zip_size(index->space), + (ulong) n_vals, + (ulong) index->stat_n_leaf_pages, + (ulong) index->stat_index_size); + + { + mtr_t local_mtr; + page_t* root; + ulint page_level; + + mtr_start(&local_mtr); + + mtr_x_lock(&(index->lock), &local_mtr); + root = btr_root_get(index, &local_mtr); + page_level = btr_page_get_level(root, &local_mtr); + + xtrabackup_stats_level(index, page_level); + + mtr_commit(&local_mtr); + } + + putc('\n', stdout); +} + index = UT_LIST_GET_NEXT(indexes, index); + } + } + +skip: + mtr_start(&mtr); + + btr_pcur_restore_position(BTR_SEARCH_LEAF, &pcur, &mtr); + } + + goto loop; + } + +end: + putc('\n', stdout); + + fflush(stdout); + + xb_filters_free(); + + /* shutdown InnoDB */ + if(innodb_end()) + exit(EXIT_FAILURE); +} + +/* ================= prepare ================= */ + +static my_bool +xtrabackup_init_temp_log(void) +{ + os_file_t src_file = XB_FILE_UNDEFINED; + char src_path[FN_REFLEN]; + char dst_path[FN_REFLEN]; + ibool success; + + ulint field; + byte* log_buf= (byte *)malloc(UNIV_PAGE_SIZE_MAX * 128); /* 2 MB */ + + ib_int64_t file_size; + + lsn_t max_no; + lsn_t max_lsn; + lsn_t checkpoint_no; + + ulint fold; + + bool checkpoint_found; + + max_no = 0; + + if (!log_buf) { + goto error; + } + + if (!xb_init_log_block_size()) { + goto error; + } + + if(!xtrabackup_incremental_dir) { + sprintf(dst_path, "%s/ib_logfile0", xtrabackup_target_dir); + sprintf(src_path, "%s/%s", xtrabackup_target_dir, + XB_LOG_FILENAME); + } else { + sprintf(dst_path, "%s/ib_logfile0", xtrabackup_incremental_dir); + sprintf(src_path, "%s/%s", xtrabackup_incremental_dir, + XB_LOG_FILENAME); + } + + srv_normalize_path_for_win(dst_path); + srv_normalize_path_for_win(src_path); +retry: + src_file = os_file_create_simple_no_error_handling(0, src_path, + OS_FILE_OPEN, + OS_FILE_READ_WRITE, + &success,0); + if (!success) { + /* The following call prints an error message */ + os_file_get_last_error(TRUE); + + msg("xtrabackup: Warning: cannot open %s. will try to find.\n", + src_path); + + /* check if ib_logfile0 may be xtrabackup_logfile */ + src_file = os_file_create_simple_no_error_handling(0, dst_path, + OS_FILE_OPEN, + OS_FILE_READ_WRITE, + &success,0); + if (!success) { + os_file_get_last_error(TRUE); + msg(" xtrabackup: Fatal error: cannot find %s.\n", + src_path); + + goto error; + } + + success = os_file_read(src_file, log_buf, 0, + LOG_FILE_HDR_SIZE); + if (!success) { + goto error; + } + + if ( ut_memcmp(log_buf + LOG_FILE_WAS_CREATED_BY_HOT_BACKUP, + (byte*)"xtrabkup", (sizeof "xtrabkup") - 1) == 0) { + msg(" xtrabackup: 'ib_logfile0' seems to be " + "'xtrabackup_logfile'. will retry.\n"); + + os_file_close(src_file); + src_file = XB_FILE_UNDEFINED; + + /* rename and try again */ + success = os_file_rename(0, dst_path, src_path); + if (!success) { + goto error; + } + + goto retry; + } + + msg(" xtrabackup: Fatal error: cannot find %s.\n", + src_path); + + os_file_close(src_file); + src_file = XB_FILE_UNDEFINED; + + goto error; + } + + file_size = os_file_get_size(src_file); + + + /* TODO: We should skip the following modifies, if it is not the first time. */ + + /* read log file header */ + success = os_file_read(src_file, log_buf, 0, LOG_FILE_HDR_SIZE); + if (!success) { + goto error; + } + + if ( ut_memcmp(log_buf + LOG_FILE_WAS_CREATED_BY_HOT_BACKUP, + (byte*)"xtrabkup", (sizeof "xtrabkup") - 1) != 0 ) { + msg("xtrabackup: notice: xtrabackup_logfile was already used " + "to '--prepare'.\n"); + goto skip_modify; + } else { + /* clear it later */ + //memset(log_buf + LOG_FILE_WAS_CREATED_BY_HOT_BACKUP, + // ' ', 4); + } + + checkpoint_found = false; + + /* read last checkpoint lsn */ + for (field = LOG_CHECKPOINT_1; field <= LOG_CHECKPOINT_2; + field += LOG_CHECKPOINT_2 - LOG_CHECKPOINT_1) { + if (!recv_check_cp_is_consistent(const_cast<const byte *> + (log_buf + field))) + goto not_consistent; + + checkpoint_no = mach_read_from_8(log_buf + field + + LOG_CHECKPOINT_NO); + + if (checkpoint_no >= max_no) { + + max_no = checkpoint_no; + max_lsn = mach_read_from_8(log_buf + field + + LOG_CHECKPOINT_LSN); + checkpoint_found = true; + } +not_consistent: + ; + } + + if (!checkpoint_found) { + msg("xtrabackup: No valid checkpoint found.\n"); + goto error; + } + + + /* It seems to be needed to overwrite the both checkpoint area. */ + mach_write_to_8(log_buf + LOG_CHECKPOINT_1 + LOG_CHECKPOINT_LSN, + max_lsn); + mach_write_to_4(log_buf + LOG_CHECKPOINT_1 + + LOG_CHECKPOINT_OFFSET_LOW32, + LOG_FILE_HDR_SIZE + + (ulint)(max_lsn - + ut_uint64_align_down(max_lsn, + OS_FILE_LOG_BLOCK_SIZE))); + mach_write_to_4(log_buf + LOG_CHECKPOINT_1 + + LOG_CHECKPOINT_OFFSET_HIGH32, 0); + fold = ut_fold_binary(log_buf + LOG_CHECKPOINT_1, LOG_CHECKPOINT_CHECKSUM_1); + mach_write_to_4(log_buf + LOG_CHECKPOINT_1 + LOG_CHECKPOINT_CHECKSUM_1, fold); + + fold = ut_fold_binary(log_buf + LOG_CHECKPOINT_1 + LOG_CHECKPOINT_LSN, + LOG_CHECKPOINT_CHECKSUM_2 - LOG_CHECKPOINT_LSN); + mach_write_to_4(log_buf + LOG_CHECKPOINT_1 + LOG_CHECKPOINT_CHECKSUM_2, fold); + + mach_write_to_8(log_buf + LOG_CHECKPOINT_2 + LOG_CHECKPOINT_LSN, + max_lsn); + mach_write_to_4(log_buf + LOG_CHECKPOINT_2 + + LOG_CHECKPOINT_OFFSET_LOW32, + LOG_FILE_HDR_SIZE + + (ulint)(max_lsn - + ut_uint64_align_down(max_lsn, + OS_FILE_LOG_BLOCK_SIZE))); + mach_write_to_4(log_buf + LOG_CHECKPOINT_2 + + LOG_CHECKPOINT_OFFSET_HIGH32, 0); + fold = ut_fold_binary(log_buf + LOG_CHECKPOINT_2, LOG_CHECKPOINT_CHECKSUM_1); + mach_write_to_4(log_buf + LOG_CHECKPOINT_2 + LOG_CHECKPOINT_CHECKSUM_1, fold); + + fold = ut_fold_binary(log_buf + LOG_CHECKPOINT_2 + LOG_CHECKPOINT_LSN, + LOG_CHECKPOINT_CHECKSUM_2 - LOG_CHECKPOINT_LSN); + mach_write_to_4(log_buf + LOG_CHECKPOINT_2 + LOG_CHECKPOINT_CHECKSUM_2, fold); + + + success = os_file_write(src_path, src_file, log_buf, 0, + LOG_FILE_HDR_SIZE); + if (!success) { + goto error; + } + + /* expand file size (9/8) and align to UNIV_PAGE_SIZE_MAX */ + + if (file_size % UNIV_PAGE_SIZE_MAX) { + memset(log_buf, 0, UNIV_PAGE_SIZE_MAX); + success = os_file_write(src_path, src_file, log_buf, + file_size, + UNIV_PAGE_SIZE_MAX + - (ulint) (file_size + % UNIV_PAGE_SIZE_MAX)); + if (!success) { + goto error; + } + + file_size = os_file_get_size(src_file); + } + + /* TODO: We should judge whether the file is already expanded or not... */ + { + ulint expand; + + memset(log_buf, 0, UNIV_PAGE_SIZE_MAX * 128); + expand = (ulint) (file_size / UNIV_PAGE_SIZE_MAX / 8); + + for (; expand > 128; expand -= 128) { + success = os_file_write(src_path, src_file, log_buf, + file_size, + UNIV_PAGE_SIZE_MAX * 128); + if (!success) { + goto error; + } + file_size += UNIV_PAGE_SIZE_MAX * 128; + } + + if (expand) { + success = os_file_write(src_path, src_file, log_buf, + file_size, + expand * UNIV_PAGE_SIZE_MAX); + if (!success) { + goto error; + } + file_size += UNIV_PAGE_SIZE_MAX * expand; + } + } + + /* make larger than 2MB */ + if (file_size < 2*1024*1024L) { + memset(log_buf, 0, UNIV_PAGE_SIZE_MAX); + while (file_size < 2*1024*1024L) { + success = os_file_write(src_path, src_file, log_buf, + file_size, + UNIV_PAGE_SIZE_MAX); + if (!success) { + goto error; + } + file_size += UNIV_PAGE_SIZE_MAX; + } + file_size = os_file_get_size(src_file); + } + + msg("xtrabackup: xtrabackup_logfile detected: size=" INT64PF ", " + "start_lsn=(" LSN_PF ")\n", file_size, max_lsn); + + os_file_close(src_file); + src_file = XB_FILE_UNDEFINED; + + /* fake InnoDB */ + innobase_log_files_in_group_save = innobase_log_files_in_group; + srv_log_group_home_dir_save = srv_log_group_home_dir; + innobase_log_file_size_save = innobase_log_file_size; + + srv_log_group_home_dir = NULL; + innobase_log_file_size = file_size; + innobase_log_files_in_group = 1; + + srv_thread_concurrency = 0; + + /* rename 'xtrabackup_logfile' to 'ib_logfile0' */ + success = os_file_rename(0, src_path, dst_path); + if (!success) { + goto error; + } + xtrabackup_logfile_is_renamed = TRUE; + free(log_buf); + return(FALSE); + +skip_modify: + free(log_buf); + os_file_close(src_file); + src_file = XB_FILE_UNDEFINED; + return(FALSE); + +error: + free(log_buf); + if (src_file != XB_FILE_UNDEFINED) + os_file_close(src_file); + msg("xtrabackup: Error: xtrabackup_init_temp_log() failed.\n"); + return(TRUE); /*ERROR*/ +} + +/*********************************************************************** +Generates path to the meta file path from a given path to an incremental .delta +by replacing trailing ".delta" with ".meta", or returns error if 'delta_path' +does not end with the ".delta" character sequence. +@return TRUE on success, FALSE on error. */ +static +ibool +get_meta_path( + const char *delta_path, /* in: path to a .delta file */ + char *meta_path) /* out: path to the corresponding .meta + file */ +{ + size_t len = strlen(delta_path); + + if (len <= 6 || strcmp(delta_path + len - 6, ".delta")) { + return FALSE; + } + memcpy(meta_path, delta_path, len - 6); + strcpy(meta_path + len - 6, XB_DELTA_INFO_SUFFIX); + + return TRUE; +} + +/****************************************************************//** +Create a new tablespace on disk and return the handle to its opened +file. Code adopted from fil_create_new_single_table_tablespace with +the main difference that only disk file is created without updating +the InnoDB in-memory dictionary data structures. + +@return TRUE on success, FALSE on error. */ +static +ibool +xb_space_create_file( +/*==================*/ + const char* path, /*!<in: path to tablespace */ + ulint space_id, /*!<in: space id */ + ulint flags __attribute__((unused)),/*!<in: tablespace + flags */ + os_file_t* file) /*!<out: file handle */ +{ + ibool ret; + byte* buf; + byte* page; + + *file = os_file_create_simple_no_error_handling(0, path, OS_FILE_CREATE, + OS_FILE_READ_WRITE, + &ret,0); + if (!ret) { + msg("xtrabackup: cannot create file %s\n", path); + return ret; + } + + ret = os_file_set_size(path, *file, + FIL_IBD_FILE_INITIAL_SIZE * UNIV_PAGE_SIZE); + if (!ret) { + msg("xtrabackup: cannot set size for file %s\n", path); + os_file_close(*file); + os_file_delete(0, path); + return ret; + } + + buf = static_cast<byte *>(ut_malloc(3 * UNIV_PAGE_SIZE)); + /* Align the memory for file i/o if we might have O_DIRECT set */ + page = static_cast<byte *>(ut_align(buf, UNIV_PAGE_SIZE)); + + memset(page, '\0', UNIV_PAGE_SIZE); + + fsp_header_init_fields(page, space_id, flags); + mach_write_to_4(page + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID, space_id); + + if (!fsp_flags_is_compressed(flags)) { + buf_flush_init_for_writing(page, NULL, 0); + + ret = os_file_write(path, *file, page, 0, UNIV_PAGE_SIZE); + } + else { + page_zip_des_t page_zip; + ulint zip_size; + + zip_size = fsp_flags_get_zip_size(flags); + page_zip_set_size(&page_zip, zip_size); + page_zip.data = page + UNIV_PAGE_SIZE; + fprintf(stderr, "zip_size = %lu\n", zip_size); + +#ifdef UNIV_DEBUG + page_zip.m_start = +#endif /* UNIV_DEBUG */ + page_zip.m_end = page_zip.m_nonempty = + page_zip.n_blobs = 0; + + buf_flush_init_for_writing(page, &page_zip, 0); + + ret = os_file_write(path, *file, page_zip.data, 0, + zip_size); + } + + ut_free(buf); + + if (!ret) { + msg("xtrabackup: could not write the first page to %s\n", + path); + os_file_close(*file); + os_file_delete(0, path); + return ret; + } + + return TRUE; +} + +/*********************************************************************** +Searches for matching tablespace file for given .delta file and space_id +in given directory. When matching tablespace found, renames it to match the +name of .delta file. If there was a tablespace with matching name and +mismatching ID, renames it to xtrabackup_tmp_#ID.ibd. If there was no +matching file, creates a new tablespace. +@return file handle of matched or created file */ +static +os_file_t +xb_delta_open_matching_space( + const char* dbname, /* in: path to destination database dir */ + const char* name, /* in: name of delta file (without .delta) */ + ulint space_id, /* in: space id of delta file */ + ulint zip_size, /* in: zip_size of tablespace */ + char* real_name, /* out: full path of destination file */ + size_t real_name_len, /* out: buffer size for real_name */ + ibool* success) /* out: indicates error. TRUE = success */ +{ + char dest_dir[FN_REFLEN]; + char dest_space_name[FN_REFLEN]; + ibool ok; + fil_space_t* fil_space; + os_file_t file = 0; + ulint tablespace_flags; + xb_filter_entry_t* table; + + ut_a(dbname != NULL || + !fil_is_user_tablespace_id(space_id) || + space_id == ULINT_UNDEFINED); + + *success = FALSE; + + if (dbname) { + snprintf(dest_dir, FN_REFLEN, "%s/%s", + xtrabackup_target_dir, dbname); + srv_normalize_path_for_win(dest_dir); + + snprintf(dest_space_name, FN_REFLEN, "%s/%s", dbname, name); + } else { + snprintf(dest_dir, FN_REFLEN, "%s", xtrabackup_target_dir); + srv_normalize_path_for_win(dest_dir); + + snprintf(dest_space_name, FN_REFLEN, "%s", name); + } + + snprintf(real_name, real_name_len, + "%s/%s", + xtrabackup_target_dir, dest_space_name); + srv_normalize_path_for_win(real_name); + /* Truncate ".ibd" */ + dest_space_name[strlen(dest_space_name) - 4] = '\0'; + + /* Create the database directory if it doesn't exist yet */ + if (!os_file_create_directory(dest_dir, FALSE)) { + msg("xtrabackup: error: cannot create dir %s\n", dest_dir); + return file; + } + + if (!fil_is_user_tablespace_id(space_id)) { + goto found; + } + + /* remember space name for further reference */ + table = static_cast<xb_filter_entry_t *> + (ut_malloc(sizeof(xb_filter_entry_t) + + strlen(dest_space_name) + 1)); + + table->name = ((char*)table) + sizeof(xb_filter_entry_t); + strcpy(table->name, dest_space_name); + HASH_INSERT(xb_filter_entry_t, name_hash, inc_dir_tables_hash, + ut_fold_string(table->name), table); + + mutex_enter(&fil_system->mutex); + fil_space = fil_space_get_by_name(dest_space_name); + mutex_exit(&fil_system->mutex); + + if (fil_space != NULL) { + if (fil_space->id == space_id || space_id == ULINT_UNDEFINED) { + /* we found matching space */ + goto found; + } else { + + char tmpname[FN_REFLEN]; + + snprintf(tmpname, FN_REFLEN, "%s/xtrabackup_tmp_#%lu", + dbname, fil_space->id); + + msg("xtrabackup: Renaming %s to %s.ibd\n", + fil_space->name, tmpname); + + if (!fil_rename_tablespace(NULL, fil_space->id, + tmpname, NULL)) + { + msg("xtrabackup: Cannot rename %s to %s\n", + fil_space->name, tmpname); + goto exit; + } + } + } + + if (space_id == ULINT_UNDEFINED) + { + msg("xtrabackup: Error: Cannot handle DDL operation on tablespace " + "%s\n", dest_space_name); + exit(EXIT_FAILURE); + } + mutex_enter(&fil_system->mutex); + fil_space = fil_space_get_by_id(space_id); + mutex_exit(&fil_system->mutex); + if (fil_space != NULL) { + char tmpname[FN_REFLEN]; + + strncpy(tmpname, dest_space_name, FN_REFLEN); + + msg("xtrabackup: Renaming %s to %s\n", + fil_space->name, dest_space_name); + + if (!fil_rename_tablespace(NULL, fil_space->id, tmpname, + NULL)) + { + msg("xtrabackup: Cannot rename %s to %s\n", + fil_space->name, dest_space_name); + goto exit; + } + + goto found; + } + + /* No matching space found. create the new one. */ + + if (!fil_space_create(dest_space_name, space_id, 0, + FIL_TABLESPACE, 0, false)) { + msg("xtrabackup: Cannot create tablespace %s\n", + dest_space_name); + goto exit; + } + + /* Calculate correct tablespace flags for compressed tablespaces. */ + if (!zip_size || zip_size == ULINT_UNDEFINED) { + tablespace_flags = 0; + } + else { + tablespace_flags + = (get_bit_shift(zip_size >> PAGE_ZIP_MIN_SIZE_SHIFT + << 1) + << DICT_TF_ZSSIZE_SHIFT) + | DICT_TF_COMPACT + | (DICT_TF_FORMAT_ZIP << DICT_TF_FORMAT_SHIFT); + ut_a(dict_tf_get_zip_size(tablespace_flags) + == zip_size); + } + *success = xb_space_create_file(real_name, space_id, tablespace_flags, + &file); + goto exit; + +found: + /* open the file and return it's handle */ + + file = os_file_create_simple_no_error_handling(0, real_name, + OS_FILE_OPEN, + OS_FILE_READ_WRITE, + &ok,0); + + if (ok) { + *success = TRUE; + } else { + msg("xtrabackup: Cannot open file %s\n", real_name); + } + +exit: + + return file; +} + +/************************************************************************ +Applies a given .delta file to the corresponding data file. +@return TRUE on success */ +static +ibool +xtrabackup_apply_delta( + const char* dirname, /* in: dir name of incremental */ + const char* dbname, /* in: database name (ibdata: NULL) */ + const char* filename, /* in: file name (not a path), + including the .delta extension */ + void* /*data*/) +{ + os_file_t src_file = XB_FILE_UNDEFINED; + os_file_t dst_file = XB_FILE_UNDEFINED; + char src_path[FN_REFLEN]; + char dst_path[FN_REFLEN]; + char meta_path[FN_REFLEN]; + char space_name[FN_REFLEN]; + ibool success; + + ibool last_buffer = FALSE; + ulint page_in_buffer; + ulint incremental_buffers = 0; + + xb_delta_info_t info; + ulint page_size; + ulint page_size_shift; + byte* incremental_buffer_base = NULL; + byte* incremental_buffer; + + size_t offset; + + ut_a(xtrabackup_incremental); + + if (dbname) { + snprintf(src_path, sizeof(src_path), "%s/%s/%s", + dirname, dbname, filename); + snprintf(dst_path, sizeof(dst_path), "%s/%s/%s", + xtrabackup_real_target_dir, dbname, filename); + } else { + snprintf(src_path, sizeof(src_path), "%s/%s", + dirname, filename); + snprintf(dst_path, sizeof(dst_path), "%s/%s", + xtrabackup_real_target_dir, filename); + } + dst_path[strlen(dst_path) - 6] = '\0'; + + strncpy(space_name, filename, FN_REFLEN); + space_name[strlen(space_name) - 6] = 0; + + if (!get_meta_path(src_path, meta_path)) { + goto error; + } + + srv_normalize_path_for_win(dst_path); + srv_normalize_path_for_win(src_path); + srv_normalize_path_for_win(meta_path); + + if (!xb_read_delta_metadata(meta_path, &info)) { + goto error; + } + + page_size = info.page_size; + page_size_shift = get_bit_shift(page_size); + msg("xtrabackup: page size for %s is %lu bytes\n", + src_path, page_size); + if (page_size_shift < 10 || + page_size_shift > UNIV_PAGE_SIZE_SHIFT_MAX) { + msg("xtrabackup: error: invalid value of page_size " + "(%lu bytes) read from %s\n", page_size, meta_path); + goto error; + } + + src_file = os_file_create_simple_no_error_handling(0, src_path, + OS_FILE_OPEN, + OS_FILE_READ_WRITE, + &success,0); + if (!success) { + os_file_get_last_error(TRUE); + msg("xtrabackup: error: cannot open %s\n", src_path); + goto error; + } + + posix_fadvise(src_file, 0, 0, POSIX_FADV_SEQUENTIAL); + + os_file_set_nocache(src_file, src_path, "OPEN"); + + dst_file = xb_delta_open_matching_space( + dbname, space_name, info.space_id, info.zip_size, + dst_path, sizeof(dst_path), &success); + if (!success) { + msg("xtrabackup: error: cannot open %s\n", dst_path); + goto error; + } + + posix_fadvise(dst_file, 0, 0, POSIX_FADV_DONTNEED); + + os_file_set_nocache(dst_file, dst_path, "OPEN"); + + /* allocate buffer for incremental backup (4096 pages) */ + incremental_buffer_base = static_cast<byte *> + (ut_malloc((page_size / 4 + 1) * + page_size)); + incremental_buffer = static_cast<byte *> + (ut_align(incremental_buffer_base, + page_size)); + + msg("Applying %s to %s...\n", src_path, dst_path); + + while (!last_buffer) { + ulint cluster_header; + + /* read to buffer */ + /* first block of block cluster */ + offset = ((incremental_buffers * (page_size / 4)) + << page_size_shift); + success = os_file_read(src_file, incremental_buffer, + offset, page_size); + if (!success) { + goto error; + } + + cluster_header = mach_read_from_4(incremental_buffer); + switch(cluster_header) { + case 0x78747261UL: /*"xtra"*/ + break; + case 0x58545241UL: /*"XTRA"*/ + last_buffer = TRUE; + break; + default: + msg("xtrabackup: error: %s seems not " + ".delta file.\n", src_path); + goto error; + } + + for (page_in_buffer = 1; page_in_buffer < page_size / 4; + page_in_buffer++) { + if (mach_read_from_4(incremental_buffer + page_in_buffer * 4) + == 0xFFFFFFFFUL) + break; + } + + ut_a(last_buffer || page_in_buffer == page_size / 4); + + /* read whole of the cluster */ + success = os_file_read(src_file, incremental_buffer, + offset, page_in_buffer * page_size); + if (!success) { + goto error; + } + + posix_fadvise(src_file, offset, page_in_buffer * page_size, + POSIX_FADV_DONTNEED); + + for (page_in_buffer = 1; page_in_buffer < page_size / 4; + page_in_buffer++) { + ulint offset_on_page; + + offset_on_page = mach_read_from_4(incremental_buffer + page_in_buffer * 4); + + if (offset_on_page == 0xFFFFFFFFUL) + break; + + success = os_file_write(dst_path, dst_file, + incremental_buffer + + page_in_buffer * page_size, + (offset_on_page << + page_size_shift), + page_size); + if (!success) { + goto error; + } + } + + incremental_buffers++; + } + + if (incremental_buffer_base) + ut_free(incremental_buffer_base); + if (src_file != XB_FILE_UNDEFINED) + os_file_close(src_file); + if (dst_file != XB_FILE_UNDEFINED) + os_file_close(dst_file); + return TRUE; + +error: + if (incremental_buffer_base) + ut_free(incremental_buffer_base); + if (src_file != XB_FILE_UNDEFINED) + os_file_close(src_file); + if (dst_file != XB_FILE_UNDEFINED) + os_file_close(dst_file); + msg("xtrabackup: Error: xtrabackup_apply_delta(): " + "failed to apply %s to %s.\n", src_path, dst_path); + return FALSE; +} + +/************************************************************************ +Callback to handle datadir entry. Function of this type will be called +for each entry which matches the mask by xb_process_datadir. +@return should return TRUE on success */ +typedef ibool (*handle_datadir_entry_func_t)( +/*=========================================*/ + const char* data_home_dir, /*!<in: path to datadir */ + const char* db_name, /*!<in: database name */ + const char* file_name, /*!<in: file name with suffix */ + void* arg); /*!<in: caller-provided data */ + +/************************************************************************ +Callback to handle datadir entry. Deletes entry if it has no matching +fil_space in fil_system directory. +@return FALSE if delete attempt was unsuccessful */ +static +ibool +rm_if_not_found( + const char* data_home_dir, /*!<in: path to datadir */ + const char* db_name, /*!<in: database name */ + const char* file_name, /*!<in: file name with suffix */ + void* arg __attribute__((unused))) +{ + char name[FN_REFLEN]; + xb_filter_entry_t* table; + + snprintf(name, FN_REFLEN, "%s/%s", db_name, file_name); + /* Truncate ".ibd" */ + name[strlen(name) - 4] = '\0'; + + HASH_SEARCH(name_hash, inc_dir_tables_hash, ut_fold_string(name), + xb_filter_entry_t*, + table, (void) 0, + !strcmp(table->name, name)); + + if (!table) { + snprintf(name, FN_REFLEN, "%s/%s/%s", data_home_dir, + db_name, file_name); + return os_file_delete(0, name); + } + + return(TRUE); +} + +/************************************************************************ +Function enumerates files in datadir (provided by path) which are matched +by provided suffix. For each entry callback is called. +@return FALSE if callback for some entry returned FALSE */ +static +ibool +xb_process_datadir( + const char* path, /*!<in: datadir path */ + const char* suffix, /*!<in: suffix to match + against */ + handle_datadir_entry_func_t func, /*!<in: callback */ + void* data) /*!<in: additional argument for + callback */ +{ + ulint ret; + char dbpath[FN_REFLEN]; + os_file_dir_t dir; + os_file_dir_t dbdir; + os_file_stat_t dbinfo; + os_file_stat_t fileinfo; + ulint suffix_len; + dberr_t err = DB_SUCCESS; + static char current_dir[2]; + + current_dir[0] = FN_CURLIB; + current_dir[1] = 0; + srv_data_home = current_dir; + + suffix_len = strlen(suffix); + + /* datafile */ + dbdir = os_file_opendir(path, FALSE); + + if (dbdir != NULL) { + ret = fil_file_readdir_next_file(&err, path, dbdir, + &fileinfo); + while (ret == 0) { + if (fileinfo.type == OS_FILE_TYPE_DIR) { + goto next_file_item_1; + } + + if (strlen(fileinfo.name) > suffix_len + && 0 == strcmp(fileinfo.name + + strlen(fileinfo.name) - suffix_len, + suffix)) { + if (!func( + path, NULL, + fileinfo.name, data)) + { + return(FALSE); + } + } +next_file_item_1: + ret = fil_file_readdir_next_file(&err, + path, dbdir, + &fileinfo); + } + + os_file_closedir(dbdir); + } else { + msg("xtrabackup: Cannot open dir %s\n", + path); + } + + /* single table tablespaces */ + dir = os_file_opendir(path, FALSE); + + if (dir == NULL) { + msg("xtrabackup: Cannot open dir %s\n", + path); + } + + ret = fil_file_readdir_next_file(&err, path, dir, + &dbinfo); + while (ret == 0) { + if (dbinfo.type == OS_FILE_TYPE_FILE + || dbinfo.type == OS_FILE_TYPE_UNKNOWN) { + + goto next_datadir_item; + } + + sprintf(dbpath, "%s/%s", path, + dbinfo.name); + srv_normalize_path_for_win(dbpath); + + dbdir = os_file_opendir(dbpath, FALSE); + + if (dbdir != NULL) { + + ret = fil_file_readdir_next_file(&err, dbpath, dbdir, + &fileinfo); + while (ret == 0) { + + if (fileinfo.type == OS_FILE_TYPE_DIR) { + + goto next_file_item_2; + } + + if (strlen(fileinfo.name) > suffix_len + && 0 == strcmp(fileinfo.name + + strlen(fileinfo.name) - + suffix_len, + suffix)) { + /* The name ends in suffix; process + the file */ + if (!func( + path, + dbinfo.name, + fileinfo.name, data)) + { + return(FALSE); + } + } +next_file_item_2: + ret = fil_file_readdir_next_file(&err, + dbpath, dbdir, + &fileinfo); + } + + os_file_closedir(dbdir); + } +next_datadir_item: + ret = fil_file_readdir_next_file(&err, + path, + dir, &dbinfo); + } + + os_file_closedir(dir); + + return(TRUE); +} + +/************************************************************************ +Applies all .delta files from incremental_dir to the full backup. +@return TRUE on success. */ +static +ibool +xtrabackup_apply_deltas() +{ + return xb_process_datadir(xtrabackup_incremental_dir, ".delta", + xtrabackup_apply_delta, NULL); +} + +static my_bool +xtrabackup_close_temp_log(my_bool clear_flag) +{ + os_file_t src_file = XB_FILE_UNDEFINED; + char src_path[FN_REFLEN]; + char dst_path[FN_REFLEN]; + ibool success; + byte log_buf[UNIV_PAGE_SIZE_MAX]; + + if (!xtrabackup_logfile_is_renamed) + return(FALSE); + + /* rename 'ib_logfile0' to 'xtrabackup_logfile' */ + if(!xtrabackup_incremental_dir) { + sprintf(dst_path, "%s/ib_logfile0", xtrabackup_target_dir); + sprintf(src_path, "%s/%s", xtrabackup_target_dir, + XB_LOG_FILENAME); + } else { + sprintf(dst_path, "%s/ib_logfile0", xtrabackup_incremental_dir); + sprintf(src_path, "%s/%s", xtrabackup_incremental_dir, + XB_LOG_FILENAME); + } + + srv_normalize_path_for_win(dst_path); + srv_normalize_path_for_win(src_path); + + success = os_file_rename(0, dst_path, src_path); + if (!success) { + goto error; + } + xtrabackup_logfile_is_renamed = FALSE; + + if (!clear_flag) + return(FALSE); + + /* clear LOG_FILE_WAS_CREATED_BY_HOT_BACKUP field */ + src_file = os_file_create_simple_no_error_handling(0, src_path, + OS_FILE_OPEN, + OS_FILE_READ_WRITE, + &success,0); + if (!success) { + goto error; + } + + success = os_file_read(src_file, log_buf, 0, LOG_FILE_HDR_SIZE); + if (!success) { + goto error; + } + + memset(log_buf + LOG_FILE_WAS_CREATED_BY_HOT_BACKUP, ' ', 4); + + success = os_file_write(src_path, src_file, log_buf, 0, + LOG_FILE_HDR_SIZE); + if (!success) { + goto error; + } + + os_file_close(src_file); + src_file = XB_FILE_UNDEFINED; + + innobase_log_files_in_group = innobase_log_files_in_group_save; + srv_log_group_home_dir = srv_log_group_home_dir_save; + innobase_log_file_size = innobase_log_file_size_save; + + return(FALSE); +error: + if (src_file != XB_FILE_UNDEFINED) + os_file_close(src_file); + msg("xtrabackup: Error: xtrabackup_close_temp_log() failed.\n"); + return(TRUE); /*ERROR*/ +} + + +/*********************************************************************//** +Write the meta data (index user fields) config file. +@return true in case of success otherwise false. */ +static +bool +xb_export_cfg_write_index_fields( +/*===========================*/ + const dict_index_t* index, /*!< in: write the meta data for + this index */ + FILE* file) /*!< in: file to write to */ +{ + byte row[sizeof(ib_uint32_t) * 2]; + + for (ulint i = 0; i < index->n_fields; ++i) { + byte* ptr = row; + const dict_field_t* field = &index->fields[i]; + + mach_write_to_4(ptr, field->prefix_len); + ptr += sizeof(ib_uint32_t); + + mach_write_to_4(ptr, field->fixed_len); + + if (fwrite(row, 1, sizeof(row), file) != sizeof(row)) { + + msg("xtrabackup: Error: writing index fields."); + + return(false); + } + + /* Include the NUL byte in the length. */ + ib_uint32_t len = (ib_uint32_t)strlen(field->name) + 1; + ut_a(len > 1); + + mach_write_to_4(row, len); + + if (fwrite(row, 1, sizeof(len), file) != sizeof(len) + || fwrite(field->name, 1, len, file) != len) { + + msg("xtrabackup: Error: writing index column."); + + return(false); + } + } + + return(true); +} + +/*********************************************************************//** +Write the meta data config file index information. +@return true in case of success otherwise false. */ +static __attribute__((nonnull, warn_unused_result)) +bool +xb_export_cfg_write_indexes( +/*======================*/ + const dict_table_t* table, /*!< in: write the meta data for + this table */ + FILE* file) /*!< in: file to write to */ +{ + { + byte row[sizeof(ib_uint32_t)]; + + /* Write the number of indexes in the table. */ + mach_write_to_4(row, UT_LIST_GET_LEN(table->indexes)); + + if (fwrite(row, 1, sizeof(row), file) != sizeof(row)) { + msg("xtrabackup: Error: writing index count."); + + return(false); + } + } + + bool ret = true; + + /* Write the index meta data. */ + for (const dict_index_t* index = UT_LIST_GET_FIRST(table->indexes); + index != 0 && ret; + index = UT_LIST_GET_NEXT(indexes, index)) { + + byte* ptr; + byte row[sizeof(ib_uint64_t) + + sizeof(ib_uint32_t) * 8]; + + ptr = row; + + ut_ad(sizeof(ib_uint64_t) == 8); + mach_write_to_8(ptr, index->id); + ptr += sizeof(ib_uint64_t); + + mach_write_to_4(ptr, index->space); + ptr += sizeof(ib_uint32_t); + + mach_write_to_4(ptr, index->page); + ptr += sizeof(ib_uint32_t); + + mach_write_to_4(ptr, index->type); + ptr += sizeof(ib_uint32_t); + + mach_write_to_4(ptr, index->trx_id_offset); + ptr += sizeof(ib_uint32_t); + + mach_write_to_4(ptr, index->n_user_defined_cols); + ptr += sizeof(ib_uint32_t); + + mach_write_to_4(ptr, index->n_uniq); + ptr += sizeof(ib_uint32_t); + + mach_write_to_4(ptr, index->n_nullable); + ptr += sizeof(ib_uint32_t); + + mach_write_to_4(ptr, index->n_fields); + + if (fwrite(row, 1, sizeof(row), file) != sizeof(row)) { + + msg("xtrabackup: Error: writing index meta-data."); + + return(false); + } + + /* Write the length of the index name. + NUL byte is included in the length. */ + ib_uint32_t len = (ib_uint32_t)strlen(index->name) + 1; + ut_a(len > 1); + + mach_write_to_4(row, len); + + if (fwrite(row, 1, sizeof(len), file) != sizeof(len) + || fwrite(index->name, 1, len, file) != len) { + + msg("xtrabackup: Error: writing index name."); + + return(false); + } + + ret = xb_export_cfg_write_index_fields(index, file); + } + + return(ret); +} + +/*********************************************************************//** +Write the meta data (table columns) config file. Serialise the contents of +dict_col_t structure, along with the column name. All fields are serialized +as ib_uint32_t. +@return true in case of success otherwise false. */ +static __attribute__((nonnull, warn_unused_result)) +bool +xb_export_cfg_write_table( +/*====================*/ + const dict_table_t* table, /*!< in: write the meta data for + this table */ + FILE* file) /*!< in: file to write to */ +{ + dict_col_t* col; + byte row[sizeof(ib_uint32_t) * 7]; + + col = table->cols; + + for (ulint i = 0; i < table->n_cols; ++i, ++col) { + byte* ptr = row; + + mach_write_to_4(ptr, col->prtype); + ptr += sizeof(ib_uint32_t); + + mach_write_to_4(ptr, col->mtype); + ptr += sizeof(ib_uint32_t); + + mach_write_to_4(ptr, col->len); + ptr += sizeof(ib_uint32_t); + + mach_write_to_4(ptr, col->mbminmaxlen); + ptr += sizeof(ib_uint32_t); + + mach_write_to_4(ptr, col->ind); + ptr += sizeof(ib_uint32_t); + + mach_write_to_4(ptr, col->ord_part); + ptr += sizeof(ib_uint32_t); + + mach_write_to_4(ptr, col->max_prefix); + + if (fwrite(row, 1, sizeof(row), file) != sizeof(row)) { + msg("xtrabackup: Error: writing table column data."); + + return(false); + } + + /* Write out the column name as [len, byte array]. The len + includes the NUL byte. */ + ib_uint32_t len; + const char* col_name; + + col_name = dict_table_get_col_name(table, dict_col_get_no(col)); + + /* Include the NUL byte in the length. */ + len = (ib_uint32_t)strlen(col_name) + 1; + ut_a(len > 1); + + mach_write_to_4(row, len); + + if (fwrite(row, 1, sizeof(len), file) != sizeof(len) + || fwrite(col_name, 1, len, file) != len) { + + msg("xtrabackup: Error: writing column name."); + + return(false); + } + } + + return(true); +} + +/*********************************************************************//** +Write the meta data config file header. +@return true in case of success otherwise false. */ +static __attribute__((nonnull, warn_unused_result)) +bool +xb_export_cfg_write_header( +/*=====================*/ + const dict_table_t* table, /*!< in: write the meta data for + this table */ + FILE* file) /*!< in: file to write to */ +{ + byte value[sizeof(ib_uint32_t)]; + + /* Write the meta-data version number. */ + mach_write_to_4(value, IB_EXPORT_CFG_VERSION_V1); + + if (fwrite(&value, 1, sizeof(value), file) != sizeof(value)) { + msg("xtrabackup: Error: writing meta-data version number."); + + return(false); + } + + /* Write the server hostname. */ + ib_uint32_t len; + const char* hostname = "Hostname unknown"; + + /* The server hostname includes the NUL byte. */ + len = (ib_uint32_t)strlen(hostname) + 1; + mach_write_to_4(value, len); + + if (fwrite(&value, 1, sizeof(value), file) != sizeof(value) + || fwrite(hostname, 1, len, file) != len) { + + msg("xtrabackup: Error: writing hostname."); + + return(false); + } + + /* The table name includes the NUL byte. */ + ut_a(table->name != 0); + len = (ib_uint32_t)strlen(table->name) + 1; + + /* Write the table name. */ + mach_write_to_4(value, len); + + if (fwrite(&value, 1, sizeof(value), file) != sizeof(value) + || fwrite(table->name, 1, len, file) != len) { + + msg("xtrabackup: Error: writing table name."); + + return(false); + } + + byte row[sizeof(ib_uint32_t) * 3]; + + /* Write the next autoinc value. */ + mach_write_to_8(row, table->autoinc); + + if (fwrite(row, 1, sizeof(ib_uint64_t), file) != sizeof(ib_uint64_t)) { + msg("xtrabackup: Error: writing table autoinc value."); + + return(false); + } + + byte* ptr = row; + + /* Write the system page size. */ + mach_write_to_4(ptr, UNIV_PAGE_SIZE); + ptr += sizeof(ib_uint32_t); + + /* Write the table->flags. */ + mach_write_to_4(ptr, table->flags); + ptr += sizeof(ib_uint32_t); + + /* Write the number of columns in the table. */ + mach_write_to_4(ptr, table->n_cols); + + if (fwrite(row, 1, sizeof(row), file) != sizeof(row)) { + msg("xtrabackup: Error: writing table meta-data."); + + return(false); + } + + return(true); +} + +/*********************************************************************//** +Write MySQL 5.6-style meta data config file. +@return true in case of success otherwise false. */ +static +bool +xb_export_cfg_write( + const fil_node_t* node, + const dict_table_t* table) /*!< in: write the meta data for + this table */ +{ + char file_path[FN_REFLEN]; + FILE* file; + bool success; + + strcpy(file_path, node->name); + strcpy(file_path + strlen(file_path) - 4, ".cfg"); + + file = fopen(file_path, "w+b"); + + if (file == NULL) { + msg("xtrabackup: Error: cannot open %s\n", node->name); + + success = false; + } else { + + success = xb_export_cfg_write_header(table, file); + + if (success) { + success = xb_export_cfg_write_table(table, file); + } + + if (success) { + success = xb_export_cfg_write_indexes(table, file); + } + + if (fclose(file) != 0) { + msg("xtrabackup: Error: cannot close %s\n", node->name); + success = false; + } + + } + + return(success); + +} + +/********************************************************************//** +Searches archived log files in archived log directory. The min and max +LSN's of found files as well as archived log file size are stored in +xtrabackup_arch_first_file_lsn, xtrabackup_arch_last_file_lsn and +xtrabackup_arch_file_size respectively. +@return true on success +*/ +static +bool +xtrabackup_arch_search_files( +/*=========================*/ + ib_uint64_t start_lsn) /*!< in: filter out log files + witch does not contain data + with lsn < start_lsn */ +{ + os_file_dir_t dir; + os_file_stat_t fileinfo; + ut_ad(innobase_log_arch_dir); + + dir = os_file_opendir(innobase_log_arch_dir, FALSE); + if (!dir) { + msg("xtrabackup: error: cannot open archived log directory %s\n", + innobase_log_arch_dir); + return false; + } + + while(!os_file_readdir_next_file(innobase_log_arch_dir, + dir, + &fileinfo) ) { + lsn_t log_file_lsn; + char* log_str_end_lsn_ptr; + + if (strncmp(fileinfo.name, + IB_ARCHIVED_LOGS_PREFIX, + sizeof(IB_ARCHIVED_LOGS_PREFIX) - 1)) { + continue; + } + + log_file_lsn = strtoll(fileinfo.name + + sizeof(IB_ARCHIVED_LOGS_PREFIX) - 1, + &log_str_end_lsn_ptr, 10); + + if (*log_str_end_lsn_ptr) { + continue; + } + + if (log_file_lsn + (fileinfo.size - LOG_FILE_HDR_SIZE) < start_lsn) { + continue; + } + + if (!xtrabackup_arch_first_file_lsn || + log_file_lsn < xtrabackup_arch_first_file_lsn) { + xtrabackup_arch_first_file_lsn = log_file_lsn; + } + if (log_file_lsn > xtrabackup_arch_last_file_lsn) { + xtrabackup_arch_last_file_lsn = log_file_lsn; + } + + //TODO: find the more suitable way to extract archived log file + //size + if (fileinfo.size > (ib_int64_t)xtrabackup_arch_file_size) { + xtrabackup_arch_file_size = fileinfo.size; + } + } + + return xtrabackup_arch_first_file_lsn != 0; +} + +static +void +innodb_free_param() +{ + srv_free_paths_and_sizes(); + free(internal_innobase_data_file_path); + internal_innobase_data_file_path = NULL; + free_tmpdir(&mysql_tmpdir_list); +} + + +/************************************************************************** +Store the current binary log coordinates in a specified file. +@return 'false' on error. */ +static bool +store_binlog_info( +/*==============*/ + const char *filename) /*!< in: output file name */ +{ + FILE *fp; + + if (trx_sys_mysql_bin_log_name[0] == '\0') { + return(true); + } + + fp = fopen(filename, "w"); + + if (!fp) { + msg("xtrabackup: failed to open '%s'\n", filename); + return(false); + } + + fprintf(fp, "%s\t" UINT64PF "\n", + trx_sys_mysql_bin_log_name, trx_sys_mysql_bin_log_pos); + fclose(fp); + + return(true); +} + +static void +xtrabackup_prepare_func(int argc, char ** argv) +{ + ulint err; + datafiles_iter_t *it; + fil_node_t *node; + fil_space_t *space; + char metadata_path[FN_REFLEN]; + + /* cd to target-dir */ + + if (my_setwd(xtrabackup_real_target_dir,MYF(MY_WME))) + { + msg("xtrabackup: cannot my_setwd %s\n", + xtrabackup_real_target_dir); + exit(EXIT_FAILURE); + } + msg("xtrabackup: cd to %s\n", xtrabackup_real_target_dir); + + encryption_plugin_prepare_init(argc, argv); + + xtrabackup_target_dir= mysql_data_home_buff; + xtrabackup_target_dir[0]=FN_CURLIB; // all paths are relative from here + xtrabackup_target_dir[1]=0; + + /* + read metadata of target, we don't need metadata reading in the case + archived logs applying + */ + sprintf(metadata_path, "%s/%s", xtrabackup_target_dir, + XTRABACKUP_METADATA_FILENAME); + + if (!xtrabackup_read_metadata(metadata_path)) { + msg("xtrabackup: Error: failed to read metadata from '%s'\n", + metadata_path); + exit(EXIT_FAILURE); + } + + if (!innobase_log_arch_dir) + { + if (!strcmp(metadata_type, "full-backuped")) { + msg("xtrabackup: This target seems to be not prepared " + "yet.\n"); + } else if (!strcmp(metadata_type, "log-applied")) { + msg("xtrabackup: This target seems to be already " + "prepared with --apply-log-only.\n"); + goto skip_check; + } else if (!strcmp(metadata_type, "full-prepared")) { + msg("xtrabackup: This target seems to be already " + "prepared.\n"); + } else { + msg("xtrabackup: This target seems not to have correct " + "metadata...\n"); + exit(EXIT_FAILURE); + } + + if (xtrabackup_incremental) { + msg("xtrabackup: error: applying incremental backup " + "needs target prepared with --apply-log-only.\n"); + exit(EXIT_FAILURE); + } +skip_check: + if (xtrabackup_incremental + && metadata_to_lsn != incremental_lsn) { + msg("xtrabackup: error: This incremental backup seems " + "not to be proper for the target.\n" + "xtrabackup: Check 'to_lsn' of the target and " + "'from_lsn' of the incremental.\n"); + exit(EXIT_FAILURE); + } + } + + /* Create logfiles for recovery from 'xtrabackup_logfile', before start InnoDB */ + srv_max_n_threads = 1000; + srv_n_purge_threads = 1; + ut_mem_init(); + /* temporally dummy value to avoid crash */ + srv_page_size_shift = 14; + srv_page_size = (1 << srv_page_size_shift); + os_sync_init(); + sync_init(); + os_io_init_simple(); + mem_init(srv_mem_pool_size); + ut_crc32_init(); + +#ifdef WITH_INNODB_DISALLOW_WRITES + srv_allow_writes_event = os_event_create(); + os_event_set(srv_allow_writes_event); +#endif + + xb_filters_init(); + + if(!innobase_log_arch_dir && xtrabackup_init_temp_log()) + goto error_cleanup; + + if(innodb_init_param()) { + goto error_cleanup; + } + + xb_normalize_init_values(); + + if (xtrabackup_incremental || innobase_log_arch_dir) { + err = xb_data_files_init(); + if (err != DB_SUCCESS) { + msg("xtrabackup: error: xb_data_files_init() failed " + "with error code %lu\n", err); + goto error_cleanup; + } + } + if (xtrabackup_incremental) { + inc_dir_tables_hash = hash_create(1000); + + if(!xtrabackup_apply_deltas()) { + xb_data_files_close(); + xb_filter_hash_free(inc_dir_tables_hash); + goto error_cleanup; + } + } + if (xtrabackup_incremental || innobase_log_arch_dir) { + xb_data_files_close(); + } + if (xtrabackup_incremental) { + /* Cleanup datadir from tablespaces deleted between full and + incremental backups */ + + xb_process_datadir("./", ".ibd", rm_if_not_found, NULL); + + xb_filter_hash_free(inc_dir_tables_hash); + } + if (fil_system) { + fil_close(); + } + + mem_close(); + ut_free_all_mem(); + + innodb_free_param(); + sync_close(); + sync_initialized = FALSE; + + /* Reset the configuration as it might have been changed by + xb_data_files_init(). */ + if(innodb_init_param()) { + goto error_cleanup; + } + + srv_apply_log_only = (bool) xtrabackup_apply_log_only; + + /* increase IO threads */ + if(srv_n_file_io_threads < 10) { + srv_n_read_io_threads = 4; + srv_n_write_io_threads = 4; + } + + if (innobase_log_arch_dir) { + srv_arch_dir = innobase_log_arch_dir; + srv_archive_recovery = true; + if (xtrabackup_archived_to_lsn) { + if (xtrabackup_archived_to_lsn < metadata_last_lsn) { + msg("xtrabackup: warning: logs applying lsn " + "limit " UINT64PF " is " + "less than metadata last-lsn " UINT64PF + " and will be set to metadata last-lsn value\n", + xtrabackup_archived_to_lsn, + metadata_last_lsn); + xtrabackup_archived_to_lsn = metadata_last_lsn; + } + if (xtrabackup_archived_to_lsn < min_flushed_lsn) { + msg("xtrabackup: error: logs applying " + "lsn limit " UINT64PF " is less than " + "min_flushed_lsn " UINT64PF + ", there is nothing to do\n", + xtrabackup_archived_to_lsn, + min_flushed_lsn); + goto error_cleanup; + } + } + srv_archive_recovery_limit_lsn= xtrabackup_archived_to_lsn; + /* + Unfinished transactions are not rolled back during log applying + as they can be finished at the firther files applyings. + */ + xtrabackup_apply_log_only = srv_apply_log_only = true; + + if (!xtrabackup_arch_search_files(min_flushed_lsn)) { + goto error_cleanup; + } + + /* + Check if last log file last lsn is big enough to overlap + last scanned lsn read from metadata. + */ + if (xtrabackup_arch_last_file_lsn + + xtrabackup_arch_file_size - + LOG_FILE_HDR_SIZE < metadata_last_lsn) { + msg("xtrabackup: error: there are no enough archived logs " + "to apply\n"); + goto error_cleanup; + } + } + + msg("xtrabackup: Starting InnoDB instance for recovery.\n" + "xtrabackup: Using %lld bytes for buffer pool " + "(set by --use-memory parameter)\n", xtrabackup_use_memory); + + srv_max_buf_pool_modified_pct = (double)max_buf_pool_modified_pct; + + if (srv_max_dirty_pages_pct_lwm > srv_max_buf_pool_modified_pct) { + srv_max_dirty_pages_pct_lwm = srv_max_buf_pool_modified_pct; + } + + if(innodb_init()) + goto error_cleanup; + + if (xtrabackup_incremental) { + + it = datafiles_iter_new(fil_system); + if (it == NULL) { + msg("xtrabackup: Error: datafiles_iter_new() failed.\n"); + exit(EXIT_FAILURE); + } + + while ((node = datafiles_iter_next(it)) != NULL) { + byte *header; + ulint size; + ulint actual_size; + mtr_t mtr; + buf_block_t *block; + ulint flags; + + space = node->space; + + /* Align space sizes along with fsp header. We want to process + each space once, so skip all nodes except the first one in a + multi-node space. */ + if (UT_LIST_GET_PREV(chain, node) != NULL) { + continue; + } + + mtr_start(&mtr); + + mtr_s_lock(fil_space_get_latch(space->id, &flags), &mtr); + + block = buf_page_get(space->id, + dict_tf_get_zip_size(flags), + 0, RW_S_LATCH, &mtr); + header = FSP_HEADER_OFFSET + buf_block_get_frame(block); + + size = mtr_read_ulint(header + FSP_SIZE, MLOG_4BYTES, + &mtr); + + mtr_commit(&mtr); + + fil_extend_space_to_desired_size(&actual_size, space->id, size); + } + + datafiles_iter_free(it); + + } /* if (xtrabackup_incremental) */ + + if (xtrabackup_export) { + msg("xtrabackup: export option is specified.\n"); + os_file_t info_file = XB_FILE_UNDEFINED; + char info_file_path[FN_REFLEN]; + ibool success; + char table_name[FN_REFLEN]; + + byte* page; + byte* buf = NULL; + + buf = static_cast<byte *>(ut_malloc(UNIV_PAGE_SIZE * 2)); + page = static_cast<byte *>(ut_align(buf, UNIV_PAGE_SIZE)); + + /* flush insert buffer at shutdwon */ + innobase_fast_shutdown = 0; + + it = datafiles_iter_new(fil_system); + if (it == NULL) { + msg("xtrabackup: Error: datafiles_iter_new() " + "failed.\n"); + exit(EXIT_FAILURE); + } + while ((node = datafiles_iter_next(it)) != NULL) { + int len; + char *next, *prev, *p; + dict_table_t* table; + dict_index_t* index; + ulint n_index; + + space = node->space; + + /* treat file_per_table only */ + if (!fil_is_user_tablespace_id(space->id)) { + continue; + } + + /* node exist == file exist, here */ + strcpy(info_file_path, node->name); +#ifdef _WIN32 + for (int i = 0; info_file_path[i]; i++) + if (info_file_path[i] == '\\') + info_file_path[i]= '/'; +#endif + strcpy(info_file_path + + strlen(info_file_path) - + 4, ".exp"); + + len =(ib_uint32_t)strlen(info_file_path); + + p = info_file_path; + prev = NULL; + while ((next = strchr(p, '/')) != NULL) + { + prev = p; + p = next + 1; + } + info_file_path[len - 4] = 0; + strncpy(table_name, prev, FN_REFLEN); + + info_file_path[len - 4] = '.'; + + mutex_enter(&(dict_sys->mutex)); + + table = dict_table_get_low(table_name); + if (!table) { + msg("xtrabackup: error: " + "cannot find dictionary " + "record of table %s\n", + table_name); + goto next_node; + } + + /* Write MySQL 5.6 .cfg file */ + if (!xb_export_cfg_write(node, table)) { + goto next_node; + } + + index = dict_table_get_first_index(table); + n_index = UT_LIST_GET_LEN(table->indexes); + if (n_index > 31) { + msg("xtrabackup: warning: table '%s' has more " + "than 31 indexes, .exp file was not " + "generated. Table will fail to import " + "on server version prior to 5.6.\n", + table->name); + goto next_node; + } + + /* init exp file */ + memset(page, 0, UNIV_PAGE_SIZE); + mach_write_to_4(page , 0x78706f72UL); + mach_write_to_4(page + 4, 0x74696e66UL);/*"xportinf"*/ + mach_write_to_4(page + 8, n_index); + strncpy((char *) page + 12, + table_name, 500); + + msg("xtrabackup: export metadata of " + "table '%s' to file `%s` " + "(%lu indexes)\n", + table_name, info_file_path, + n_index); + + n_index = 1; + while (index) { + mach_write_to_8(page + n_index * 512, index->id); + mach_write_to_4(page + n_index * 512 + 8, + index->page); + strncpy((char *) page + n_index * 512 + + 12, index->name, 500); + + msg("xtrabackup: name=%s, " + "id.low=%lu, page=%lu\n", + index->name, + (ulint)(index->id & + 0xFFFFFFFFUL), + (ulint) index->page); + index = dict_table_get_next_index(index); + n_index++; + } + + srv_normalize_path_for_win(info_file_path); + info_file = os_file_create( + 0, + info_file_path, + OS_FILE_OVERWRITE, + OS_FILE_NORMAL, OS_DATA_FILE, + &success,0); + if (!success) { + os_file_get_last_error(TRUE); + goto next_node; + } + success = os_file_write(info_file_path, + info_file, page, + 0, UNIV_PAGE_SIZE); + if (!success) { + os_file_get_last_error(TRUE); + goto next_node; + } + success = os_file_flush(info_file); + if (!success) { + os_file_get_last_error(TRUE); + goto next_node; + } +next_node: + if (info_file != XB_FILE_UNDEFINED) { + os_file_close(info_file); + info_file = XB_FILE_UNDEFINED; + } + mutex_exit(&(dict_sys->mutex)); + } + + ut_free(buf); + } + + /* print the binary log position */ + trx_sys_print_mysql_binlog_offset(); + msg("\n"); + + /* output to xtrabackup_binlog_pos_innodb and (if + backup_safe_binlog_info was available on the server) to + xtrabackup_binlog_info. In the latter case xtrabackup_binlog_pos_innodb + becomes redundant and is created only for compatibility. */ + if (!store_binlog_info("xtrabackup_binlog_pos_innodb") || + (recover_binlog_info && + !store_binlog_info(XTRABACKUP_BINLOG_INFO))) { + + exit(EXIT_FAILURE); + } + + if (innobase_log_arch_dir) + srv_start_lsn = log_sys->lsn = recv_sys->recovered_lsn; + + /* Check whether the log is applied enough or not. */ + if ((xtrabackup_incremental + && srv_start_lsn < incremental_to_lsn) + ||(!xtrabackup_incremental + && srv_start_lsn < metadata_to_lsn)) { + msg("xtrabackup: error: " + "The transaction log file is corrupted.\n" + "xtrabackup: error: " + "The log was not applied to the intended LSN!\n"); + msg("xtrabackup: Log applied to lsn " LSN_PF "\n", + srv_start_lsn); + if (xtrabackup_incremental) { + msg("xtrabackup: The intended lsn is " LSN_PF "\n", + incremental_to_lsn); + } else { + msg("xtrabackup: The intended lsn is " LSN_PF "\n", + metadata_to_lsn); + } + exit(EXIT_FAILURE); + } +#ifdef WITH_WSREP + xb_write_galera_info(xtrabackup_incremental); +#endif + + if(innodb_end()) + goto error_cleanup; + + innodb_free_param(); + + sync_initialized = FALSE; + + /* re-init necessary components */ + ut_mem_init(); + os_sync_init(); + sync_init(); + os_io_init_simple(); + + if(xtrabackup_close_temp_log(TRUE)) + exit(EXIT_FAILURE); + + /* output to metadata file */ + { + char filename[FN_REFLEN]; + + strcpy(metadata_type, srv_apply_log_only ? + "log-applied" : "full-prepared"); + + if(xtrabackup_incremental + && metadata_to_lsn < incremental_to_lsn) + { + metadata_to_lsn = incremental_to_lsn; + metadata_last_lsn = incremental_last_lsn; + } + + sprintf(filename, "%s/%s", xtrabackup_target_dir, XTRABACKUP_METADATA_FILENAME); + if (!xtrabackup_write_metadata(filename)) { + + msg("xtrabackup: Error: failed to write metadata " + "to '%s'\n", filename); + exit(EXIT_FAILURE); + } + + if(xtrabackup_extra_lsndir) { + sprintf(filename, "%s/%s", xtrabackup_extra_lsndir, XTRABACKUP_METADATA_FILENAME); + if (!xtrabackup_write_metadata(filename)) { + msg("xtrabackup: Error: failed to write " + "metadata to '%s'\n", filename); + exit(EXIT_FAILURE); + } + } + } + + if (!apply_log_finish()) { + exit(EXIT_FAILURE); + } + + sync_close(); + sync_initialized = FALSE; + if (fil_system) { + fil_close(); + } + + ut_free_all_mem(); + + /* start InnoDB once again to create log files */ + + if (!xtrabackup_apply_log_only) { + + /* xtrabackup_incremental_dir is used to indicate that + we are going to apply incremental backup. Here we already + applied incremental backup and are about to do final prepare + of the full backup */ + xtrabackup_incremental_dir = NULL; + + if(innodb_init_param()) { + goto error; + } + + srv_apply_log_only = false; + + /* increase IO threads */ + if(srv_n_file_io_threads < 10) { + srv_n_read_io_threads = 4; + srv_n_write_io_threads = 4; + } + + srv_shutdown_state = SRV_SHUTDOWN_NONE; + + if(innodb_init()) + goto error; + + if(innodb_end()) + goto error; + + innodb_free_param(); + + } + + xb_filters_free(); + + return; + +error_cleanup: + xtrabackup_close_temp_log(FALSE); + xb_filters_free(); + +error: + exit(EXIT_FAILURE); +} + +/************************************************************************** +Append group name to xb_load_default_groups list. */ +static +void +append_defaults_group(const char *group, const char *default_groups[], + size_t default_groups_size) +{ + uint i; + bool appended = false; + for (i = 0; i < default_groups_size - 1; i++) { + if (default_groups[i] == NULL) { + default_groups[i] = group; + appended = true; + break; + } + } + ut_a(appended); +} + +bool +xb_init() +{ + const char *mixed_options[4] = {NULL, NULL, NULL, NULL}; + int n_mixed_options; + + /* sanity checks */ + + if (opt_slave_info + && opt_no_lock + && !opt_safe_slave_backup) { + msg("Error: --slave-info is used with --no-lock but " + "without --safe-slave-backup. The binlog position " + "cannot be consistent with the backup data.\n"); + return(false); + } + + if (opt_rsync && xtrabackup_stream_fmt) { + msg("Error: --rsync doesn't work with --stream\n"); + return(false); + } + + n_mixed_options = 0; + + if (opt_decompress) { + mixed_options[n_mixed_options++] = "--decompress"; + } else if (opt_decrypt) { + mixed_options[n_mixed_options++] = "--decrypt"; + } + + if (xtrabackup_copy_back) { + mixed_options[n_mixed_options++] = "--copy-back"; + } + + if (xtrabackup_move_back) { + mixed_options[n_mixed_options++] = "--move-back"; + } + + if (xtrabackup_prepare) { + mixed_options[n_mixed_options++] = "--apply-log"; + } + + if (n_mixed_options > 1) { + msg("Error: %s and %s are mutually exclusive\n", + mixed_options[0], mixed_options[1]); + return(false); + } + + if (xtrabackup_backup) { + if ((mysql_connection = xb_mysql_connect()) == NULL) { + return(false); + } + + if (!get_mysql_vars(mysql_connection)) { + return(false); + } + + encryption_plugin_backup_init(mysql_connection); + history_start_time = time(NULL); + + } + + return(true); +} + + +extern void init_signals(void); + +#include <sql_locale.h> + +/* Messages . Avoid loading errmsg.sys file */ +void setup_error_messages() +{ + static const char *all_msgs[ER_ERROR_LAST - ER_ERROR_FIRST +1]; + my_default_lc_messages = &my_locale_en_US; + my_default_lc_messages->errmsgs->errmsgs = all_msgs; + + /* Populate the necessary error messages */ + struct { + int id; + const char *fmt; + } + xb_msgs[] = + { + { ER_DATABASE_NAME,"Database" }, + { ER_TABLE_NAME,"Table"}, + { ER_PARTITION_NAME, "Partition" }, + { ER_SUBPARTITION_NAME, "Subpartition" }, + { ER_TEMPORARY_NAME, "Temporary"}, + { ER_RENAMED_NAME, "Renamed"}, + { ER_CANT_FIND_DL_ENTRY, "Can't find symbol '%-.128s' in library"}, + { ER_CANT_OPEN_LIBRARY, "Can't open shared library '%-.192s' (errno: %d, %-.128s)" }, + { ER_OUTOFMEMORY, "Out of memory; restart server and try again (needed %d bytes)" }, + { ER_CANT_OPEN_LIBRARY, "Can't open shared library '%-.192s' (errno: %d, %-.128s)" }, + { ER_UDF_NO_PATHS, "No paths allowed for shared library" }, + { ER_CANT_INITIALIZE_UDF,"Can't initialize function '%-.192s'; %-.80s"}, + { ER_PLUGIN_IS_NOT_LOADED,"Plugin '%-.192s' is not loaded" } + }; + + for (int i = 0; i < (int)array_elements(all_msgs); i++) + all_msgs[i] = "Unknown error"; + + for (int i = 0; i < (int)array_elements(xb_msgs); i++) + all_msgs[xb_msgs[i].id - ER_ERROR_FIRST] = xb_msgs[i].fmt; +} + +extern my_bool(*dict_check_if_skip_table)(const char* name) ; + +void +handle_options(int argc, char **argv, char ***argv_client, char ***argv_server) +{ + /* Setup some variables for Innodb.*/ + + srv_xtrabackup = true; + + + files_charset_info = &my_charset_utf8_general_ci; + dict_check_if_skip_table = check_if_skip_table; + + setup_error_messages(); + sys_var_init(); + plugin_mutex_init(); + mysql_rwlock_init(key_rwlock_LOCK_system_variables_hash, &LOCK_system_variables_hash); + opt_stack_trace = 1; + test_flags |= TEST_SIGINT; + init_signals(); +#ifndef _WIN32 + /* Exit process on SIGINT. */ + my_sigset(SIGINT, SIG_DFL); +#endif + + sf_leaking_memory = 0; /* don't report memory leaks on early exist */ + + int i; + int ho_error; + + char* target_dir = NULL; + bool prepare = false; + + char conf_file[FN_REFLEN]; + int argc_client = argc; + int argc_server = argc; + + /* scan options for group and config file to load defaults from */ + for (i = 1; i < argc; i++) { + + char *optend = strcend(argv[i], '='); + + if (strncmp(argv[i], "--defaults-group", + optend - argv[i]) == 0) { + defaults_group = optend + 1; + append_defaults_group(defaults_group, + xb_server_default_groups, + array_elements(xb_server_default_groups)); + } + + if (strncmp(argv[i], "--login-path", + optend - argv[i]) == 0) { + append_defaults_group(optend + 1, + xb_client_default_groups, + array_elements(xb_client_default_groups)); + } + + if (!strncmp(argv[i], "--prepare", + optend - argv[i])) { + prepare = true; + } + + if (!strncmp(argv[i], "--apply-log", + optend - argv[i])) { + prepare = true; + } + + if (!strncmp(argv[i], "--target-dir", + optend - argv[i]) && *optend) { + target_dir = optend + 1; + } + + if (!*optend && argv[i][0] != '-') { + target_dir = argv[i]; + } + } + + snprintf(conf_file, sizeof(conf_file), "my"); + + if (prepare && target_dir) { + snprintf(conf_file, sizeof(conf_file), + "%s/backup-my.cnf", target_dir); + if (!strncmp(argv[1], "--defaults-file=", 16)) { + /* Remove defaults-file*/ + for (int i = 2; ; i++) { + if ((argv[i-1]= argv[i]) == 0) + break; + } + argc--; + } + } + + *argv_client = argv; + *argv_server = argv; + if (load_defaults(conf_file, xb_server_default_groups, + &argc_server, argv_server)) { + exit(EXIT_FAILURE); + } + + int n; + for (n = 0; (*argv_server)[n]; n++) {}; + argc_server = n; + + print_param_str << + "# This MySQL options file was generated by XtraBackup.\n" + "[" << defaults_group << "]\n"; + + /* We want xtrabackup to ignore unknown options, because it only + recognizes a small subset of server variables */ + my_getopt_skip_unknown = TRUE; + + /* Reset u_max_value for all options, as we don't want the + --maximum-... modifier to set the actual option values */ + for (my_option *optp= xb_server_options; optp->name; optp++) { + optp->u_max_value = (G_PTR *) &global_max_value; + } + + + /* Throw a descriptive error if --defaults-file or --defaults-extra-file + is not the first command line argument */ + for (int i = 2 ; i < argc ; i++) { + char *optend = strcend((argv)[i], '='); + + if (optend - argv[i] == 15 && + !strncmp(argv[i], "--defaults-file", optend - argv[i])) { + + msg("xtrabackup: Error: --defaults-file " + "must be specified first on the command " + "line\n"); + exit(EXIT_FAILURE); + } + if (optend - argv[i] == 21 && + !strncmp(argv[i], "--defaults-extra-file", + optend - argv[i])) { + + msg("xtrabackup: Error: --defaults-extra-file " + "must be specified first on the command " + "line\n"); + exit(EXIT_FAILURE); + } + } + + if (argc_server > 0 + && (ho_error=handle_options(&argc_server, argv_server, + xb_server_options, xb_get_one_option))) + exit(ho_error); + + if (load_defaults(conf_file, xb_client_default_groups, + &argc_client, argv_client)) { + exit(EXIT_FAILURE); + } + + for (n = 0; (*argv_client)[n]; n++) {}; + argc_client = n; + + if (strcmp(base_name(my_progname), INNOBACKUPEX_BIN_NAME) == 0 && + argc_client > 0) { + /* emulate innobackupex script */ + innobackupex_mode = true; + if (!ibx_handle_options(&argc_client, argv_client)) { + exit(EXIT_FAILURE); + } + } + + if (argc_client > 0 + && (ho_error=handle_options(&argc_client, argv_client, + xb_client_options, xb_get_one_option))) + exit(ho_error); + + /* Reject command line arguments that don't look like options, i.e. are + not of the form '-X' (single-character options) or '--option' (long + options) */ + for (int i = 0 ; i < argc_client ; i++) { + const char * const opt = (*argv_client)[i]; + + if (strncmp(opt, "--", 2) && + !(strlen(opt) == 2 && opt[0] == '-')) { + bool server_option = true; + + for (int j = 0; j < argc_server; j++) { + if (opt == (*argv_server)[j]) { + server_option = false; + break; + } + } + + if (!server_option) { + msg("xtrabackup: Error:" + " unknown argument: '%s'\n", opt); + exit(EXIT_FAILURE); + } + } + } +} + +/* ================= main =================== */ +extern my_bool(*fil_check_if_skip_database_by_path)(const char* name); + +int main(int argc, char **argv) +{ + char **client_defaults, **server_defaults; + char cwd[FN_REFLEN]; + static char INNOBACKUPEX_EXE[]= "innobackupex"; + if (argc > 1 && (strcmp(argv[1], "--innobackupex") == 0)) + { + argv++; + argc--; + argv[0] = INNOBACKUPEX_EXE; + innobackupex_mode = true; + } + + /* Setup skip fil_load_single_tablespaces callback.*/ + fil_check_if_skip_database_by_path = check_if_skip_database_by_path; + + init_signals(); + MY_INIT(argv[0]); + + pthread_key_create(&THR_THD, NULL); + my_pthread_setspecific_ptr(THR_THD, NULL); + + xb_regex_init(); + + capture_tool_command(argc, argv); + + if (mysql_server_init(-1, NULL, NULL)) + { + exit(EXIT_FAILURE); + } + + system_charset_info = &my_charset_utf8_general_ci; + key_map_full.set_all(); + + handle_options(argc, argv, &client_defaults, &server_defaults); + + int argc_server; + for (argc_server = 0; server_defaults[argc_server]; argc_server++) {} + + int argc_client; + for (argc_client = 0; client_defaults[argc_client]; argc_client++) {} + + + if (innobackupex_mode) { + if (!ibx_init()) { + exit(EXIT_FAILURE); + } + } + + if ((!xtrabackup_print_param) && (!xtrabackup_prepare) && (strcmp(mysql_data_home, "./") == 0)) { + if (!xtrabackup_print_param) + usage(); + msg("\nxtrabackup: Error: Please set parameter 'datadir'\n"); + exit(EXIT_FAILURE); + } + + /* Expand target-dir, incremental-basedir, etc. */ + + my_getwd(cwd, sizeof(cwd), MYF(0)); + + my_load_path(xtrabackup_real_target_dir, + xtrabackup_target_dir, cwd); + unpack_dirname(xtrabackup_real_target_dir, + xtrabackup_real_target_dir); + xtrabackup_target_dir= xtrabackup_real_target_dir; + + if (xtrabackup_incremental_basedir) { + my_load_path(xtrabackup_real_incremental_basedir, + xtrabackup_incremental_basedir, cwd); + unpack_dirname(xtrabackup_real_incremental_basedir, + xtrabackup_real_incremental_basedir); + xtrabackup_incremental_basedir = + xtrabackup_real_incremental_basedir; + } + + if (xtrabackup_incremental_dir) { + my_load_path(xtrabackup_real_incremental_dir, + xtrabackup_incremental_dir, cwd); + unpack_dirname(xtrabackup_real_incremental_dir, + xtrabackup_real_incremental_dir); + xtrabackup_incremental_dir = xtrabackup_real_incremental_dir; + } + + if (xtrabackup_extra_lsndir) { + my_load_path(xtrabackup_real_extra_lsndir, + xtrabackup_extra_lsndir, cwd); + unpack_dirname(xtrabackup_real_extra_lsndir, + xtrabackup_real_extra_lsndir); + xtrabackup_extra_lsndir = xtrabackup_real_extra_lsndir; + } + + /* get default temporary directory */ + if (!opt_mysql_tmpdir || !opt_mysql_tmpdir[0]) { + opt_mysql_tmpdir = getenv("TMPDIR"); +#if defined(__WIN__) + if (!opt_mysql_tmpdir) { + opt_mysql_tmpdir = getenv("TEMP"); + } + if (!opt_mysql_tmpdir) { + opt_mysql_tmpdir = getenv("TMP"); + } +#endif + if (!opt_mysql_tmpdir || !opt_mysql_tmpdir[0]) { + opt_mysql_tmpdir = const_cast<char*>(DEFAULT_TMPDIR); + } + } + + /* temporary setting of enough size */ + srv_page_size_shift = UNIV_PAGE_SIZE_SHIFT_MAX; + srv_page_size = UNIV_PAGE_SIZE_MAX; + if (xtrabackup_backup && xtrabackup_incremental) { + /* direct specification is only for --backup */ + /* and the lsn is prior to the other option */ + + char* endchar; + int error = 0; + incremental_lsn = strtoll(xtrabackup_incremental, &endchar, 10); + if (*endchar != '\0') + error = 1; + + if (error) { + msg("xtrabackup: value '%s' may be wrong format for " + "incremental option.\n", xtrabackup_incremental); + exit(EXIT_FAILURE); + } + } else if (xtrabackup_backup && xtrabackup_incremental_basedir) { + char filename[FN_REFLEN]; + + sprintf(filename, "%s/%s", xtrabackup_incremental_basedir, XTRABACKUP_METADATA_FILENAME); + + if (!xtrabackup_read_metadata(filename)) { + msg("xtrabackup: error: failed to read metadata from " + "%s\n", filename); + exit(EXIT_FAILURE); + } + + incremental_lsn = metadata_to_lsn; + xtrabackup_incremental = xtrabackup_incremental_basedir; //dummy + } else if (xtrabackup_prepare && xtrabackup_incremental_dir) { + char filename[FN_REFLEN]; + + sprintf(filename, "%s/%s", xtrabackup_incremental_dir, XTRABACKUP_METADATA_FILENAME); + + if (!xtrabackup_read_metadata(filename)) { + msg("xtrabackup: error: failed to read metadata from " + "%s\n", filename); + exit(EXIT_FAILURE); + } + + incremental_lsn = metadata_from_lsn; + incremental_to_lsn = metadata_to_lsn; + incremental_last_lsn = metadata_last_lsn; + xtrabackup_incremental = xtrabackup_incremental_dir; //dummy + + } else if (opt_incremental_history_name) { + xtrabackup_incremental = opt_incremental_history_name; + } else if (opt_incremental_history_uuid) { + xtrabackup_incremental = opt_incremental_history_uuid; + } else { + xtrabackup_incremental = NULL; + } + + if (!xb_init()) { + exit(EXIT_FAILURE); + } + + /* --print-param */ + if (xtrabackup_print_param) { + + printf("%s", print_param_str.str().c_str()); + + exit(EXIT_SUCCESS); + } + + print_version(); + if (xtrabackup_incremental) { + msg("incremental backup from " LSN_PF " is enabled.\n", + incremental_lsn); + } + + if (xtrabackup_export && innobase_file_per_table == FALSE) { + msg("xtrabackup: auto-enabling --innodb-file-per-table due to " + "the --export option\n"); + innobase_file_per_table = TRUE; + } + + if (xtrabackup_incremental && xtrabackup_stream && + xtrabackup_stream_fmt == XB_STREAM_FMT_TAR) { + msg("xtrabackup: error: " + "streaming incremental backups are incompatible with the \n" + "'tar' streaming format. Use --stream=xbstream instead.\n"); + exit(EXIT_FAILURE); + } + + if ((xtrabackup_compress || xtrabackup_encrypt) && xtrabackup_stream && + xtrabackup_stream_fmt == XB_STREAM_FMT_TAR) { + msg("xtrabackup: error: " + "compressed and encrypted backups are incompatible with the \n" + "'tar' streaming format. Use --stream=xbstream instead.\n"); + exit(EXIT_FAILURE); + } + + if (!xtrabackup_prepare && + (innobase_log_arch_dir || xtrabackup_archived_to_lsn)) { + + /* Default my.cnf can contain innobase_log_arch_dir option set + for server, reset it to allow backup. */ + innobase_log_arch_dir= NULL; + xtrabackup_archived_to_lsn= 0; + msg("xtrabackup: warning: " + "as --innodb-log-arch-dir and --to-archived-lsn can be used " + "only with --prepare they will be reset\n"); + } + + /* cannot execute both for now */ + { + int num = 0; + + if (xtrabackup_backup) num++; + if (xtrabackup_stats) num++; + if (xtrabackup_prepare) num++; + if (xtrabackup_copy_back) num++; + if (xtrabackup_move_back) num++; + if (xtrabackup_decrypt_decompress) num++; + if (num != 1) { /* !XOR (for now) */ + usage(); + exit(EXIT_FAILURE); + } + } + +#ifndef __WIN__ + if (xtrabackup_debug_sync) { + signal(SIGCONT, sigcont_handler); + } +#endif + + /* --backup */ + if (xtrabackup_backup) + xtrabackup_backup_func(); + + /* --stats */ + if (xtrabackup_stats) + xtrabackup_stats_func(argc_server,server_defaults); + + /* --prepare */ + if (xtrabackup_prepare) { + xtrabackup_prepare_func(argc_server, server_defaults); + } + + if (xtrabackup_copy_back || xtrabackup_move_back) { + if (!check_if_param_set("datadir")) { + msg("Error: datadir must be specified.\n"); + exit(EXIT_FAILURE); + } + if (!copy_back()) + exit(EXIT_FAILURE); + } + + if (xtrabackup_decrypt_decompress && !decrypt_decompress()) { + exit(EXIT_FAILURE); + } + + backup_cleanup(); + + if (innobackupex_mode) { + ibx_cleanup(); + } + + + free_defaults(client_defaults); + free_defaults(server_defaults); + + if (THR_THD) + (void) pthread_key_delete(THR_THD); + + msg_ts("completed OK!\n"); + + exit(EXIT_SUCCESS); +} diff --git a/extra/mariabackup/xtrabackup.h b/extra/mariabackup/xtrabackup.h new file mode 100644 index 00000000000..51491ce1f00 --- /dev/null +++ b/extra/mariabackup/xtrabackup.h @@ -0,0 +1,247 @@ +/****************************************************** +Copyright (c) 2011-2015 Percona LLC and/or its affiliates. + +Declarations for xtrabackup.cc + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +*******************************************************/ + +#ifndef XB_XTRABACKUP_H +#define XB_XTRABACKUP_H + +#include <my_getopt.h> +#include "datasink.h" +#include "xbstream.h" +#include "changed_page_bitmap.h" + +#ifdef __WIN__ +#define XB_FILE_UNDEFINED NULL +#else +#define XB_FILE_UNDEFINED (-1) +#endif + +typedef struct { + ulint page_size; + ulint zip_size; + ulint space_id; +} xb_delta_info_t; + +/* ======== Datafiles iterator ======== */ +typedef struct { + fil_system_t *system; + fil_space_t *space; + fil_node_t *node; + ibool started; + os_ib_mutex_t mutex; +} datafiles_iter_t; + +/* value of the --incremental option */ +extern lsn_t incremental_lsn; + +extern char *xtrabackup_target_dir; +extern char *xtrabackup_incremental_dir; +extern char *xtrabackup_incremental_basedir; +extern char *innobase_data_home_dir; +extern char *innobase_buffer_pool_filename; +extern ds_ctxt_t *ds_meta; +extern ds_ctxt_t *ds_data; + +/* The last checkpoint LSN at the backup startup time */ +extern lsn_t checkpoint_lsn_start; + +extern xb_page_bitmap *changed_page_bitmap; + +extern char *xtrabackup_incremental; +extern my_bool xtrabackup_incremental_force_scan; + +extern lsn_t metadata_from_lsn; +extern lsn_t metadata_to_lsn; +extern lsn_t metadata_last_lsn; + +extern xb_stream_fmt_t xtrabackup_stream_fmt; +extern ibool xtrabackup_stream; + +extern char *xtrabackup_tables; +extern char *xtrabackup_tables_file; +extern char *xtrabackup_databases; +extern char *xtrabackup_databases_file; +extern char *xtrabackup_tables_exclude; +extern char *xtrabackup_databases_exclude; + +extern ibool xtrabackup_compress; +extern ibool xtrabackup_encrypt; + +extern my_bool xtrabackup_backup; +extern my_bool xtrabackup_prepare; +extern my_bool xtrabackup_apply_log_only; +extern my_bool xtrabackup_copy_back; +extern my_bool xtrabackup_move_back; +extern my_bool xtrabackup_decrypt_decompress; + +extern char *innobase_data_file_path; +extern char *innobase_doublewrite_file; +extern char *xtrabackup_encrypt_key; +extern char *xtrabackup_encrypt_key_file; +extern longlong innobase_log_file_size; +extern long innobase_log_files_in_group; +extern longlong innobase_page_size; + +extern const char *xtrabackup_encrypt_algo_names[]; +extern TYPELIB xtrabackup_encrypt_algo_typelib; + +extern int xtrabackup_parallel; + +extern my_bool xb_close_files; +extern const char *xtrabackup_compress_alg; +#ifdef __cplusplus +extern "C"{ +#endif + extern uint xtrabackup_compress_threads; + extern ulonglong xtrabackup_compress_chunk_size; +#ifdef __cplusplus +} +#endif +extern ulong xtrabackup_encrypt_algo; +extern uint xtrabackup_encrypt_threads; +extern ulonglong xtrabackup_encrypt_chunk_size; +extern my_bool xtrabackup_export; +extern char *xtrabackup_incremental_basedir; +extern char *xtrabackup_extra_lsndir; +extern char *xtrabackup_incremental_dir; +extern ulint xtrabackup_log_copy_interval; +extern char *xtrabackup_stream_str; +extern long xtrabackup_throttle; +extern longlong xtrabackup_use_memory; + +extern my_bool opt_galera_info; +extern my_bool opt_slave_info; +extern my_bool opt_no_lock; +extern my_bool opt_safe_slave_backup; +extern my_bool opt_rsync; +extern my_bool opt_force_non_empty_dirs; +extern my_bool opt_noversioncheck; +extern my_bool opt_no_backup_locks; +extern my_bool opt_decompress; +extern my_bool opt_remove_original; + +extern char *opt_incremental_history_name; +extern char *opt_incremental_history_uuid; + +extern char *opt_user; +extern char *opt_password; +extern char *opt_host; +extern char *opt_defaults_group; +extern char *opt_socket; +extern uint opt_port; +extern char *opt_login_path; +extern char *opt_log_bin; + +extern const char *query_type_names[]; + +enum query_type_t {QUERY_TYPE_ALL, QUERY_TYPE_UPDATE, + QUERY_TYPE_SELECT}; + +extern TYPELIB query_type_typelib; + +extern ulong opt_lock_wait_query_type; +extern ulong opt_kill_long_query_type; + +extern ulong opt_decrypt_algo; + +extern uint opt_kill_long_queries_timeout; +extern uint opt_lock_wait_timeout; +extern uint opt_lock_wait_threshold; +extern uint opt_debug_sleep_before_unlock; +extern uint opt_safe_slave_backup_timeout; + +extern const char *opt_history; +extern my_bool opt_decrypt; + +enum binlog_info_enum { BINLOG_INFO_OFF, BINLOG_INFO_LOCKLESS, BINLOG_INFO_ON, + BINLOG_INFO_AUTO}; + +extern ulong opt_binlog_info; + +void xtrabackup_io_throttling(void); +my_bool xb_write_delta_metadata(const char *filename, + const xb_delta_info_t *info); + +datafiles_iter_t *datafiles_iter_new(fil_system_t *f_system); +fil_node_t *datafiles_iter_next(datafiles_iter_t *it); +void datafiles_iter_free(datafiles_iter_t *it); + +/************************************************************************ +Initialize the tablespace memory cache and populate it by scanning for and +opening data files */ +ulint xb_data_files_init(void); + +/************************************************************************ +Destroy the tablespace memory cache. */ +void xb_data_files_close(void); + +/*********************************************************************** +Reads the space flags from a given data file and returns the compressed +page size, or 0 if the space is not compressed. */ +ulint xb_get_zip_size(os_file_t file); + +/************************************************************************ +Checks if a table specified as a name in the form "database/name" (InnoDB 5.6) +or "./database/name.ibd" (InnoDB 5.5-) should be skipped from backup based on +the --tables or --tables-file options. + +@return TRUE if the table should be skipped. */ +my_bool +check_if_skip_table( +/******************/ + const char* name); /*!< in: path to the table */ + + +/************************************************************************ +Checks if a database specified by path should be skipped from backup based on +the --databases, --databases_file or --databases_exclude options. + +@return TRUE if the table should be skipped. */ +my_bool +check_if_skip_database_by_path( + const char* path /*!< in: path to the db directory. */ +); + +/************************************************************************ +Check if parameter is set in defaults file or via command line argument +@return true if parameter is set. */ +bool +check_if_param_set(const char *param); + +#if defined(HAVE_OPENSSL) +extern my_bool opt_use_ssl; +extern my_bool opt_ssl_verify_server_cert; +#if !defined(HAVE_YASSL) +extern char *opt_server_public_key; +#endif +#endif + + +void +xtrabackup_backup_func(void); + +my_bool +xb_get_one_option(int optid, + const struct my_option *opt __attribute__((unused)), + char *argument); + +const char* +xb_get_copy_action(const char *dflt = "Copying"); + +#endif /* XB_XTRABACKUP_H */ diff --git a/extra/my_print_defaults.c b/extra/my_print_defaults.c index e91163dde1c..bfd0c3c635a 100644 --- a/extra/my_print_defaults.c +++ b/extra/my_print_defaults.c @@ -98,6 +98,11 @@ static struct my_option my_long_options[] = {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} }; +void cleanup_and_exit(int exit_code) +{ + my_end(0); + exit(exit_code); +} static void usage(my_bool version) { @@ -112,7 +117,7 @@ static void usage(my_bool version) my_print_default_files(config_file); my_print_variables(my_long_options); printf("\nExample usage:\n%s --defaults-file=example.cnf client client-server mysql\n", my_progname); - exit(0); + cleanup_and_exit(0); } @@ -125,7 +130,7 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), opt_defaults_file_used= 1; break; case 'n': - exit(0); + cleanup_and_exit(0); case 'I': case '?': usage(0); @@ -174,7 +179,7 @@ int main(int argc, char **argv) /* Check out the args */ if (get_options(&argc,&argv)) - exit(1); + cleanup_and_exit(1); nargs= argc + 1; if (opt_mysqld) diff --git a/extra/perror.c b/extra/perror.c index 55d1e921739..062a04003c2 100644 --- a/extra/perror.c +++ b/extra/perror.c @@ -23,11 +23,6 @@ #include <m_string.h> #include <errno.h> #include <my_getopt.h> -#ifdef WITH_NDBCLUSTER_STORAGE_ENGINE -#include "../storage/ndb/src/ndbapi/ndberror.c" -#include "../storage/ndb/src/kernel/error/ndbd_exit_codes.c" -#include "../storage/ndb/include/mgmapi/mgmapi_error.h" -#endif #include <welcome_copyright_notice.h> /* ORACLE_WELCOME_COPYRIGHT_NOTICE */ static my_bool verbose, print_all_codes; @@ -35,35 +30,12 @@ static my_bool verbose, print_all_codes; #include <my_base.h> #include <my_handler_errors.h> -#ifdef WITH_NDBCLUSTER_STORAGE_ENGINE -static my_bool ndb_code; -static char ndb_string[1024]; -int mgmapi_error_string(int err_no, char *str, int size) -{ - int i; - for (i= 0; i < ndb_mgm_noOfErrorMsgs; i++) - { - if ((int)ndb_mgm_error_msgs[i].code == err_no) - { - my_snprintf(str, size-1, "%s", ndb_mgm_error_msgs[i].msg); - str[size-1]= '\0'; - return 0; - } - } - return -1; -} -#endif - static struct my_option my_long_options[] = { {"help", '?', "Displays this help and exits.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, {"info", 'I', "Synonym for --help.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, -#ifdef WITH_NDBCLUSTER_STORAGE_ENGINE - {"ndb", 257, "Ndbcluster storage engine specific error codes.", &ndb_code, - &ndb_code, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, -#endif #ifdef HAVE_SYS_ERRLIST {"all", 'a', "Print all the error messages and the number. Deprecated," " will be removed in a future release.", @@ -334,35 +306,7 @@ int main(int argc,char *argv[]) found=0; code=atoi(*argv); -#ifdef WITH_NDBCLUSTER_STORAGE_ENGINE - if (ndb_code) - { - if ((ndb_error_string(code, ndb_string, sizeof(ndb_string)) < 0) && - (ndbd_exit_string(code, ndb_string, sizeof(ndb_string)) < 0) && - (mgmapi_error_string(code, ndb_string, sizeof(ndb_string)) < 0)) - { - msg= 0; - } - else - msg= ndb_string; - if (msg) - { - if (verbose) - printf("NDB error code %3d: %s\n",code,msg); - else - puts(msg); - } - else - { - fprintf(stderr,"Illegal ndb error code: %d\n",code); - error= 1; - } - found= 1; - msg= 0; - } - else -#endif - msg = strerror(code); + msg = strerror(code); /* We don't print the OS error message if it is the same as the diff --git a/extra/readline/CMakeLists.txt b/extra/readline/CMakeLists.txt new file mode 100644 index 00000000000..bdecdd1fcce --- /dev/null +++ b/extra/readline/CMakeLists.txt @@ -0,0 +1,59 @@ +# Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}) + +ADD_DEFINITIONS(-DHAVE_CONFIG_H -DNO_KILL_INTR) + +INCLUDE_DIRECTORIES(${CURSES_INCLUDE_PATH}) + +ADD_LIBRARY(readline STATIC + readline.c + funmap.c + keymaps.c + vi_mode.c + parens.c + rltty.c + complete.c + bind.c + isearch.c + display.c + signals.c + util.c + kill.c + undo.c + macro.c + input.c + callback.c + terminal.c + xmalloc.c + history.c + histsearch.c + histexpand.c + histfile.c + nls.c + search.c + shell.c + tilde.c + misc.c + text.c + mbutil.c + compat.c + savestring.c +) + +# Declare dependency +# so every executable that links with readline links with curses as well +TARGET_LINK_LIBRARIES(readline ${CURSES_LIBRARY}) diff --git a/extra/readline/COPYING b/extra/readline/COPYING new file mode 100644 index 00000000000..18e17032a13 --- /dev/null +++ b/extra/readline/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) 19yy <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/extra/readline/INSTALL b/extra/readline/INSTALL new file mode 100644 index 00000000000..f360b9e7907 --- /dev/null +++ b/extra/readline/INSTALL @@ -0,0 +1,287 @@ +Basic Installation +================== + +These are installation instructions for Readline-5.2. + +The simplest way to compile readline is: + + 1. `cd' to the directory containing the readline source code and type + `./configure' to configure readline for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes some time. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile readline and build the static readline + and history libraries. If supported, the shared readline and history + libraries will be built also. See below for instructions on compiling + the other parts of the distribution. Typing `make everything' will + cause the static and shared libraries (if supported) and the example + programs to be built. + + 3. Type `make install' to install the static readline and history + libraries, the readline include files, the documentation, and, if + supported, the shared readline and history libraries. + + 4. You can remove the created libraries and object files from the + build directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile readline for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the readline developers, and should be used with care. + +The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It +uses those values to create a `Makefile' in the build directory, +and Makefiles in the `doc', `shlib', and `examples' +subdirectories. It also creates a `config.h' file containing +system-dependent definitions. Finally, it creates a shell script +`config.status' that you can run in the future to recreate the +current configuration, a file `config.cache' that saves the +results of its tests to speed up reconfiguring, and a file +`config.log' containing compiler output (useful mainly for +debugging `configure'). + +If you need to do unusual things to compile readline, please try +to figure out how `configure' could check whether to do them, and +mail diffs or instructions to <bug-readline@gnu.org> so they can +be considered for the next release. If at some point +`config.cache' contains results you don't want to keep, you may +remove or edit it. + +The file `configure.in' is used to create `configure' by a +program called `autoconf'. You only need `configure.in' if you +want to change it or regenerate `configure' using a newer version +of `autoconf'. The readline `configure.in' requires autoconf +version 2.50 or newer. + +Compilers and Options +===================== + +Some systems require unusual options for compilation or linking that +the `configure' script does not know about. You can give `configure' +initial values for variables by setting them in the environment. Using +a Bourne-compatible shell, you can do that on the command line like +this: + + CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure + +Or on systems that have the `env' program, you can do it like this: + + env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure + +Compiling For Multiple Architectures +==================================== + +You can compile readline for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + +If you have to use a `make' that does not supports the `VPATH' +variable, you have to compile readline for one architecture at a +time in the source code directory. After you have installed +readline for one architecture, use `make distclean' before +reconfiguring for another architecture. + +Installation Names +================== + +By default, `make install' will install the readline libraries in +`/usr/local/lib', the include files in +`/usr/local/include/readline', the man pages in `/usr/local/man', +and the info files in `/usr/local/info'. You can specify an +installation prefix other than `/usr/local' by giving `configure' +the option `--prefix=PATH' or by supplying a value for the +DESTDIR variable when running `make install'. + +You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. +If you give `configure' the option `--exec-prefix=PATH', the +readline Makefiles will use PATH as the prefix for installing the +libraries. Documentation and other data files will still use the +regular prefix. + +Specifying the System Type +========================== + +There may be some features `configure' can not figure out +automatically, but need to determine by the type of host readline +will run on. Usually `configure' can figure that out, but if it +prints a message saying it can not guess the host type, give it +the `--host=TYPE' option. TYPE can either be a short name for +the system type, such as `sun4', or a canonical name with three +fields: CPU-COMPANY-SYSTEM (e.g., i386-unknown-freebsd4.2). + +See the file `config.sub' for the possible values of each field. + +Sharing Defaults +================ + +If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: the readline `configure' looks for a site script, but not +all `configure' scripts do. + +Operation Controls +================== + +`configure' recognizes the following options to control how it +operates. + +`--cache-file=FILE' + Use and save the results of the tests in FILE instead of + `./config.cache'. Set FILE to `/dev/null' to disable caching, for + debugging `configure'. + +`--help' + Print a summary of the options to `configure', and exit. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--version' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`configure' also accepts some other, not widely useful, options. + +Optional Features +================= + +The readline `configure' recognizes a single `--with-PACKAGE' option: + +`--with-curses' + This tells readline that it can find the termcap library functions + (tgetent, et al.) in the curses library, rather than a separate + termcap library. Readline uses the termcap functions, but does not + link with the termcap or curses library itself, allowing applications + which link with readline the to choose an appropriate library. + This option tells readline to link the example programs with the + curses library rather than libtermcap. + +`configure' also recognizes two `--enable-FEATURE' options: + +`--enable-shared' + Build the shared libraries by default on supported platforms. The + default is `yes'. + +`--enable-static' + Build the static libraries by default. The default is `yes'. + +Shared Libraries +================ + +There is support for building shared versions of the readline and +history libraries. The configure script creates a Makefile in +the `shlib' subdirectory, and typing `make shared' will cause +shared versions of the readline and history libraries to be built +on supported platforms. + +If `configure' is given the `--enable-shared' option, it will attempt +to build the shared libraries by default on supported platforms. + +Configure calls the script support/shobj-conf to test whether or +not shared library creation is supported and to generate the values +of variables that are substituted into shlib/Makefile. If you +try to build shared libraries on an unsupported platform, `make' +will display a message asking you to update support/shobj-conf for +your platform. + +If you need to update support/shobj-conf, you will need to create +a `stanza' for your operating system and compiler. The script uses +the value of host_os and ${CC} as determined by configure. For +instance, FreeBSD 4.2 with any version of gcc is identified as +`freebsd4.2-gcc*'. + +In the stanza for your operating system-compiler pair, you will need to +define several variables. They are: + +SHOBJ_CC The C compiler used to compile source files into shareable + object files. This is normally set to the value of ${CC} + by configure, and should not need to be changed. + +SHOBJ_CFLAGS Flags to pass to the C compiler ($SHOBJ_CC) to create + position-independent code. If you are using gcc, this + should probably be set to `-fpic'. + +SHOBJ_LD The link editor to be used to create the shared library from + the object files created by $SHOBJ_CC. If you are using + gcc, a value of `gcc' will probably work. + +SHOBJ_LDFLAGS Flags to pass to SHOBJ_LD to enable shared object creation. + If you are using gcc, `-shared' may be all that is necessary. + These should be the flags needed for generic shared object + creation. + +SHLIB_XLDFLAGS Additional flags to pass to SHOBJ_LD for shared library + creation. Many systems use the -R option to the link + editor to embed a path within the library for run-time + library searches. A reasonable value for such systems would + be `-R$(libdir)'. + +SHLIB_LIBS Any additional libraries that shared libraries should be + linked against when they are created. + +SHLIB_LIBPREF The prefix to use when generating the filename of the shared + library. The default is `lib'; Cygwin uses `cyg'. + +SHLIB_LIBSUFF The suffix to add to `libreadline' and `libhistory' when + generating the filename of the shared library. Many systems + use `so'; HP-UX uses `sl'. + +SHLIB_LIBVERSION The string to append to the filename to indicate the version + of the shared library. It should begin with $(SHLIB_LIBSUFF), + and possibly include version information that allows the + run-time loader to load the version of the shared library + appropriate for a particular program. Systems using shared + libraries similar to SunOS 4.x use major and minor library + version numbers; for those systems a value of + `$(SHLIB_LIBSUFF).$(SHLIB_MAJOR)$(SHLIB_MINOR)' is appropriate. + Systems based on System V Release 4 don't use minor version + numbers; use `$(SHLIB_LIBSUFF).$(SHLIB_MAJOR)' on those systems. + Other Unix versions use different schemes. + +SHLIB_DLLVERSION The version number for shared libraries that determines API + compatibility between readline versions and the underlying + system. Used only on Cygwin. Defaults to $SHLIB_MAJOR, but + can be overridden at configuration time by defining DLLVERSION + in the environment. + +SHLIB_DOT The character used to separate the name of the shared library + from the suffix and version information. The default is `.'; + systems like Cygwin which don't separate version information + from the library name should set this to the empty string. + +SHLIB_STATUS Set this to `supported' when you have defined the other + necessary variables. Make uses this to determine whether + or not shared library creation should be attempted. If + shared libraries are not supported, this will be set to + `unsupported'. + +You should look at the existing stanzas in support/shobj-conf for ideas. + +Once you have updated support/shobj-conf, re-run configure and type +`make shared' or `make'. The shared libraries will be created in the +shlib subdirectory. + +If shared libraries are created, `make install' will install them. +You may install only the shared libraries by running `make +install-shared' from the top-level build directory. Running `make +install' in the shlib subdirectory will also work. If you don't want +to install any created shared libraries, run `make install-static'. diff --git a/extra/readline/README b/extra/readline/README new file mode 100644 index 00000000000..8da99626aa1 --- /dev/null +++ b/extra/readline/README @@ -0,0 +1,186 @@ +Introduction +============ + +This is the Gnu Readline library, version 5.2. + +The Readline library provides a set of functions for use by applications +that allow users to edit command lines as they are typed in. Both +Emacs and vi editing modes are available. The Readline library includes +additional functions to maintain a list of previously-entered command +lines, to recall and perhaps reedit those lines, and perform csh-like +history expansion on previous commands. + +The history facilites are also placed into a separate library, the +History library, as part of the build process. The History library +may be used without Readline in applications which desire its +capabilities. + +The Readline library is free software, distributed under the terms of +the [GNU] General Public License, version 2. For more information, see +the file COPYING. + +To build the library, try typing `./configure', then `make'. The +configuration process is automated, so no further intervention should +be necessary. Readline builds with `gcc' by default if it is +available. If you want to use `cc' instead, type + + CC=cc ./configure + +if you are using a Bourne-style shell. If you are not, the following +may work: + + env CC=cc ./configure + +Read the file INSTALL in this directory for more information about how +to customize and control the build process. + +The file rlconf.h contains C preprocessor defines that enable and disable +certain Readline features. + +The special make target `everything' will build the static and shared +libraries (if the target platform supports them) and the examples. + +Examples +======== + +There are several example programs that use Readline features in the +examples directory. The `rl' program is of particular interest. It +is a command-line interface to Readline, suitable for use in shell +scripts in place of `read'. + +Shared Libraries +================ + +There is skeletal support for building shared versions of the +Readline and History libraries. The configure script creates +a Makefile in the `shlib' subdirectory, and typing `make shared' +will cause shared versions of the Readline and History libraries +to be built on supported platforms. + +If `configure' is given the `--enable-shared' option, it will attempt +to build the shared libraries by default on supported platforms. + +Configure calls the script support/shobj-conf to test whether or +not shared library creation is supported and to generate the values +of variables that are substituted into shlib/Makefile. If you +try to build shared libraries on an unsupported platform, `make' +will display a message asking you to update support/shobj-conf for +your platform. + +If you need to update support/shobj-conf, you will need to create +a `stanza' for your operating system and compiler. The script uses +the value of host_os and ${CC} as determined by configure. For +instance, FreeBSD 4.2 with any version of gcc is identified as +`freebsd4.2-gcc*'. + +In the stanza for your operating system-compiler pair, you will need to +define several variables. They are: + +SHOBJ_CC The C compiler used to compile source files into shareable + object files. This is normally set to the value of ${CC} + by configure, and should not need to be changed. + +SHOBJ_CFLAGS Flags to pass to the C compiler ($SHOBJ_CC) to create + position-independent code. If you are using gcc, this + should probably be set to `-fpic'. + +SHOBJ_LD The link editor to be used to create the shared library from + the object files created by $SHOBJ_CC. If you are using + gcc, a value of `gcc' will probably work. + +SHOBJ_LDFLAGS Flags to pass to SHOBJ_LD to enable shared object creation. + If you are using gcc, `-shared' may be all that is necessary. + These should be the flags needed for generic shared object + creation. + +SHLIB_XLDFLAGS Additional flags to pass to SHOBJ_LD for shared library + creation. Many systems use the -R option to the link + editor to embed a path within the library for run-time + library searches. A reasonable value for such systems would + be `-R$(libdir)'. + +SHLIB_LIBS Any additional libraries that shared libraries should be + linked against when they are created. + +SHLIB_LIBPREF The prefix to use when generating the filename of the shared + library. The default is `lib'; Cygwin uses `cyg'. + +SHLIB_LIBSUFF The suffix to add to `libreadline' and `libhistory' when + generating the filename of the shared library. Many systems + use `so'; HP-UX uses `sl'. + +SHLIB_LIBVERSION The string to append to the filename to indicate the version + of the shared library. It should begin with $(SHLIB_LIBSUFF), + and possibly include version information that allows the + run-time loader to load the version of the shared library + appropriate for a particular program. Systems using shared + libraries similar to SunOS 4.x use major and minor library + version numbers; for those systems a value of + `$(SHLIB_LIBSUFF).$(SHLIB_MAJOR)$(SHLIB_MINOR)' is appropriate. + Systems based on System V Release 4 don't use minor version + numbers; use `$(SHLIB_LIBSUFF).$(SHLIB_MAJOR)' on those systems. + Other Unix versions use different schemes. + +SHLIB_DLLVERSION The version number for shared libraries that determines API + compatibility between readline versions and the underlying + system. Used only on Cygwin. Defaults to $SHLIB_MAJOR, but + can be overridden at configuration time by defining DLLVERSION + in the environment. + +SHLIB_DOT The character used to separate the name of the shared library + from the suffix and version information. The default is `.'; + systems like Cygwin which don't separate version information + from the library name should set this to the empty string. + +SHLIB_STATUS Set this to `supported' when you have defined the other + necessary variables. Make uses this to determine whether + or not shared library creation should be attempted. + +You should look at the existing stanzas in support/shobj-conf for ideas. + +Once you have updated support/shobj-conf, re-run configure and type +`make shared'. The shared libraries will be created in the shlib +subdirectory. + +If shared libraries are created, `make install' will install them. +You may install only the shared libraries by running `make +install-shared' from the top-level build directory. Running `make +install' in the shlib subdirectory will also work. If you don't want +to install any created shared libraries, run `make install-static'. + +Documentation +============= + +The documentation for the Readline and History libraries appears in +the `doc' subdirectory. There are three texinfo files and a +Unix-style manual page describing the facilities available in the +Readline library. The texinfo files include both user and +programmer's manuals. HTML versions of the manuals appear in the +`doc' subdirectory as well. + +Reporting Bugs +============== + +Bug reports for Readline should be sent to: + + bug-readline@gnu.org + +When reporting a bug, please include the following information: + + * the version number and release status of Readline (e.g., 4.2-release) + * the machine and OS that it is running on + * a list of the compilation flags or the contents of `config.h', if + appropriate + * a description of the bug + * a recipe for recreating the bug reliably + * a fix for the bug if you have one! + +If you would like to contact the Readline maintainer directly, send mail +to bash-maintainers@gnu.org. + +Since Readline is developed along with bash, the bug-bash@gnu.org mailing +list (mirrored to the Usenet newsgroup gnu.bash.bug) often contains +Readline bug reports and fixes. + +Chet Ramey +chet.ramey@case.edu diff --git a/extra/readline/ansi_stdlib.h b/extra/readline/ansi_stdlib.h new file mode 100644 index 00000000000..42ac66ebcc0 --- /dev/null +++ b/extra/readline/ansi_stdlib.h @@ -0,0 +1,54 @@ +/* ansi_stdlib.h -- An ANSI Standard stdlib.h. */ +/* A minimal stdlib.h containing extern declarations for those functions + that bash uses. */ + +/* Copyright (C) 1993 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2, or (at your option) any later + version. + + Bash 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 General Public License + for more details. + + You should have received a copy of the GNU General Public License along + with Bash; see the file COPYING. If not, write to the Free Software + Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#if !defined (_STDLIB_H_) +#define _STDLIB_H_ 1 + +/* String conversion functions. */ +extern int atoi (); + +extern double atof (); +extern double strtod (); + +/* Memory allocation functions. */ +/* Generic pointer type. */ +#ifndef PTR_T + +#if defined (__STDC__) +# define PTR_T void * +#else +# define PTR_T char * +#endif + +#endif /* PTR_T */ + +extern PTR_T malloc (); +extern PTR_T realloc (); +extern void free (); + +/* Other miscellaneous functions. */ +extern void abort (); +extern void exit (); +extern char *getenv (); +extern void qsort (); + +#endif /* _STDLIB_H */ diff --git a/extra/readline/bind.c b/extra/readline/bind.c new file mode 100644 index 00000000000..d979f244f4d --- /dev/null +++ b/extra/readline/bind.c @@ -0,0 +1,2311 @@ +/* bind.c -- key binding and startup file support for the readline library. */ + +/* Copyright (C) 1987-2006 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#define READLINE_LIBRARY + +#if defined (__TANDEM) +# include <floss.h> +#endif + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <stdio.h> +#include <sys/types.h> +#include <fcntl.h> +#if defined (HAVE_SYS_FILE_H) +# include <sys/file.h> +#endif /* HAVE_SYS_FILE_H */ + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#include <errno.h> + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +#include "posixstat.h" + +/* System-specific feature definitions and include files. */ +#include "rldefs.h" + +/* Some standard library routines. */ +#include "readline.h" +#include "history.h" + +#include "rlprivate.h" +#include "rlshell.h" +#include "xmalloc.h" + +#if !defined (strchr) && !defined (__STDC__) +extern char *strchr (), *strrchr (); +#endif /* !strchr && !__STDC__ */ + +/* Variables exported by this file. */ +Keymap rl_binding_keymap; + +static char *_rl_read_file PARAMS((char *, size_t *)); +static void _rl_init_file_error PARAMS((const char *)); +static int _rl_read_init_file PARAMS((const char *, int)); +static int glean_key_from_name PARAMS((char *)); +static int find_boolean_var PARAMS((const char *)); + +static const char *_rl_get_string_variable_value PARAMS((const char *)); +static int substring_member_of_array PARAMS((char *, const char **)); + +static int currently_reading_init_file; + +/* used only in this file */ +static int _rl_prefer_visible_bell = 1; + +/* **************************************************************** */ +/* */ +/* Binding keys */ +/* */ +/* **************************************************************** */ + +/* rl_add_defun (char *name, rl_command_func_t *function, int key) + Add NAME to the list of named functions. Make FUNCTION be the function + that gets called. If KEY is not -1, then bind it. */ +int +rl_add_defun (name, function, key) + const char *name; + rl_command_func_t *function; + int key; +{ + if (key != -1) + rl_bind_key (key, function); + rl_add_funmap_entry (name, function); + return 0; +} + +/* Bind KEY to FUNCTION. Returns non-zero if KEY is out of range. */ +int +rl_bind_key (key, function) + int key; + rl_command_func_t *function; +{ + if (key < 0) + return (key); + + if (META_CHAR (key) && _rl_convert_meta_chars_to_ascii) + { + if (_rl_keymap[ESC].type == ISKMAP) + { + Keymap escmap; + + escmap = FUNCTION_TO_KEYMAP (_rl_keymap, ESC); + key = UNMETA (key); + escmap[key].type = ISFUNC; + escmap[key].function = function; + return (0); + } + return (key); + } + + _rl_keymap[key].type = ISFUNC; + _rl_keymap[key].function = function; + rl_binding_keymap = _rl_keymap; + return (0); +} + +/* Bind KEY to FUNCTION in MAP. Returns non-zero in case of invalid + KEY. */ +int +rl_bind_key_in_map (key, function, map) + int key; + rl_command_func_t *function; + Keymap map; +{ + int result; + Keymap oldmap; + + oldmap = _rl_keymap; + _rl_keymap = map; + result = rl_bind_key (key, function); + _rl_keymap = oldmap; + return (result); +} + +/* Bind key sequence KEYSEQ to DEFAULT_FUNC if KEYSEQ is unbound. Right + now, this is always used to attempt to bind the arrow keys, hence the + check for rl_vi_movement_mode. */ +int +rl_bind_key_if_unbound_in_map (key, default_func, kmap) + int key; + rl_command_func_t *default_func; + Keymap kmap; +{ + char keyseq[2]; + + keyseq[0] = (unsigned char)key; + keyseq[1] = '\0'; + return (rl_bind_keyseq_if_unbound_in_map (keyseq, default_func, kmap)); +} + +int +rl_bind_key_if_unbound (key, default_func) + int key; + rl_command_func_t *default_func; +{ + char keyseq[2]; + + keyseq[0] = (unsigned char)key; + keyseq[1] = '\0'; + return (rl_bind_keyseq_if_unbound_in_map (keyseq, default_func, _rl_keymap)); +} + +/* Make KEY do nothing in the currently selected keymap. + Returns non-zero in case of error. */ +int +rl_unbind_key (key) + int key; +{ + return (rl_bind_key (key, (rl_command_func_t *)NULL)); +} + +/* Make KEY do nothing in MAP. + Returns non-zero in case of error. */ +int +rl_unbind_key_in_map (key, map) + int key; + Keymap map; +{ + return (rl_bind_key_in_map (key, (rl_command_func_t *)NULL, map)); +} + +/* Unbind all keys bound to FUNCTION in MAP. */ +int +rl_unbind_function_in_map (func, map) + rl_command_func_t *func; + Keymap map; +{ + register int i, rval; + + for (i = rval = 0; i < KEYMAP_SIZE; i++) + { + if (map[i].type == ISFUNC && map[i].function == func) + { + map[i].function = (rl_command_func_t *)NULL; + rval = 1; + } + } + return rval; +} + +int +rl_unbind_command_in_map (command, map) + const char *command; + Keymap map; +{ + rl_command_func_t *func; + + func = rl_named_function (command); + if (func == 0) + return 0; + return (rl_unbind_function_in_map (func, map)); +} + +/* Bind the key sequence represented by the string KEYSEQ to + FUNCTION, starting in the current keymap. This makes new + keymaps as necessary. */ +int +rl_bind_keyseq (keyseq, function) + const char *keyseq; + rl_command_func_t *function; +{ + return (rl_generic_bind (ISFUNC, keyseq, (char *)function, _rl_keymap)); +} + +/* Bind the key sequence represented by the string KEYSEQ to + FUNCTION. This makes new keymaps as necessary. The initial + place to do bindings is in MAP. */ +int +rl_bind_keyseq_in_map (keyseq, function, map) + const char *keyseq; + rl_command_func_t *function; + Keymap map; +{ + return (rl_generic_bind (ISFUNC, keyseq, (char *)function, map)); +} + +/* Backwards compatibility; equivalent to rl_bind_keyseq_in_map() */ +int +rl_set_key (keyseq, function, map) + const char *keyseq; + rl_command_func_t *function; + Keymap map; +{ + return (rl_generic_bind (ISFUNC, keyseq, (char *)function, map)); +} + +/* Bind key sequence KEYSEQ to DEFAULT_FUNC if KEYSEQ is unbound. Right + now, this is always used to attempt to bind the arrow keys, hence the + check for rl_vi_movement_mode. */ +int +rl_bind_keyseq_if_unbound_in_map (keyseq, default_func, kmap) + const char *keyseq; + rl_command_func_t *default_func; + Keymap kmap; +{ + rl_command_func_t *func; + + if (keyseq) + { + func = rl_function_of_keyseq (keyseq, kmap, (int *)NULL); +#if defined (VI_MODE) + if (!func || func == rl_do_lowercase_version || func == rl_vi_movement_mode) +#else + if (!func || func == rl_do_lowercase_version) +#endif + return (rl_bind_keyseq_in_map (keyseq, default_func, kmap)); + else + return 1; + } + return 0; +} + +int +rl_bind_keyseq_if_unbound (keyseq, default_func) + const char *keyseq; + rl_command_func_t *default_func; +{ + return (rl_bind_keyseq_if_unbound_in_map (keyseq, default_func, _rl_keymap)); +} + +/* Bind the key sequence represented by the string KEYSEQ to + the string of characters MACRO. This makes new keymaps as + necessary. The initial place to do bindings is in MAP. */ +int +rl_macro_bind (keyseq, macro, map) + const char *keyseq, *macro; + Keymap map; +{ + char *macro_keys; + int macro_keys_len; + + macro_keys = (char *)xmalloc ((2 * strlen (macro)) + 1); + + if (rl_translate_keyseq (macro, macro_keys, ¯o_keys_len)) + { + free (macro_keys); + return -1; + } + rl_generic_bind (ISMACR, keyseq, macro_keys, map); + return 0; +} + +/* Bind the key sequence represented by the string KEYSEQ to + the arbitrary pointer DATA. TYPE says what kind of data is + pointed to by DATA, right now this can be a function (ISFUNC), + a macro (ISMACR), or a keymap (ISKMAP). This makes new keymaps + as necessary. The initial place to do bindings is in MAP. */ +int +rl_generic_bind (type, keyseq, data, map) + int type; + const char *keyseq; + char *data; + Keymap map; +{ + char *keys; + int keys_len; + register int i; + KEYMAP_ENTRY k= { 0, NULL }; + + /* If no keys to bind to, exit right away. */ + if (keyseq == 0 || *keyseq == 0) + { + if (type == ISMACR) + free (data); + return -1; + } + + keys = (char *)xmalloc (1 + (2 * strlen (keyseq))); + + /* Translate the ASCII representation of KEYSEQ into an array of + characters. Stuff the characters into KEYS, and the length of + KEYS into KEYS_LEN. */ + if (rl_translate_keyseq (keyseq, keys, &keys_len)) + { + free (keys); + return -1; + } + + /* Bind keys, making new keymaps as necessary. */ + for (i = 0; i < keys_len; i++) + { + unsigned char uc = keys[i]; + int ic; + + ic = uc; + if (ic < 0 || ic >= KEYMAP_SIZE) + { + free (keys); + return -1; + } + + if (META_CHAR (ic) && _rl_convert_meta_chars_to_ascii) + { + ic = UNMETA (ic); + if (map[ESC].type == ISKMAP) + map = FUNCTION_TO_KEYMAP (map, ESC); + } + + if ((i + 1) < keys_len) + { + if (map[ic].type != ISKMAP) + { + /* We allow subsequences of keys. If a keymap is being + created that will `shadow' an existing function or macro + key binding, we save that keybinding into the ANYOTHERKEY + index in the new map. The dispatch code will look there + to find the function to execute if the subsequence is not + matched. ANYOTHERKEY was chosen to be greater than + UCHAR_MAX. */ + k = map[ic]; + + map[ic].type = ISKMAP; + map[ic].function = KEYMAP_TO_FUNCTION (rl_make_bare_keymap()); + } + map = FUNCTION_TO_KEYMAP (map, ic); + /* The dispatch code will return this function if no matching + key sequence is found in the keymap. This (with a little + help from the dispatch code in readline.c) allows `a' to be + mapped to something, `abc' to be mapped to something else, + and the function bound to `a' to be executed when the user + types `abx', leaving `bx' in the input queue. */ + if (k.function && ((k.type == ISFUNC && k.function != rl_do_lowercase_version) || k.type == ISMACR)) + { + map[ANYOTHERKEY] = k; + k.function = 0; + } + } + else + { + if (map[ic].type == ISMACR) + free ((char *)map[ic].function); + else if (map[ic].type == ISKMAP) + { + map = FUNCTION_TO_KEYMAP (map, ic); + ic = ANYOTHERKEY; + } + + map[ic].function = KEYMAP_TO_FUNCTION (data); + map[ic].type = type; + } + + rl_binding_keymap = map; + } + free (keys); + return 0; +} + +/* Translate the ASCII representation of SEQ, stuffing the values into ARRAY, + an array of characters. LEN gets the final length of ARRAY. Return + non-zero if there was an error parsing SEQ. */ +int +rl_translate_keyseq (seq, array, len) + const char *seq; + char *array; + int *len; +{ + register int i, c, l, temp; + + for (i = l = 0; (c = seq[i]); i++) + { + if (c == '\\') + { + c = seq[++i]; + + if (c == 0) + break; + + /* Handle \C- and \M- prefixes. */ + if ((c == 'C' || c == 'M') && seq[i + 1] == '-') + { + /* Handle special case of backwards define. */ + if (strncmp (&seq[i], "C-\\M-", 5) == 0) + { + array[l++] = ESC; /* ESC is meta-prefix */ + i += 5; + array[l++] = CTRL (_rl_to_upper (seq[i])); + if (seq[i] == '\0') + i--; + } + else if (c == 'M') + { + i++; /* seq[i] == '-' */ + /* XXX - obey convert-meta setting */ + if (_rl_convert_meta_chars_to_ascii && _rl_keymap[ESC].type == ISKMAP) + array[l++] = ESC; /* ESC is meta-prefix */ + else if (seq[i+1] == '\\' && seq[i+2] == 'C' && seq[i+3] == '-') + { + i += 4; + temp = (seq[i] == '?') ? RUBOUT : CTRL (_rl_to_upper (seq[i])); + array[l++] = META (temp); + } + else + { + /* This doesn't yet handle things like \M-\a, which may + or may not have any reasonable meaning. You're + probably better off using straight octal or hex. */ + i++; + array[l++] = META (seq[i]); + } + } + else if (c == 'C') + { + i += 2; + /* Special hack for C-?... */ + array[l++] = (seq[i] == '?') ? RUBOUT : CTRL (_rl_to_upper (seq[i])); + } + continue; + } + + /* Translate other backslash-escaped characters. These are the + same escape sequences that bash's `echo' and `printf' builtins + handle, with the addition of \d -> RUBOUT. A backslash + preceding a character that is not special is stripped. */ + switch (c) + { + case 'a': + array[l++] = '\007'; + break; + case 'b': + array[l++] = '\b'; + break; + case 'd': + array[l++] = RUBOUT; /* readline-specific */ + break; + case 'e': + array[l++] = ESC; + break; + case 'f': + array[l++] = '\f'; + break; + case 'n': + array[l++] = NEWLINE; + break; + case 'r': + array[l++] = RETURN; + break; + case 't': + array[l++] = TAB; + break; + case 'v': + array[l++] = 0x0B; + break; + case '\\': + array[l++] = '\\'; + break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + i++; + for (temp = 2, c -= '0'; ISOCTAL (seq[i]) && temp--; i++) + c = (c * 8) + OCTVALUE (seq[i]); + i--; /* auto-increment in for loop */ + array[l++] = c & largest_char; + break; + case 'x': + i++; + for (temp = 2, c = 0; ISXDIGIT ((unsigned char)seq[i]) && temp--; i++) + c = (c * 16) + HEXVALUE (seq[i]); + if (temp == 2) + c = 'x'; + i--; /* auto-increment in for loop */ + array[l++] = c & largest_char; + break; + default: /* backslashes before non-special chars just add the char */ + array[l++] = c; + break; /* the backslash is stripped */ + } + continue; + } + + array[l++] = c; + } + + *len = l; + array[l] = '\0'; + return (0); +} + +char * +rl_untranslate_keyseq (seq) + int seq; +{ + static char kseq[16]; + int i, c; + + i = 0; + c = seq; + if (META_CHAR (c)) + { + kseq[i++] = '\\'; + kseq[i++] = 'M'; + kseq[i++] = '-'; + c = UNMETA (c); + } + else if (c == ESC) + { + kseq[i++] = '\\'; + c = 'e'; + } + else if (CTRL_CHAR (c)) + { + kseq[i++] = '\\'; + kseq[i++] = 'C'; + kseq[i++] = '-'; + c = _rl_to_lower (UNCTRL (c)); + } + else if (c == RUBOUT) + { + kseq[i++] = '\\'; + kseq[i++] = 'C'; + kseq[i++] = '-'; + c = '?'; + } + + if (c == ESC) + { + kseq[i++] = '\\'; + c = 'e'; + } + else if (c == '\\' || c == '"') + { + kseq[i++] = '\\'; + } + + kseq[i++] = (unsigned char) c; + kseq[i] = '\0'; + return kseq; +} + +static char * +_rl_untranslate_macro_value (seq) + char *seq; +{ + char *ret, *r, *s; + int c; + + r = ret = (char *)xmalloc (7 * strlen (seq) + 1); + for (s = seq; *s; s++) + { + c = *s; + if (META_CHAR (c)) + { + *r++ = '\\'; + *r++ = 'M'; + *r++ = '-'; + c = UNMETA (c); + } + else if (c == ESC) + { + *r++ = '\\'; + c = 'e'; + } + else if (CTRL_CHAR (c)) + { + *r++ = '\\'; + *r++ = 'C'; + *r++ = '-'; + c = _rl_to_lower (UNCTRL (c)); + } + else if (c == RUBOUT) + { + *r++ = '\\'; + *r++ = 'C'; + *r++ = '-'; + c = '?'; + } + + if (c == ESC) + { + *r++ = '\\'; + c = 'e'; + } + else if (c == '\\' || c == '"') + *r++ = '\\'; + + *r++ = (unsigned char)c; + } + *r = '\0'; + return ret; +} + +/* Return a pointer to the function that STRING represents. + If STRING doesn't have a matching function, then a NULL pointer + is returned. */ +rl_command_func_t * +rl_named_function (string) + const char *string; +{ + register int i; + + rl_initialize_funmap (); + + for (i = 0; funmap[i]; i++) + if (_rl_stricmp (funmap[i]->name, string) == 0) + return (funmap[i]->function); + return ((rl_command_func_t *)NULL); +} + +/* Return the function (or macro) definition which would be invoked via + KEYSEQ if executed in MAP. If MAP is NULL, then the current keymap is + used. TYPE, if non-NULL, is a pointer to an int which will receive the + type of the object pointed to. One of ISFUNC (function), ISKMAP (keymap), + or ISMACR (macro). */ +rl_command_func_t * +rl_function_of_keyseq (keyseq, map, type) + const char *keyseq; + Keymap map; + int *type; +{ + register int i; + + if (map == 0) + map = _rl_keymap; + + for (i = 0; keyseq && keyseq[i]; i++) + { + unsigned char ic = keyseq[i]; + + if (META_CHAR_FOR_UCHAR(ic) && _rl_convert_meta_chars_to_ascii) + { + if (map[ESC].type == ISKMAP) + { + map = FUNCTION_TO_KEYMAP (map, ESC); + ic = UNMETA (ic); + } + /* XXX - should we just return NULL here, since this obviously + doesn't match? */ + else + { + if (type) + *type = map[ESC].type; + + return (map[ESC].function); + } + } + + if (map[ic].type == ISKMAP) + { + /* If this is the last key in the key sequence, return the + map. */ + if (keyseq[i + 1] == '\0') + { + if (type) + *type = ISKMAP; + + return (map[ic].function); + } + else + map = FUNCTION_TO_KEYMAP (map, ic); + } + /* If we're not at the end of the key sequence, and the current key + is bound to something other than a keymap, then the entire key + sequence is not bound. */ + else if (map[ic].type != ISKMAP && keyseq[i+1]) + return ((rl_command_func_t *)NULL); + else /* map[ic].type != ISKMAP && keyseq[i+1] == 0 */ + { + if (type) + *type = map[ic].type; + + return (map[ic].function); + } + } + return ((rl_command_func_t *) NULL); +} + +/* The last key bindings file read. */ +static char *last_readline_init_file = (char *)NULL; + +/* The file we're currently reading key bindings from. */ +static const char *current_readline_init_file; +static int current_readline_init_include_level; +static int current_readline_init_lineno; + +/* Read FILENAME into a locally-allocated buffer and return the buffer. + The size of the buffer is returned in *SIZEP. Returns NULL if any + errors were encountered. */ +static char * +_rl_read_file (filename, sizep) + char *filename; + size_t *sizep; +{ + struct stat finfo; + size_t file_size; + char *buffer; + int i, file; + + if ((stat (filename, &finfo) < 0) || (file = open (filename, O_RDONLY, 0666)) < 0) + return ((char *)NULL); + + file_size = (size_t)finfo.st_size; + + /* check for overflow on very large files */ + if ((sizeof(off_t) > sizeof(size_t) && finfo.st_size > (off_t)(size_t)~0) || + file_size + 1 < file_size) + { + if (file >= 0) + close (file); +#if defined (EFBIG) + errno = EFBIG; +#endif + return ((char *)NULL); + } + + /* Read the file into BUFFER. */ + buffer = (char *)xmalloc (file_size + 1); + i = read (file, buffer, file_size); + close (file); + + if (i < 0) + { + free (buffer); + return ((char *)NULL); + } + + buffer[i] = '\0'; + if (sizep) + *sizep = i; + + return (buffer); +} + +/* Re-read the current keybindings file. */ +int +rl_re_read_init_file (count, ignore) + int count __attribute__((unused)), ignore __attribute__((unused)); +{ + int r; + r = rl_read_init_file ((const char *)NULL); + rl_set_keymap_from_edit_mode (); + return r; +} + +/* Do key bindings from a file. If FILENAME is NULL it defaults + to the first non-null filename from this list: + 1. the filename used for the previous call + 2. the value of the shell variable `INPUTRC' + 3. ~/.inputrc + 4. /etc/inputrc + If the file existed and could be opened and read, 0 is returned, + otherwise errno is returned. */ +int +rl_read_init_file (filename) + const char *filename; +{ + /* Default the filename. */ + if (filename == 0) + filename = last_readline_init_file; + if (filename == 0) + filename = sh_get_env_value ("INPUTRC"); + if (filename == 0 || *filename == 0) + { + filename = DEFAULT_INPUTRC; + /* Try to read DEFAULT_INPUTRC; fall back to SYS_INPUTRC on failure */ + if (_rl_read_init_file (filename, 0) == 0) + return 0; + filename = SYS_INPUTRC; + } + +#if defined (__MSDOS__) + if (_rl_read_init_file (filename, 0) == 0) + return 0; + filename = "~/_inputrc"; +#endif + return (_rl_read_init_file (filename, 0)); +} + +static int +_rl_read_init_file (filename, include_level) + const char *filename; + int include_level; +{ + register int i; + char *buffer, *openname, *line, *end; + size_t file_size = 0; + + current_readline_init_file = filename; + current_readline_init_include_level = include_level; + + openname = tilde_expand (filename); + buffer = _rl_read_file (openname, &file_size); + free (openname); + + if (buffer == 0) + return (errno); + + if (include_level == 0 && filename != last_readline_init_file) + { + FREE (last_readline_init_file); + last_readline_init_file = savestring (filename); + } + + currently_reading_init_file = 1; + + /* Loop over the lines in the file. Lines that start with `#' are + comments; all other lines are commands for readline initialization. */ + current_readline_init_lineno = 1; + line = buffer; + end = buffer + file_size; + while (line < end) + { + /* Find the end of this line. */ + for (i = 0; line + i != end && line[i] != '\n'; i++); + +#if defined (__CYGWIN__) + /* ``Be liberal in what you accept.'' */ + if (line[i] == '\n' && line[i-1] == '\r') + line[i - 1] = '\0'; +#endif + + /* Mark end of line. */ + line[i] = '\0'; + + /* Skip leading whitespace. */ + while (*line && whitespace (*line)) + { + line++; + i--; + } + + /* If the line is not a comment, then parse it. */ + if (*line && *line != '#') + rl_parse_and_bind (line); + + /* Move to the next line. */ + line += i + 1; + current_readline_init_lineno++; + } + + free (buffer); + currently_reading_init_file = 0; + return (0); +} + +static void +_rl_init_file_error (msg) + const char *msg; +{ + if (currently_reading_init_file) + fprintf (stderr, "readline: %s: line %d: %s\n", current_readline_init_file, + current_readline_init_lineno, msg); + else + fprintf (stderr, "readline: %s\n", msg); +} + +/* **************************************************************** */ +/* */ +/* Parser Directives */ +/* */ +/* **************************************************************** */ + +typedef int _rl_parser_func_t PARAMS((char *)); + +/* Things that mean `Control'. */ +const char *_rl_possible_control_prefixes[] = { + "Control-", "C-", "CTRL-", (const char *)NULL +}; + +const char *_rl_possible_meta_prefixes[] = { + "Meta", "M-", (const char *)NULL +}; + +/* Conditionals. */ + +/* Calling programs set this to have their argv[0]. */ +const char *rl_readline_name = "other"; + +/* Stack of previous values of parsing_conditionalized_out. */ +static unsigned char *if_stack = (unsigned char *)NULL; +static int if_stack_depth; +static int if_stack_size; + +/* Push _rl_parsing_conditionalized_out, and set parser state based + on ARGS. */ +static int +parser_if (args) + char *args; +{ + register int i; + + /* Push parser state. */ + if (if_stack_depth + 1 >= if_stack_size) + { + if (!if_stack) + if_stack = (unsigned char *)xmalloc (if_stack_size = 20); + else + if_stack = (unsigned char *)xrealloc (if_stack, if_stack_size += 20); + } + if_stack[if_stack_depth++] = _rl_parsing_conditionalized_out; + + /* If parsing is turned off, then nothing can turn it back on except + for finding the matching endif. In that case, return right now. */ + if (_rl_parsing_conditionalized_out) + return 0; + + /* Isolate first argument. */ + for (i = 0; args[i] && !whitespace (args[i]); i++); + + if (args[i]) + args[i++] = '\0'; + + /* Handle "$if term=foo" and "$if mode=emacs" constructs. If this + isn't term=foo, or mode=emacs, then check to see if the first + word in ARGS is the same as the value stored in rl_readline_name. */ + if (rl_terminal_name && _rl_strnicmp (args, "term=", 5) == 0) + { + char *tem, *tname; + + /* Terminals like "aaa-60" are equivalent to "aaa". */ + tname = savestring (rl_terminal_name); + tem = strchr (tname, '-'); + if (tem) + *tem = '\0'; + + /* Test the `long' and `short' forms of the terminal name so that + if someone has a `sun-cmd' and does not want to have bindings + that will be executed if the terminal is a `sun', they can put + `$if term=sun-cmd' into their .inputrc. */ + _rl_parsing_conditionalized_out = _rl_stricmp (args + 5, tname) && + _rl_stricmp (args + 5, rl_terminal_name); + free (tname); + } +#if defined (VI_MODE) + else if (_rl_strnicmp (args, "mode=", 5) == 0) + { + int mode; + + if (_rl_stricmp (args + 5, "emacs") == 0) + mode = emacs_mode; + else if (_rl_stricmp (args + 5, "vi") == 0) + mode = vi_mode; + else + mode = no_mode; + + _rl_parsing_conditionalized_out = mode != rl_editing_mode; + } +#endif /* VI_MODE */ + /* Check to see if the first word in ARGS is the same as the + value stored in rl_readline_name. */ + else if (_rl_stricmp (args, rl_readline_name) == 0) + _rl_parsing_conditionalized_out = 0; + else + _rl_parsing_conditionalized_out = 1; + return 0; +} + +/* Invert the current parser state if there is anything on the stack. */ +static int +parser_else (args) + char *args __attribute__((unused)); +{ + register int i; + + if (if_stack_depth == 0) + { + _rl_init_file_error ("$else found without matching $if"); + return 0; + } + +#if 0 + /* Check the previous (n - 1) levels of the stack to make sure that + we haven't previously turned off parsing. */ + for (i = 0; i < if_stack_depth - 1; i++) +#else + /* Check the previous (n) levels of the stack to make sure that + we haven't previously turned off parsing. */ + for (i = 0; i < if_stack_depth; i++) +#endif + if (if_stack[i] == 1) + return 0; + + /* Invert the state of parsing if at top level. */ + _rl_parsing_conditionalized_out = !_rl_parsing_conditionalized_out; + return 0; +} + +/* Terminate a conditional, popping the value of + _rl_parsing_conditionalized_out from the stack. */ +static int +parser_endif (args) + char *args __attribute__((unused)); +{ + if (if_stack_depth) + _rl_parsing_conditionalized_out = if_stack[--if_stack_depth]; + else + _rl_init_file_error ("$endif without matching $if"); + return 0; +} + +static int +parser_include (args) + char *args; +{ + const char *old_init_file; + char *e; + int old_line_number, old_include_level, r; + + if (_rl_parsing_conditionalized_out) + return (0); + + old_init_file = current_readline_init_file; + old_line_number = current_readline_init_lineno; + old_include_level = current_readline_init_include_level; + + e = strchr (args, '\n'); + if (e) + *e = '\0'; + r = _rl_read_init_file ((const char *)args, old_include_level + 1); + + current_readline_init_file = old_init_file; + current_readline_init_lineno = old_line_number; + current_readline_init_include_level = old_include_level; + + return r; +} + +/* Associate textual names with actual functions. */ +static struct { + const char *name; + _rl_parser_func_t *function; +} parser_directives [] = { + { "if", parser_if }, + { "endif", parser_endif }, + { "else", parser_else }, + { "include", parser_include }, + { (char *)0x0, (_rl_parser_func_t *)0x0 } +}; + +/* Handle a parser directive. STATEMENT is the line of the directive + without any leading `$'. */ +static int +handle_parser_directive (statement) + char *statement; +{ + register int i; + char *directive, *args; + + /* Isolate the actual directive. */ + + /* Skip whitespace. */ + for (i = 0; whitespace (statement[i]); i++); + + directive = &statement[i]; + + for (; statement[i] && !whitespace (statement[i]); i++); + + if (statement[i]) + statement[i++] = '\0'; + + for (; statement[i] && whitespace (statement[i]); i++); + + args = &statement[i]; + + /* Lookup the command, and act on it. */ + for (i = 0; parser_directives[i].name; i++) + if (_rl_stricmp (directive, parser_directives[i].name) == 0) + { + (*parser_directives[i].function) (args); + return (0); + } + + /* display an error message about the unknown parser directive */ + _rl_init_file_error ("unknown parser directive"); + return (1); +} + +/* Read the binding command from STRING and perform it. + A key binding command looks like: Keyname: function-name\0, + a variable binding command looks like: set variable value. + A new-style keybinding looks like "\C-x\C-x": exchange-point-and-mark. */ +int +rl_parse_and_bind (string) + char *string; +{ + char *funname, *kname; + register int c, i; + int key, equivalency; + + while (string && whitespace (*string)) + string++; + + if (!string || !*string || *string == '#') + return 0; + + /* If this is a parser directive, act on it. */ + if (*string == '$') + { + handle_parser_directive (&string[1]); + return 0; + } + + /* If we aren't supposed to be parsing right now, then we're done. */ + if (_rl_parsing_conditionalized_out) + return 0; + + i = 0; + /* If this keyname is a complex key expression surrounded by quotes, + advance to after the matching close quote. This code allows the + backslash to quote characters in the key expression. */ + if (*string == '"') + { + int passc = 0; + + for (i = 1; (c = string[i]); i++) + { + if (passc) + { + passc = 0; + continue; + } + + if (c == '\\') + { + passc++; + continue; + } + + if (c == '"') + break; + } + /* If we didn't find a closing quote, abort the line. */ + if (string[i] == '\0') + { + _rl_init_file_error ("no closing `\"' in key binding"); + return 1; + } + } + + /* Advance to the colon (:) or whitespace which separates the two objects. */ + for (; (c = string[i]) && c != ':' && c != ' ' && c != '\t'; i++ ); + + equivalency = (c == ':' && string[i + 1] == '='); + + /* Mark the end of the command (or keyname). */ + if (string[i]) + string[i++] = '\0'; + + /* If doing assignment, skip the '=' sign as well. */ + if (equivalency) + string[i++] = '\0'; + + /* If this is a command to set a variable, then do that. */ + if (_rl_stricmp (string, "set") == 0) + { + char *var, *value, *e; + + var = string + i; + /* Make VAR point to start of variable name. */ + while (*var && whitespace (*var)) var++; + + /* Make VALUE point to start of value string. */ + value = var; + while (*value && !whitespace (*value)) value++; + if (*value) + *value++ = '\0'; + while (*value && whitespace (*value)) value++; + + /* Strip trailing whitespace from values to boolean variables. Temp + fix until I get a real quoted-string parser here. */ + i = find_boolean_var (var); + if (i >= 0) + { + /* remove trailing whitespace */ + e = value + strlen (value) - 1; + while (e >= value && whitespace (*e)) + e--; + e++; /* skip back to whitespace or EOS */ + if (*e && e >= value) + *e = '\0'; + } + + rl_variable_bind (var, value); + return 0; + } + + /* Skip any whitespace between keyname and funname. */ + for (; string[i] && whitespace (string[i]); i++); + funname = &string[i]; + + /* Now isolate funname. + For straight function names just look for whitespace, since + that will signify the end of the string. But this could be a + macro definition. In that case, the string is quoted, so skip + to the matching delimiter. We allow the backslash to quote the + delimiter characters in the macro body. */ + /* This code exists to allow whitespace in macro expansions, which + would otherwise be gobbled up by the next `for' loop.*/ + /* XXX - it may be desirable to allow backslash quoting only if " is + the quoted string delimiter, like the shell. */ + if (*funname == '\'' || *funname == '"') + { + int delimiter, passc; + + delimiter = string[i++]; + for (passc = 0; (c = string[i]); i++) + { + if (passc) + { + passc = 0; + continue; + } + + if (c == '\\') + { + passc = 1; + continue; + } + + if (c == delimiter) + break; + } + if (c) + i++; + } + + /* Advance to the end of the string. */ + for (; string[i] && !whitespace (string[i]); i++); + + /* No extra whitespace at the end of the string. */ + string[i] = '\0'; + + /* Handle equivalency bindings here. Make the left-hand side be exactly + whatever the right-hand evaluates to, including keymaps. */ + if (equivalency) + { + return 0; + } + + /* If this is a new-style key-binding, then do the binding with + rl_bind_keyseq (). Otherwise, let the older code deal with it. */ + if (*string == '"') + { + char *seq; + register int j, k, passc; + + seq = (char *)xmalloc (1 + strlen (string)); + for (j = 1, k = passc = 0; string[j]; j++) + { + /* Allow backslash to quote characters, but leave them in place. + This allows a string to end with a backslash quoting another + backslash, or with a backslash quoting a double quote. The + backslashes are left in place for rl_translate_keyseq (). */ + if (passc || (string[j] == '\\')) + { + seq[k++] = string[j]; + passc = !passc; + continue; + } + + if (string[j] == '"') + break; + + seq[k++] = string[j]; + } + seq[k] = '\0'; + + /* Binding macro? */ + if (*funname == '\'' || *funname == '"') + { + j = strlen (funname); + + /* Remove the delimiting quotes from each end of FUNNAME. */ + if (j && funname[j - 1] == *funname) + funname[j - 1] = '\0'; + + rl_macro_bind (seq, &funname[1], _rl_keymap); + } + else + rl_bind_keyseq (seq, rl_named_function (funname)); + + free (seq); + return 0; + } + + /* Get the actual character we want to deal with. */ + kname = strrchr (string, '-'); + if (!kname) + kname = string; + else + kname++; + + key = glean_key_from_name (kname); + + /* Add in control and meta bits. */ + if (substring_member_of_array (string, _rl_possible_control_prefixes)) + key = CTRL (_rl_to_upper (key)); + + if (substring_member_of_array (string, _rl_possible_meta_prefixes)) + key = META (key); + + /* Temporary. Handle old-style keyname with macro-binding. */ + if (*funname == '\'' || *funname == '"') + { + char useq[2]; + int fl = strlen (funname); + + useq[0] = key; useq[1] = '\0'; + if (fl && funname[fl - 1] == *funname) + funname[fl - 1] = '\0'; + + rl_macro_bind (useq, &funname[1], _rl_keymap); + } +#if defined (PREFIX_META_HACK) + /* Ugly, but working hack to keep prefix-meta around. */ + else if (_rl_stricmp (funname, "prefix-meta") == 0) + { + char seq[2]; + + seq[0] = key; + seq[1] = '\0'; + rl_generic_bind (ISKMAP, seq, (char *)emacs_meta_keymap, _rl_keymap); + } +#endif /* PREFIX_META_HACK */ + else + rl_bind_key (key, rl_named_function (funname)); + return 0; +} + +/* Simple structure for boolean readline variables (i.e., those that can + have one of two values; either "On" or 1 for truth, or "Off" or 0 for + false. */ + +#define V_SPECIAL 0x1 + +static struct { + const char *name; + int *value; + int flags; +} boolean_varlist [] = { + { "bind-tty-special-chars", &_rl_bind_stty_chars, 0 }, + { "blink-matching-paren", &rl_blink_matching_paren, V_SPECIAL }, + { "byte-oriented", &rl_byte_oriented, 0 }, + { "completion-ignore-case", &_rl_completion_case_fold, 0 }, + { "convert-meta", &_rl_convert_meta_chars_to_ascii, 0 }, + { "disable-completion", &rl_inhibit_completion, 0 }, + { "enable-keypad", &_rl_enable_keypad, 0 }, + { "expand-tilde", &rl_complete_with_tilde_expansion, 0 }, + { "history-preserve-point", &_rl_history_preserve_point, 0 }, + { "horizontal-scroll-mode", &_rl_horizontal_scroll_mode, 0 }, + { "input-meta", &_rl_meta_flag, 0 }, + { "mark-directories", &_rl_complete_mark_directories, 0 }, + { "mark-modified-lines", &_rl_mark_modified_lines, 0 }, + { "mark-symlinked-directories", &_rl_complete_mark_symlink_dirs, 0 }, + { "match-hidden-files", &_rl_match_hidden_files, 0 }, + { "meta-flag", &_rl_meta_flag, 0 }, + { "output-meta", &_rl_output_meta_chars, 0 }, + { "page-completions", &_rl_page_completions, 0 }, + { "prefer-visible-bell", &_rl_prefer_visible_bell, V_SPECIAL }, + { "print-completions-horizontally", &_rl_print_completions_horizontally, 0 }, + { "show-all-if-ambiguous", &_rl_complete_show_all, 0 }, + { "show-all-if-unmodified", &_rl_complete_show_unmodified, 0 }, +#if defined (VISIBLE_STATS) + { "visible-stats", &rl_visible_stats, 0 }, +#endif /* VISIBLE_STATS */ + { (char *)NULL, (int *)NULL, 0 } +}; + +static int +find_boolean_var (name) + const char *name; +{ + register int i; + + for (i = 0; boolean_varlist[i].name; i++) + if (_rl_stricmp (name, boolean_varlist[i].name) == 0) + return i; + return -1; +} + +/* Hooks for handling special boolean variables, where a + function needs to be called or another variable needs + to be changed when they're changed. */ +static void +hack_special_boolean_var (i) + int i; +{ + const char *name; + + name = boolean_varlist[i].name; + + if (_rl_stricmp (name, "blink-matching-paren") == 0) + _rl_enable_paren_matching (rl_blink_matching_paren); + else if (_rl_stricmp (name, "prefer-visible-bell") == 0) + { + if (_rl_prefer_visible_bell) + _rl_bell_preference = VISIBLE_BELL; + else + _rl_bell_preference = AUDIBLE_BELL; + } +} + +typedef int _rl_sv_func_t PARAMS((const char *)); + +/* These *must* correspond to the array indices for the appropriate + string variable. (Though they're not used right now.) */ +#define V_BELLSTYLE 0 +#define V_COMBEGIN 1 +#define V_EDITMODE 2 +#define V_ISRCHTERM 3 +#define V_KEYMAP 4 + +#define V_STRING 1 +#define V_INT 2 + +/* Forward declarations */ +static int sv_bell_style PARAMS((const char *)); +static int sv_combegin PARAMS((const char *)); +static int sv_compquery PARAMS((const char *)); +static int sv_editmode PARAMS((const char *)); +static int sv_isrchterm PARAMS((const char *)); +static int sv_keymap PARAMS((const char *)); + +static struct { + const char *name; + int flags; + _rl_sv_func_t *set_func; +} string_varlist[] = { + { "bell-style", V_STRING, sv_bell_style }, + { "comment-begin", V_STRING, sv_combegin }, + { "completion-query-items", V_INT, sv_compquery }, + { "editing-mode", V_STRING, sv_editmode }, + { "isearch-terminators", V_STRING, sv_isrchterm }, + { "keymap", V_STRING, sv_keymap }, + { (char *)NULL, 0, (_rl_sv_func_t*)NULL } +}; + +static int +find_string_var (name) + const char *name; +{ + register int i; + + for (i = 0; string_varlist[i].name; i++) + if (_rl_stricmp (name, string_varlist[i].name) == 0) + return i; + return -1; +} + +/* A boolean value that can appear in a `set variable' command is true if + the value is null or empty, `on' (case-insenstive), or "1". Any other + values result in 0 (false). */ +static int +bool_to_int (value) + const char *value; +{ + return (value == 0 || *value == '\0' || + (_rl_stricmp (value, "on") == 0) || + (value[0] == '1' && value[1] == '\0')); +} + +const char * +rl_variable_value (name) + const char *name; +{ + register int i; + + /* Check for simple variables first. */ + i = find_boolean_var (name); + if (i >= 0) + return (*boolean_varlist[i].value ? "on" : "off"); + + i = find_string_var (name); + if (i >= 0) + return (_rl_get_string_variable_value (string_varlist[i].name)); + + /* Unknown variable names return NULL. */ + return 0; +} + +int +rl_variable_bind (name, value) + const char *name, *value; +{ + register int i; + int v; + + /* Check for simple variables first. */ + i = find_boolean_var (name); + if (i >= 0) + { + *boolean_varlist[i].value = bool_to_int (value); + if (boolean_varlist[i].flags & V_SPECIAL) + hack_special_boolean_var (i); + return 0; + } + + i = find_string_var (name); + + /* For the time being, unknown variable names or string names without a + handler function are simply ignored. */ + if (i < 0 || string_varlist[i].set_func == 0) + return 0; + + v = (*string_varlist[i].set_func) (value); + return v; +} + +static int +sv_editmode (value) + const char *value; +{ + if (_rl_strnicmp (value, "vi", 2) == 0) + { +#if defined (VI_MODE) + _rl_keymap = vi_insertion_keymap; + rl_editing_mode = vi_mode; +#endif /* VI_MODE */ + return 0; + } + else if (_rl_strnicmp (value, "emacs", 5) == 0) + { + _rl_keymap = emacs_standard_keymap; + rl_editing_mode = emacs_mode; + return 0; + } + return 1; +} + +static int +sv_combegin (value) + const char *value; +{ + if (value && *value) + { + FREE (_rl_comment_begin); + _rl_comment_begin = savestring (value); + return 0; + } + return 1; +} + +static int +sv_compquery (value) + const char *value; +{ + int nval = 100; + + if (value && *value) + { + nval = atoi (value); + if (nval < 0) + nval = 0; + } + rl_completion_query_items = nval; + return 0; +} + +static int +sv_keymap (value) + const char *value; +{ + Keymap kmap; + + kmap = rl_get_keymap_by_name (value); + if (kmap) + { + rl_set_keymap (kmap); + return 0; + } + return 1; +} + +static int +sv_bell_style (value) + const char *value; +{ + if (value == 0 || *value == '\0') + _rl_bell_preference = AUDIBLE_BELL; + else if (_rl_stricmp (value, "none") == 0 || _rl_stricmp (value, "off") == 0) + _rl_bell_preference = NO_BELL; + else if (_rl_stricmp (value, "audible") == 0 || _rl_stricmp (value, "on") == 0) + _rl_bell_preference = AUDIBLE_BELL; + else if (_rl_stricmp (value, "visible") == 0) + _rl_bell_preference = VISIBLE_BELL; + else + return 1; + return 0; +} + +static int +sv_isrchterm (value) + const char *value; +{ + int beg, end, delim; + char *v; + + if (value == 0) + return 1; + + /* Isolate the value and translate it into a character string. */ + v = savestring (value); + FREE (_rl_isearch_terminators); + if (v[0] == '"' || v[0] == '\'') + { + delim = v[0]; + for (beg = end = 1; v[end] && v[end] != delim; end++) + ; + } + else + { + for (beg = end = 0; whitespace (v[end]) == 0; end++) + ; + } + + v[end] = '\0'; + + /* The value starts at v + beg. Translate it into a character string. */ + _rl_isearch_terminators = (char *)xmalloc (2 * strlen (v) + 1); + rl_translate_keyseq (v + beg, _rl_isearch_terminators, &end); + _rl_isearch_terminators[end] = '\0'; + + free (v); + return 0; +} + +/* Return the character which matches NAME. + For example, `Space' returns ' '. */ + +typedef struct { + const char *name; + int value; +} assoc_list; + +static assoc_list name_key_alist[] = { + { "DEL", 0x7f }, + { "ESC", '\033' }, + { "Escape", '\033' }, + { "LFD", '\n' }, + { "Newline", '\n' }, + { "RET", '\r' }, + { "Return", '\r' }, + { "Rubout", 0x7f }, + { "SPC", ' ' }, + { "Space", ' ' }, + { "Tab", 0x09 }, + { (char *)0x0, 0 } +}; + +static int +glean_key_from_name (name) + char *name; +{ + register int i; + + for (i = 0; name_key_alist[i].name; i++) + if (_rl_stricmp (name, name_key_alist[i].name) == 0) + return (name_key_alist[i].value); + + return (*(unsigned char *)name); /* XXX was return (*name) */ +} + +/* Auxiliary functions to manage keymaps. */ +static struct { + const char *name; + Keymap map; +} keymap_names[] = { + { "emacs", emacs_standard_keymap }, + { "emacs-standard", emacs_standard_keymap }, + { "emacs-meta", emacs_meta_keymap }, + { "emacs-ctlx", emacs_ctlx_keymap }, +#if defined (VI_MODE) + { "vi", vi_movement_keymap }, + { "vi-move", vi_movement_keymap }, + { "vi-command", vi_movement_keymap }, + { "vi-insert", vi_insertion_keymap }, +#endif /* VI_MODE */ + { (char *)0x0, (Keymap)0x0 } +}; + +Keymap +rl_get_keymap_by_name (name) + const char *name; +{ + register int i; + + for (i = 0; keymap_names[i].name; i++) + if (_rl_stricmp (name, keymap_names[i].name) == 0) + return (keymap_names[i].map); + return ((Keymap) NULL); +} + +char * +rl_get_keymap_name (map) + Keymap map; +{ + register int i; + for (i = 0; keymap_names[i].name; i++) + if (map == keymap_names[i].map) + return ((char *)keymap_names[i].name); + return ((char *)NULL); +} + +void +rl_set_keymap (map) + Keymap map; +{ + if (map) + _rl_keymap = map; +} + +Keymap +rl_get_keymap () +{ + return (_rl_keymap); +} + +void +rl_set_keymap_from_edit_mode () +{ + if (rl_editing_mode == emacs_mode) + _rl_keymap = emacs_standard_keymap; +#if defined (VI_MODE) + else if (rl_editing_mode == vi_mode) + _rl_keymap = vi_insertion_keymap; +#endif /* VI_MODE */ +} + +const char * +rl_get_keymap_name_from_edit_mode () +{ + if (rl_editing_mode == emacs_mode) + return "emacs"; +#if defined (VI_MODE) + else if (rl_editing_mode == vi_mode) + return "vi"; +#endif /* VI_MODE */ + else + return "none"; +} + +/* **************************************************************** */ +/* */ +/* Key Binding and Function Information */ +/* */ +/* **************************************************************** */ + +/* Each of the following functions produces information about the + state of keybindings and functions known to Readline. The info + is always printed to rl_outstream, and in such a way that it can + be read back in (i.e., passed to rl_parse_and_bind ()). */ + +/* Print the names of functions known to Readline. */ +void +rl_list_funmap_names () +{ + register int i; + const char **funmap_names; + + funmap_names = rl_funmap_names (); + + if (!funmap_names) + return; + + for (i = 0; funmap_names[i]; i++) + fprintf (rl_outstream, "%s\n", funmap_names[i]); + + free (funmap_names); +} + +static char * +_rl_get_keyname (key) + int key; +{ + char *keyname; + int i, c; + + keyname = (char *)xmalloc (8); + + c = key; + /* Since this is going to be used to write out keysequence-function + pairs for possible inclusion in an inputrc file, we don't want to + do any special meta processing on KEY. */ + +#if 1 + /* XXX - Experimental */ + /* We might want to do this, but the old version of the code did not. */ + + /* If this is an escape character, we don't want to do any more processing. + Just add the special ESC key sequence and return. */ + if (c == ESC) + { + keyname[0] = '\\'; + keyname[1] = 'e'; + keyname[2] = '\0'; + return keyname; + } +#endif + + /* RUBOUT is translated directly into \C-? */ + if (key == RUBOUT) + { + keyname[0] = '\\'; + keyname[1] = 'C'; + keyname[2] = '-'; + keyname[3] = '?'; + keyname[4] = '\0'; + return keyname; + } + + i = 0; + /* Now add special prefixes needed for control characters. This can + potentially change C. */ + if (CTRL_CHAR (c)) + { + keyname[i++] = '\\'; + keyname[i++] = 'C'; + keyname[i++] = '-'; + c = _rl_to_lower (UNCTRL (c)); + } + + /* XXX experimental code. Turn the characters that are not ASCII or + ISO Latin 1 (128 - 159) into octal escape sequences (\200 - \237). + This changes C. */ + if (c >= 128 && c <= 159) + { + keyname[i++] = '\\'; + keyname[i++] = '2'; + c -= 128; + keyname[i++] = (c / 8) + '0'; + c = (c % 8) + '0'; + } + + /* Now, if the character needs to be quoted with a backslash, do that. */ + if (c == '\\' || c == '"') + keyname[i++] = '\\'; + + /* Now add the key, terminate the string, and return it. */ + keyname[i++] = (char) c; + keyname[i] = '\0'; + + return keyname; +} + +/* Return a NULL terminated array of strings which represent the key + sequences that are used to invoke FUNCTION in MAP. */ +char ** +rl_invoking_keyseqs_in_map (function, map) + rl_command_func_t *function; + Keymap map; +{ + register int key; + char **result; + int result_index, result_size; + + result = (char **)NULL; + result_index = result_size = 0; + + for (key = 0; key < KEYMAP_SIZE; key++) + { + switch (map[key].type) + { + case ISMACR: + /* Macros match, if, and only if, the pointers are identical. + Thus, they are treated exactly like functions in here. */ + case ISFUNC: + /* If the function in the keymap is the one we are looking for, + then add the current KEY to the list of invoking keys. */ + if (map[key].function == function) + { + char *keyname; + + keyname = _rl_get_keyname (key); + + if (result_index + 2 > result_size) + { + result_size += 10; + result = (char **)xrealloc (result, result_size * sizeof (char *)); + } + + result[result_index++] = keyname; + result[result_index] = (char *)NULL; + } + break; + + case ISKMAP: + { + char **seqs; + register int i; + + /* Find the list of keyseqs in this map which have FUNCTION as + their target. Add the key sequences found to RESULT. */ + if (map[key].function) + seqs = + rl_invoking_keyseqs_in_map (function, FUNCTION_TO_KEYMAP (map, key)); + else + break; + + if (seqs == 0) + break; + + for (i = 0; seqs[i]; i++) + { + char *keyname = (char *)xmalloc (6 + strlen (seqs[i])); + + if (key == ESC) + { + /* If ESC is the meta prefix and we're converting chars + with the eighth bit set to ESC-prefixed sequences, then + we can use \M-. Otherwise we need to use the sequence + for ESC. */ + if (_rl_convert_meta_chars_to_ascii && map[ESC].type == ISKMAP) + sprintf (keyname, "\\M-"); + else + sprintf (keyname, "\\e"); + } + else if (CTRL_CHAR (key)) + sprintf (keyname, "\\C-%c", _rl_to_lower (UNCTRL (key))); + else if (key == RUBOUT) + sprintf (keyname, "\\C-?"); + else if (key == '\\' || key == '"') + { + keyname[0] = '\\'; + keyname[1] = (char) key; + keyname[2] = '\0'; + } + else + { + keyname[0] = (char) key; + keyname[1] = '\0'; + } + + strcat (keyname, seqs[i]); + free (seqs[i]); + + if (result_index + 2 > result_size) + { + result_size += 10; + result = (char **)xrealloc (result, result_size * sizeof (char *)); + } + + result[result_index++] = keyname; + result[result_index] = (char *)NULL; + } + + free (seqs); + } + break; + } + } + return (result); +} + +/* Return a NULL terminated array of strings which represent the key + sequences that can be used to invoke FUNCTION using the current keymap. */ +char ** +rl_invoking_keyseqs (function) + rl_command_func_t *function; +{ + return (rl_invoking_keyseqs_in_map (function, _rl_keymap)); +} + +/* Print all of the functions and their bindings to rl_outstream. If + PRINT_READABLY is non-zero, then print the output in such a way + that it can be read back in. */ +void +rl_function_dumper (print_readably) + int print_readably; +{ + register int i; + const char **names; + const char *name; + + names = rl_funmap_names (); + + fprintf (rl_outstream, "\n"); + + for (i = 0; (name = names[i]); i++) + { + rl_command_func_t *function; + char **invokers; + + function = rl_named_function (name); + invokers = rl_invoking_keyseqs_in_map (function, _rl_keymap); + + if (print_readably) + { + if (!invokers) + fprintf (rl_outstream, "# %s (not bound)\n", name); + else + { + register int j; + + for (j = 0; invokers[j]; j++) + { + fprintf (rl_outstream, "\"%s\": %s\n", + invokers[j], name); + free (invokers[j]); + } + + free (invokers); + } + } + else + { + if (!invokers) + fprintf (rl_outstream, "%s is not bound to any keys\n", + name); + else + { + register int j; + + fprintf (rl_outstream, "%s can be found on ", name); + + for (j = 0; invokers[j] && j < 5; j++) + { + fprintf (rl_outstream, "\"%s\"%s", invokers[j], + invokers[j + 1] ? ", " : ".\n"); + } + + if (j == 5 && invokers[j]) + fprintf (rl_outstream, "...\n"); + + for (j = 0; invokers[j]; j++) + free (invokers[j]); + + free (invokers); + } + } + } +} + +/* Print all of the current functions and their bindings to + rl_outstream. If an explicit argument is given, then print + the output in such a way that it can be read back in. */ +int +rl_dump_functions (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ + if (rl_dispatching) + fprintf (rl_outstream, "\r\n"); + rl_function_dumper (rl_explicit_arg); + rl_on_new_line (); + return (0); +} + +static void +_rl_macro_dumper_internal (print_readably, map, prefix) + int print_readably; + Keymap map; + char *prefix; +{ + register int key; + char *keyname, *out; + int prefix_len; + + for (key = 0; key < KEYMAP_SIZE; key++) + { + switch (map[key].type) + { + case ISMACR: + keyname = _rl_get_keyname (key); + out = _rl_untranslate_macro_value ((char *)map[key].function); + + if (print_readably) + fprintf (rl_outstream, "\"%s%s\": \"%s\"\n", prefix ? prefix : "", + keyname, + out ? out : ""); + else + fprintf (rl_outstream, "%s%s outputs %s\n", prefix ? prefix : "", + keyname, + out ? out : ""); + free (keyname); + free (out); + break; + case ISFUNC: + break; + case ISKMAP: + prefix_len = prefix ? strlen (prefix) : 0; + if (key == ESC) + { + keyname = (char *)xmalloc (3 + prefix_len); + if (prefix) + strcpy (keyname, prefix); + keyname[prefix_len] = '\\'; + keyname[prefix_len + 1] = 'e'; + keyname[prefix_len + 2] = '\0'; + } + else + { + keyname = _rl_get_keyname (key); + if (prefix) + { + out = (char *)xmalloc (strlen (keyname) + prefix_len + 1); + strcpy (out, prefix); + strcpy (out + prefix_len, keyname); + free (keyname); + keyname = out; + } + } + + _rl_macro_dumper_internal (print_readably, FUNCTION_TO_KEYMAP (map, key), keyname); + free (keyname); + break; + } + } +} + +void +rl_macro_dumper (print_readably) + int print_readably; +{ + _rl_macro_dumper_internal (print_readably, _rl_keymap, (char *)NULL); +} + +int +rl_dump_macros (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ + if (rl_dispatching) + fprintf (rl_outstream, "\r\n"); + rl_macro_dumper (rl_explicit_arg); + rl_on_new_line (); + return (0); +} + +static const char * +_rl_get_string_variable_value (name) + const char *name; +{ + static char numbuf[32]; + const char *ret; + char *tmp; + + if (_rl_stricmp (name, "bell-style") == 0) + { + switch (_rl_bell_preference) + { + case NO_BELL: + return "none"; + case VISIBLE_BELL: + return "visible"; + case AUDIBLE_BELL: + default: + return "audible"; + } + } + else if (_rl_stricmp (name, "comment-begin") == 0) + return (_rl_comment_begin ? _rl_comment_begin : RL_COMMENT_BEGIN_DEFAULT); + else if (_rl_stricmp (name, "completion-query-items") == 0) + { + sprintf (numbuf, "%d", rl_completion_query_items); + return (numbuf); + } + else if (_rl_stricmp (name, "editing-mode") == 0) + return (rl_get_keymap_name_from_edit_mode ()); + else if (_rl_stricmp (name, "isearch-terminators") == 0) + { + if (_rl_isearch_terminators == 0) + return 0; + tmp = _rl_untranslate_macro_value (_rl_isearch_terminators); + if (tmp) + { + strncpy (numbuf, tmp, sizeof (numbuf) - 1); + free (tmp); + numbuf[sizeof(numbuf) - 1] = '\0'; + } + else + numbuf[0] = '\0'; + return numbuf; + } + else if (_rl_stricmp (name, "keymap") == 0) + { + ret = rl_get_keymap_name (_rl_keymap); + if (ret == 0) + ret = rl_get_keymap_name_from_edit_mode (); + return (ret ? ret : "none"); + } + else + return (0); +} + +void +rl_variable_dumper (print_readably) + int print_readably; +{ + int i; + const char *v; + + for (i = 0; boolean_varlist[i].name; i++) + { + if (print_readably) + fprintf (rl_outstream, "set %s %s\n", boolean_varlist[i].name, + *boolean_varlist[i].value ? "on" : "off"); + else + fprintf (rl_outstream, "%s is set to `%s'\n", boolean_varlist[i].name, + *boolean_varlist[i].value ? "on" : "off"); + } + + for (i = 0; string_varlist[i].name; i++) + { + v = _rl_get_string_variable_value (string_varlist[i].name); + if (v == 0) /* _rl_isearch_terminators can be NULL */ + continue; + if (print_readably) + fprintf (rl_outstream, "set %s %s\n", string_varlist[i].name, v); + else + fprintf (rl_outstream, "%s is set to `%s'\n", string_varlist[i].name, v); + } +} + +/* Print all of the current variables and their values to + rl_outstream. If an explicit argument is given, then print + the output in such a way that it can be read back in. */ +int +rl_dump_variables (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ + if (rl_dispatching) + fprintf (rl_outstream, "\r\n"); + rl_variable_dumper (rl_explicit_arg); + rl_on_new_line (); + return (0); +} + +/* Return non-zero if any members of ARRAY are a substring in STRING. */ +static int +substring_member_of_array (string, array) + char *string; + const char **array; +{ + while (*array) + { + if (_rl_strindex (string, *array)) + return (1); + array++; + } + return (0); +} diff --git a/extra/readline/callback.c b/extra/readline/callback.c new file mode 100644 index 00000000000..08c2f0ce80b --- /dev/null +++ b/extra/readline/callback.c @@ -0,0 +1,258 @@ +/* callback.c -- functions to use readline as an X `callback' mechanism. */ + +/* Copyright (C) 1987-2005 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include "rlconf.h" + +#if defined (READLINE_CALLBACKS) + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif + +#include <stdio.h> + +/* System-specific feature definitions and include files. */ +#include "rldefs.h" +#include "readline.h" +#include "rlprivate.h" +#include "xmalloc.h" + +/* Private data for callback registration functions. See comments in + rl_callback_read_char for more details. */ +_rl_callback_func_t *_rl_callback_func = 0; +_rl_callback_generic_arg *_rl_callback_data = 0; + +/* **************************************************************** */ +/* */ +/* Callback Readline Functions */ +/* */ +/* **************************************************************** */ + +/* Allow using readline in situations where a program may have multiple + things to handle at once, and dispatches them via select(). Call + rl_callback_handler_install() with the prompt and a function to call + whenever a complete line of input is ready. The user must then + call rl_callback_read_char() every time some input is available, and + rl_callback_read_char() will call the user's function with the complete + text read in at each end of line. The terminal is kept prepped and + signals handled all the time, except during calls to the user's function. */ + +rl_vcpfunc_t *rl_linefunc; /* user callback function */ +static int in_handler; /* terminal_prepped and signals set? */ + +/* Make sure the terminal is set up, initialize readline, and prompt. */ +static void +_rl_callback_newline () +{ + rl_initialize (); + + if (in_handler == 0) + { + in_handler = 1; + + if (rl_prep_term_function) + (*rl_prep_term_function) (_rl_meta_flag); + +#if defined (HANDLE_SIGNALS) + rl_set_signals (); +#endif + } + + readline_internal_setup (); +} + +/* Install a readline handler, set up the terminal, and issue the prompt. */ +void +rl_callback_handler_install (prompt, linefunc) + const char *prompt; + rl_vcpfunc_t *linefunc; +{ + rl_set_prompt (prompt); + RL_SETSTATE (RL_STATE_CALLBACK); + rl_linefunc = linefunc; + _rl_callback_newline (); +} + +/* Read one character, and dispatch to the handler if it ends the line. */ +void +rl_callback_read_char () +{ + char *line; + int eof, jcode; + static procenv_t olevel; + + if (rl_linefunc == NULL) + { + fprintf (stderr, "readline: readline_callback_read_char() called with no handler!\r\n"); + abort (); + } + + memcpy ((void *)olevel, (void *)readline_top_level, sizeof (procenv_t)); + jcode = setjmp (readline_top_level); + if (jcode) + { + (*rl_redisplay_function) (); + _rl_want_redisplay = 0; + memcpy ((void *)readline_top_level, (void *)olevel, sizeof (procenv_t)); + return; + } + + do + { + if (RL_ISSTATE (RL_STATE_ISEARCH)) + { + eof = _rl_isearch_callback (_rl_iscxt); + if (eof == 0 && (RL_ISSTATE (RL_STATE_ISEARCH) == 0) && RL_ISSTATE (RL_STATE_INPUTPENDING)) + rl_callback_read_char (); + + return; + } + else if (RL_ISSTATE (RL_STATE_NSEARCH)) + { + eof = _rl_nsearch_callback (_rl_nscxt); + return; + } + else if (RL_ISSTATE (RL_STATE_NUMERICARG)) + { + eof = _rl_arg_callback (_rl_argcxt); + if (eof == 0 && (RL_ISSTATE (RL_STATE_NUMERICARG) == 0) && RL_ISSTATE (RL_STATE_INPUTPENDING)) + rl_callback_read_char (); + /* XXX - this should handle _rl_last_command_was_kill better */ + else if (RL_ISSTATE (RL_STATE_NUMERICARG) == 0) + _rl_internal_char_cleanup (); + + return; + } + else if (RL_ISSTATE (RL_STATE_MULTIKEY)) + { + eof = _rl_dispatch_callback (_rl_kscxt); /* For now */ + while ((eof == -1 || eof == -2) && RL_ISSTATE (RL_STATE_MULTIKEY) && _rl_kscxt && (_rl_kscxt->flags & KSEQ_DISPATCHED)) + eof = _rl_dispatch_callback (_rl_kscxt); + if (RL_ISSTATE (RL_STATE_MULTIKEY) == 0) + { + _rl_internal_char_cleanup (); + _rl_want_redisplay = 1; + } + } + else if (_rl_callback_func) + { + /* This allows functions that simply need to read an additional + character (like quoted-insert) to register a function to be + called when input is available. _rl_callback_data is simply a + pointer to a struct that has the argument count originally + passed to the registering function and space for any additional + parameters. */ + eof = (*_rl_callback_func) (_rl_callback_data); + /* If the function `deregisters' itself, make sure the data is + cleaned up. */ + if (_rl_callback_func == 0) + { + if (_rl_callback_data) + { + _rl_callback_data_dispose (_rl_callback_data); + _rl_callback_data = 0; + } + _rl_internal_char_cleanup (); + } + } + else + eof = readline_internal_char (); + + if (rl_done == 0 && _rl_want_redisplay) + { + (*rl_redisplay_function) (); + _rl_want_redisplay = 0; + } + + if (rl_done) + { + line = readline_internal_teardown (eof); + + if (rl_deprep_term_function) + (*rl_deprep_term_function) (); +#if defined (HANDLE_SIGNALS) + rl_clear_signals (); +#endif + in_handler = 0; + (*rl_linefunc) (line); + + /* If the user did not clear out the line, do it for him. */ + if (rl_line_buffer[0]) + _rl_init_line_state (); + + /* Redisplay the prompt if readline_handler_{install,remove} + not called. */ + if (in_handler == 0 && rl_linefunc) + _rl_callback_newline (); + } + } + while (rl_pending_input || _rl_pushed_input_available () || RL_ISSTATE (RL_STATE_MACROINPUT)); +} + +/* Remove the handler, and make sure the terminal is in its normal state. */ +void +rl_callback_handler_remove () +{ + rl_linefunc = NULL; + RL_UNSETSTATE (RL_STATE_CALLBACK); + if (in_handler) + { + in_handler = 0; + if (rl_deprep_term_function) + (*rl_deprep_term_function) (); +#if defined (HANDLE_SIGNALS) + rl_clear_signals (); +#endif + } +} + +_rl_callback_generic_arg * +_rl_callback_data_alloc (count) + int count; +{ + _rl_callback_generic_arg *arg; + + arg = (_rl_callback_generic_arg *)xmalloc (sizeof (_rl_callback_generic_arg)); + arg->count = count; + + arg->i1 = arg->i2 = 0; + + return arg; +} + +void _rl_callback_data_dispose (arg) + _rl_callback_generic_arg *arg; +{ + if (arg) + free (arg); +} + +#endif diff --git a/extra/readline/chardefs.h b/extra/readline/chardefs.h new file mode 100644 index 00000000000..6aa185d9383 --- /dev/null +++ b/extra/readline/chardefs.h @@ -0,0 +1,166 @@ +/* chardefs.h -- Character definitions for readline. */ + +/* Copyright (C) 1994 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#ifndef _CHARDEFS_H_ +#define _CHARDEFS_H_ + +#include <ctype.h> + +#if defined (HAVE_CONFIG_H) +# if defined (HAVE_STRING_H) +# if ! defined (STDC_HEADERS) && defined (HAVE_MEMORY_H) +# include <memory.h> +# endif +# include <string.h> +# endif /* HAVE_STRING_H */ +# if defined (HAVE_STRINGS_H) +# include <strings.h> +# endif /* HAVE_STRINGS_H */ +#else +# include <string.h> +#endif /* !HAVE_CONFIG_H */ + +#ifndef whitespace +#define whitespace(c) (((c) == ' ') || ((c) == '\t')) +#endif + +#ifdef CTRL +# undef CTRL +#endif +#ifdef UNCTRL +# undef UNCTRL +#endif + +/* Some character stuff. */ +#define control_character_threshold 0x020 /* Smaller than this is control. */ +#define control_character_mask 0x1f /* 0x20 - 1 */ +#define meta_character_threshold 0x07f /* Larger than this is Meta. */ +#define control_character_bit 0x40 /* 0x000000, must be off. */ +#define meta_character_bit 0x080 /* x0000000, must be on. */ +#define largest_char 255 /* Largest character value. */ + +#define CTRL_CHAR(c) ((c) < control_character_threshold && (((c) & 0x80) == 0)) +#define META_CHAR(c) ((c) > meta_character_threshold && (c) <= largest_char) +#define META_CHAR_FOR_UCHAR(c) ((c) > meta_character_threshold) + +#define CTRL(c) ((c) & control_character_mask) +#define META(c) ((c) | meta_character_bit) + +#define UNMETA(c) ((c) & (~meta_character_bit)) +#define UNCTRL(c) _rl_to_upper(((c)|control_character_bit)) + +#if defined STDC_HEADERS || (!defined (isascii) && !defined (HAVE_ISASCII)) +# define IN_CTYPE_DOMAIN(c) 1 +#else +# define IN_CTYPE_DOMAIN(c) isascii(c) +#endif + +#if !defined (isxdigit) && !defined (HAVE_ISXDIGIT) +# define isxdigit(c) (isdigit((c)) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F')) +#endif + +#if defined (CTYPE_NON_ASCII) +# define NON_NEGATIVE(c) 1 +#else +# define NON_NEGATIVE(c) ((unsigned char)(c) == (c)) +#endif + +/* Some systems define these; we want our definitions. */ +#undef ISPRINT + +/* Beware: these only work with single-byte ASCII characters. */ + +#define ISALNUM(c) (IN_CTYPE_DOMAIN (c) && isalnum (c)) +#define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c)) +#define ISDIGIT(c) (IN_CTYPE_DOMAIN (c) && isdigit (c)) +#define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c)) +#define ISPRINT(c) (IN_CTYPE_DOMAIN (c) && isprint (c)) +#define ISUPPER(c) (IN_CTYPE_DOMAIN (c) && isupper (c)) +#define ISXDIGIT(c) (IN_CTYPE_DOMAIN (c) && isxdigit (c)) + +#define _rl_lowercase_p(c) (NON_NEGATIVE(c) && ISLOWER(c)) +#define _rl_uppercase_p(c) (NON_NEGATIVE(c) && ISUPPER(c)) +#define _rl_digit_p(c) ((c) >= '0' && (c) <= '9') + +#define _rl_pure_alphabetic(c) (NON_NEGATIVE(c) && ISALPHA(c)) +#define ALPHABETIC(c) (NON_NEGATIVE(c) && ISALNUM(c)) + +#ifndef _rl_to_upper +# define _rl_to_upper(c) (_rl_lowercase_p(c) ? toupper((unsigned char)c) : (c)) +# define _rl_to_lower(c) (_rl_uppercase_p(c) ? tolower((unsigned char)c) : (c)) +#endif + +#ifndef _rl_digit_value +# define _rl_digit_value(x) ((x) - '0') +#endif + +#ifndef _rl_isident +# define _rl_isident(c) (ISALNUM(c) || (c) == '_') +#endif + +#ifndef ISOCTAL +# define ISOCTAL(c) ((c) >= '0' && (c) <= '7') +#endif +#define OCTVALUE(c) ((c) - '0') + +#define HEXVALUE(c) \ + (((c) >= 'a' && (c) <= 'f') \ + ? (c)-'a'+10 \ + : (c) >= 'A' && (c) <= 'F' ? (c)-'A'+10 : (c)-'0') + +#ifndef NEWLINE +#define NEWLINE '\n' +#endif + +#ifndef RETURN +#define RETURN CTRL('M') +#endif + +#ifndef RUBOUT +#define RUBOUT 0x7f +#endif + +#ifndef TAB +#define TAB '\t' +#endif + +#ifdef ABORT_CHAR +#undef ABORT_CHAR +#endif +#define ABORT_CHAR CTRL('G') + +#ifdef PAGE +#undef PAGE +#endif +#define PAGE CTRL('L') + +#ifdef SPACE +#undef SPACE +#endif +#define SPACE ' ' /* XXX - was 0x20 */ + +#ifdef ESC +#undef ESC +#endif +#define ESC CTRL('[') + +#endif /* _CHARDEFS_H_ */ diff --git a/extra/readline/compat.c b/extra/readline/compat.c new file mode 100644 index 00000000000..c16771b8f29 --- /dev/null +++ b/extra/readline/compat.c @@ -0,0 +1,113 @@ +/* compat.c -- backwards compatibility functions. */ + +/* Copyright (C) 2000 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <stdio.h> + +#include "rlstdc.h" +#include "rltypedefs.h" + +extern void rl_free_undo_list PARAMS((void)); +extern int rl_maybe_save_line PARAMS((void)); +extern int rl_maybe_unsave_line PARAMS((void)); +extern int rl_maybe_replace_line PARAMS((void)); + +extern int rl_crlf PARAMS((void)); +extern int rl_ding PARAMS((void)); +extern int rl_alphabetic PARAMS((int)); + +extern char **rl_completion_matches PARAMS((const char *, rl_compentry_func_t *)); +extern char *rl_username_completion_function PARAMS((const char *, int)); +extern char *rl_filename_completion_function PARAMS((const char *, int)); + +/* Provide backwards-compatible entry points for old function names. */ + +void +free_undo_list () +{ + rl_free_undo_list (); +} + +int +maybe_replace_line () +{ + return rl_maybe_replace_line (); +} + +int +maybe_save_line () +{ + return rl_maybe_save_line (); +} + +int +maybe_unsave_line () +{ + return rl_maybe_unsave_line (); +} + +int +ding () +{ + return rl_ding (); +} + +int +crlf () +{ + return rl_crlf (); +} + +int +alphabetic (c) + int c; +{ + return rl_alphabetic (c); +} + +char ** +completion_matches (s, f) + const char *s; + rl_compentry_func_t *f; +{ + return rl_completion_matches (s, f); +} + +char * +username_completion_function (s, i) + const char *s; + int i; +{ + return rl_username_completion_function (s, i); +} + +char * +filename_completion_function (s, i) + const char *s; + int i; +{ + return rl_filename_completion_function (s, i); +} diff --git a/extra/readline/complete.c b/extra/readline/complete.c new file mode 100644 index 00000000000..b6c20789da4 --- /dev/null +++ b/extra/readline/complete.c @@ -0,0 +1,2223 @@ +/* complete.c -- filename completion for readline. */ + +/* Copyright (C) 1987-2005 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <sys/types.h> +#include <fcntl.h> +/* FreeBSD 5.3 will not declare u_int in sys/types.h, file.h needs it */ +#if defined (HAVE_SYS_FILE_H) && !defined(__FreeBSD__) +# include <sys/file.h> +#endif + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#include <stdio.h> + +#include <errno.h> +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +#if defined (HAVE_PWD_H) +#include <pwd.h> +#endif + +#include "posixdir.h" +#include "posixstat.h" + +/* System-specific feature definitions and include files. */ +#include "rldefs.h" +#include "rlmbutil.h" + +/* Some standard library routines. */ +#include "readline.h" +#include "xmalloc.h" +#include "rlprivate.h" + +#ifdef __STDC__ +typedef int QSFUNC (const void *, const void *); +#else +typedef int QSFUNC (); +#endif + +#ifdef HAVE_LSTAT +# define LSTAT lstat +#else +# define LSTAT stat +#endif + +/* Unix version of a hidden file. Could be different on other systems. */ +#define HIDDEN_FILE(fname) ((fname)[0] == '.') + +/* Most systems don't declare getpwent in <pwd.h> if _POSIX_SOURCE is + defined. */ +#if defined (HAVE_GETPWENT) && (!defined (HAVE_GETPW_DECLS) || defined (_POSIX_SOURCE)) +extern struct passwd *getpwent PARAMS((void)); +#endif /* HAVE_GETPWENT && (!HAVE_GETPW_DECLS || _POSIX_SOURCE) */ + +/* If non-zero, then this is the address of a function to call when + completing a word would normally display the list of possible matches. + This function is called instead of actually doing the display. + It takes three arguments: (char **matches, int num_matches, int max_length) + where MATCHES is the array of strings that matched, NUM_MATCHES is the + number of strings in that array, and MAX_LENGTH is the length of the + longest string in that array. */ +rl_compdisp_func_t *rl_completion_display_matches_hook = (rl_compdisp_func_t *)NULL; + +#if defined (VISIBLE_STATS) +# if !defined (X_OK) +# define X_OK 1 +# endif +static int stat_char PARAMS((char *)); +#endif + +static int path_isdir PARAMS((const char *)); + +static char *rl_quote_filename PARAMS((char *, int, char *)); + +static void set_completion_defaults PARAMS((int)); +static int get_y_or_n PARAMS((int)); +static int _rl_internal_pager PARAMS((int)); +static char *printable_part PARAMS((char *)); +static int fnwidth PARAMS((const char *)); +static int fnprint PARAMS((const char *)); +static int print_filename PARAMS((char *, char *)); + +static char **gen_completion_matches PARAMS((char *, int, int, rl_compentry_func_t *, int, int)); + +static char **remove_duplicate_matches PARAMS((char **)); +static void insert_match PARAMS((char *, int, int, char *)); +static int append_to_match PARAMS((char *, int, int, int)); +static void insert_all_matches PARAMS((char **, int, char *)); +static void display_matches PARAMS((char **)); +static int compute_lcd_of_matches PARAMS((char **, int, const char *)); +static int postprocess_matches PARAMS((char ***, int)); + +static char *make_quoted_replacement PARAMS((char *, int, char *)); + +/* **************************************************************** */ +/* */ +/* Completion matching, from readline's point of view. */ +/* */ +/* **************************************************************** */ + +/* Variables known only to the readline library. */ + +/* If non-zero, non-unique completions always show the list of matches. */ +int _rl_complete_show_all = 0; + +/* If non-zero, non-unique completions show the list of matches, unless it + is not possible to do partial completion and modify the line. */ +int _rl_complete_show_unmodified = 0; + +/* If non-zero, completed directory names have a slash appended. */ +int _rl_complete_mark_directories = 1; + +/* If non-zero, the symlinked directory completion behavior introduced in + readline-4.2a is disabled, and symlinks that point to directories have + a slash appended (subject to the value of _rl_complete_mark_directories). + This is user-settable via the mark-symlinked-directories variable. */ +int _rl_complete_mark_symlink_dirs = 0; + +/* If non-zero, completions are printed horizontally in alphabetical order, + like `ls -x'. */ +int _rl_print_completions_horizontally; + +/* Non-zero means that case is not significant in filename completion. */ +#if defined (__MSDOS__) && !defined (__DJGPP__) +int _rl_completion_case_fold = 1; +#else +int _rl_completion_case_fold; +#endif + +/* If non-zero, don't match hidden files (filenames beginning with a `.' on + Unix) when doing filename completion. */ +int _rl_match_hidden_files = 1; + +/* Global variables available to applications using readline. */ + +#if defined (VISIBLE_STATS) +/* Non-zero means add an additional character to each filename displayed + during listing completion iff rl_filename_completion_desired which helps + to indicate the type of file being listed. */ +int rl_visible_stats = 0; +#endif /* VISIBLE_STATS */ + +/* If non-zero, then this is the address of a function to call when + completing on a directory name. The function is called with + the address of a string (the current directory name) as an arg. */ +rl_icppfunc_t *rl_directory_completion_hook = (rl_icppfunc_t *)NULL; + +rl_icppfunc_t *rl_directory_rewrite_hook = (rl_icppfunc_t *)NULL; + +/* Non-zero means readline completion functions perform tilde expansion. */ +int rl_complete_with_tilde_expansion = 0; + +/* Pointer to the generator function for completion_matches (). + NULL means to use rl_filename_completion_function (), the default filename + completer. */ +rl_compentry_func_t *rl_completion_entry_function = (rl_compentry_func_t *)NULL; + +/* Pointer to alternative function to create matches. + Function is called with TEXT, START, and END. + START and END are indices in RL_LINE_BUFFER saying what the boundaries + of TEXT are. + If this function exists and returns NULL then call the value of + rl_completion_entry_function to try to match, otherwise use the + array of strings returned. */ +rl_completion_func_t *rl_attempted_completion_function = (rl_completion_func_t *)NULL; + +/* Non-zero means to suppress normal filename completion after the + user-specified completion function has been called. */ +int rl_attempted_completion_over = 0; + +/* Set to a character indicating the type of completion being performed + by rl_complete_internal, available for use by application completion + functions. */ +int rl_completion_type = 0; + +/* Up to this many items will be displayed in response to a + possible-completions call. After that, we ask the user if + she is sure she wants to see them all. A negative value means + don't ask. */ +int rl_completion_query_items = 100; + +int _rl_page_completions = 1; + +/* The basic list of characters that signal a break between words for the + completer routine. The contents of this variable is what breaks words + in the shell, i.e. " \t\n\"\\'`@$><=" */ +const char *rl_basic_word_break_characters = " \t\n\"\\'`@$><=;|&{("; /* }) */ + +/* List of basic quoting characters. */ +const char *rl_basic_quote_characters = "\"'"; + +/* The list of characters that signal a break between words for + rl_complete_internal. The default list is the contents of + rl_basic_word_break_characters. */ +/*const*/ char *rl_completer_word_break_characters = (/*const*/ char *)NULL; + +/* Hook function to allow an application to set the completion word + break characters before readline breaks up the line. Allows + position-dependent word break characters. */ +rl_cpvfunc_t *rl_completion_word_break_hook = (rl_cpvfunc_t *)NULL; + +/* List of characters which can be used to quote a substring of the line. + Completion occurs on the entire substring, and within the substring + rl_completer_word_break_characters are treated as any other character, + unless they also appear within this list. */ +const char *rl_completer_quote_characters = (const char *)NULL; + +/* List of characters that should be quoted in filenames by the completer. */ +const char *rl_filename_quote_characters = (const char *)NULL; + +/* List of characters that are word break characters, but should be left + in TEXT when it is passed to the completion function. The shell uses + this to help determine what kind of completing to do. */ +const char *rl_special_prefixes = (const char *)NULL; + +/* If non-zero, then disallow duplicates in the matches. */ +int rl_ignore_completion_duplicates = 1; + +/* Non-zero means that the results of the matches are to be treated + as filenames. This is ALWAYS zero on entry, and can only be changed + within a completion entry finder function. */ +int rl_filename_completion_desired = 0; + +/* Non-zero means that the results of the matches are to be quoted using + double quotes (or an application-specific quoting mechanism) if the + filename contains any characters in rl_filename_quote_chars. This is + ALWAYS non-zero on entry, and can only be changed within a completion + entry finder function. */ +int rl_filename_quoting_desired = 1; + +/* This function, if defined, is called by the completer when real + filename completion is done, after all the matching names have been + generated. It is passed a (char**) known as matches in the code below. + It consists of a NULL-terminated array of pointers to potential + matching strings. The 1st element (matches[0]) is the maximal + substring that is common to all matches. This function can re-arrange + the list of matches as required, but all elements of the array must be + free()'d if they are deleted. The main intent of this function is + to implement FIGNORE a la SunOS csh. */ +rl_compignore_func_t *rl_ignore_some_completions_function = (rl_compignore_func_t *)NULL; + +/* Set to a function to quote a filename in an application-specific fashion. + Called with the text to quote, the type of match found (single or multiple) + and a pointer to the quoting character to be used, which the function can + reset if desired. */ +rl_quote_func_t *rl_filename_quoting_function = rl_quote_filename; + +/* Function to call to remove quoting characters from a filename. Called + before completion is attempted, so the embedded quotes do not interfere + with matching names in the file system. Readline doesn't do anything + with this; it's set only by applications. */ +rl_dequote_func_t *rl_filename_dequoting_function = (rl_dequote_func_t *)NULL; + +/* Function to call to decide whether or not a word break character is + quoted. If a character is quoted, it does not break words for the + completer. */ +rl_linebuf_func_t *rl_char_is_quoted_p = (rl_linebuf_func_t *)NULL; + +/* If non-zero, the completion functions don't append anything except a + possible closing quote. This is set to 0 by rl_complete_internal and + may be changed by an application-specific completion function. */ +int rl_completion_suppress_append = 0; + +/* Character appended to completed words when at the end of the line. The + default is a space. */ +int rl_completion_append_character = ' '; + +/* If non-zero, the completion functions don't append any closing quote. + This is set to 0 by rl_complete_internal and may be changed by an + application-specific completion function. */ +int rl_completion_suppress_quote = 0; + +/* Set to any quote character readline thinks it finds before any application + completion function is called. */ +int rl_completion_quote_character; + +/* Set to a non-zero value if readline found quoting anywhere in the word to + be completed; set before any application completion function is called. */ +int rl_completion_found_quote; + +/* If non-zero, a slash will be appended to completed filenames that are + symbolic links to directory names, subject to the value of the + mark-directories variable (which is user-settable). This exists so + that application completion functions can override the user's preference + (set via the mark-symlinked-directories variable) if appropriate. + It's set to the value of _rl_complete_mark_symlink_dirs in + rl_complete_internal before any application-specific completion + function is called, so without that function doing anything, the user's + preferences are honored. */ +int rl_completion_mark_symlink_dirs; + +/* If non-zero, inhibit completion (temporarily). */ +int rl_inhibit_completion; + +/* Variables local to this file. */ + +/* Local variable states what happened during the last completion attempt. */ +static int completion_changed_buffer; + +/*************************************/ +/* */ +/* Bindable completion functions */ +/* */ +/*************************************/ + +/* Complete the word at or before point. You have supplied the function + that does the initial simple matching selection algorithm (see + rl_completion_matches ()). The default is to do filename completion. */ +int +rl_complete (ignore, invoking_key) + int ignore, invoking_key; +{ + if (rl_inhibit_completion) + return (_rl_insert_char (ignore, invoking_key)); + else if (rl_last_func == rl_complete && !completion_changed_buffer) + return (rl_complete_internal ('?')); + else if (_rl_complete_show_all) + return (rl_complete_internal ('!')); + else if (_rl_complete_show_unmodified) + return (rl_complete_internal ('@')); + else + return (rl_complete_internal (TAB)); +} + +/* List the possible completions. See description of rl_complete (). */ +int +rl_possible_completions (ignore, invoking_key) + int ignore __attribute__((unused)), invoking_key __attribute__((unused)); +{ + return (rl_complete_internal ('?')); +} + +int +rl_insert_completions (ignore, invoking_key) + int ignore __attribute__((unused)), invoking_key __attribute__((unused)); +{ + return (rl_complete_internal ('*')); +} + +/* Return the correct value to pass to rl_complete_internal performing + the same tests as rl_complete. This allows consecutive calls to an + application's completion function to list possible completions and for + an application-specific completion function to honor the + show-all-if-ambiguous readline variable. */ +int +rl_completion_mode (cfunc) + rl_command_func_t *cfunc; +{ + if (rl_last_func == cfunc && !completion_changed_buffer) + return '?'; + else if (_rl_complete_show_all) + return '!'; + else if (_rl_complete_show_unmodified) + return '@'; + else + return TAB; +} + +/************************************/ +/* */ +/* Completion utility functions */ +/* */ +/************************************/ + +/* Set default values for readline word completion. These are the variables + that application completion functions can change or inspect. */ +static void +set_completion_defaults (what_to_do) + int what_to_do; +{ + /* Only the completion entry function can change these. */ + rl_filename_completion_desired = 0; + rl_filename_quoting_desired = 1; + rl_completion_type = what_to_do; + rl_completion_suppress_append = rl_completion_suppress_quote = 0; + + /* The completion entry function may optionally change this. */ + rl_completion_mark_symlink_dirs = _rl_complete_mark_symlink_dirs; +} + +/* The user must press "y" or "n". Non-zero return means "y" pressed. */ +static int +get_y_or_n (for_pager) + int for_pager; +{ + int c; + + for (;;) + { + RL_SETSTATE(RL_STATE_MOREINPUT); + c = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); + + if (c == 'y' || c == 'Y' || c == ' ') + return (1); + if (c == 'n' || c == 'N' || c == RUBOUT) + return (0); + if (c == ABORT_CHAR) + _rl_abort_internal (); + if (for_pager && (c == NEWLINE || c == RETURN)) + return (2); + if (for_pager && (c == 'q' || c == 'Q')) + return (0); + rl_ding (); + } +} + +static int +_rl_internal_pager (lines) + int lines; +{ + int i; + + fprintf (rl_outstream, "--More--"); + fflush (rl_outstream); + i = get_y_or_n (1); + _rl_erase_entire_line (); + if (i == 0) + return -1; + else if (i == 2) + return (lines - 1); + else + return 0; +} + +static int +path_isdir (filename) + const char *filename; +{ + struct stat finfo; + + return (stat (filename, &finfo) == 0 && S_ISDIR (finfo.st_mode)); +} + +#if defined (VISIBLE_STATS) +/* Return the character which best describes FILENAME. + `@' for symbolic links + `/' for directories + `*' for executables + `=' for sockets + `|' for FIFOs + `%' for character special devices + `#' for block special devices */ +static int +stat_char (filename) + char *filename; +{ + struct stat finfo; + int character, r; + +#if defined (HAVE_LSTAT) && defined (S_ISLNK) + r = lstat (filename, &finfo); +#else + r = stat (filename, &finfo); +#endif + + if (r == -1) + return (0); + + character = 0; + if (S_ISDIR (finfo.st_mode)) + character = '/'; +#if defined (S_ISCHR) + else if (S_ISCHR (finfo.st_mode)) + character = '%'; +#endif /* S_ISCHR */ +#if defined (S_ISBLK) + else if (S_ISBLK (finfo.st_mode)) + character = '#'; +#endif /* S_ISBLK */ +#if defined (S_ISLNK) + else if (S_ISLNK (finfo.st_mode)) + character = '@'; +#endif /* S_ISLNK */ +#if defined (S_ISSOCK) + else if (S_ISSOCK (finfo.st_mode)) + character = '='; +#endif /* S_ISSOCK */ +#if defined (S_ISFIFO) + else if (S_ISFIFO (finfo.st_mode)) + character = '|'; +#endif + else if (S_ISREG (finfo.st_mode)) + { + if (access (filename, X_OK) == 0) + character = '*'; + } + return (character); +} +#endif /* VISIBLE_STATS */ + +/* Return the portion of PATHNAME that should be output when listing + possible completions. If we are hacking filename completion, we + are only interested in the basename, the portion following the + final slash. Otherwise, we return what we were passed. Since + printing empty strings is not very informative, if we're doing + filename completion, and the basename is the empty string, we look + for the previous slash and return the portion following that. If + there's no previous slash, we just return what we were passed. */ +static char * +printable_part (pathname) + char *pathname; +{ + char *temp, *x; + + if (rl_filename_completion_desired == 0) /* don't need to do anything */ + return (pathname); + + temp = strrchr (pathname, '/'); +#if defined (__MSDOS__) + if (temp == 0 && ISALPHA ((unsigned char)pathname[0]) && pathname[1] == ':') + temp = pathname + 1; +#endif + + if (temp == 0 || *temp == '\0') + return (pathname); + /* If the basename is NULL, we might have a pathname like '/usr/src/'. + Look for a previous slash and, if one is found, return the portion + following that slash. If there's no previous slash, just return the + pathname we were passed. */ + else if (temp[1] == '\0') + { + for (x = temp - 1; x > pathname; x--) + if (*x == '/') + break; + return ((*x == '/') ? x + 1 : pathname); + } + else + return ++temp; +} + +/* Compute width of STRING when displayed on screen by print_filename */ +static int +fnwidth (string) + const char *string; +{ + int width, pos; +#if defined (HANDLE_MULTIBYTE) + mbstate_t ps; + int left, w; + size_t clen; + wchar_t wc; + + left = strlen (string) + 1; + memset (&ps, 0, sizeof (mbstate_t)); +#endif + + width = pos = 0; + while (string[pos]) + { + if (CTRL_CHAR (*string) || *string == RUBOUT) + { + width += 2; + pos++; + } + else + { +#if defined (HANDLE_MULTIBYTE) + clen = mbrtowc (&wc, string + pos, left - pos, &ps); + if (MB_INVALIDCH (clen)) + { + width++; + pos++; + memset (&ps, 0, sizeof (mbstate_t)); + } + else if (MB_NULLWCH (clen)) + break; + else + { + pos += clen; + w = wcwidth (wc); + width += (w >= 0) ? w : 1; + } +#else + width++; + pos++; +#endif + } + } + + return width; +} + +static int +fnprint (to_print) + const char *to_print; +{ + int printed_len; + const char *s; +#if defined (HANDLE_MULTIBYTE) + mbstate_t ps; + const char *end; + size_t tlen; + int width, w; + wchar_t wc; + + end = to_print + strlen (to_print) + 1; + memset (&ps, 0, sizeof (mbstate_t)); +#endif + + printed_len = 0; + s = to_print; + while (*s) + { + if (CTRL_CHAR (*s)) + { + putc ('^', rl_outstream); + putc (UNCTRL (*s), rl_outstream); + printed_len += 2; + s++; +#if defined (HANDLE_MULTIBYTE) + memset (&ps, 0, sizeof (mbstate_t)); +#endif + } + else if (*s == RUBOUT) + { + putc ('^', rl_outstream); + putc ('?', rl_outstream); + printed_len += 2; + s++; +#if defined (HANDLE_MULTIBYTE) + memset (&ps, 0, sizeof (mbstate_t)); +#endif + } + else + { +#if defined (HANDLE_MULTIBYTE) + tlen = mbrtowc (&wc, s, end - s, &ps); + if (MB_INVALIDCH (tlen)) + { + tlen = 1; + width = 1; + memset (&ps, 0, sizeof (mbstate_t)); + } + else if (MB_NULLWCH (tlen)) + break; + else + { + w = wcwidth (wc); + width = (w >= 0) ? w : 1; + } + s+= fwrite (s, 1, tlen, rl_outstream); + printed_len += width; +#else + putc (*s, rl_outstream); + s++; + printed_len++; +#endif + } + } + + return printed_len; +} + +/* Output TO_PRINT to rl_outstream. If VISIBLE_STATS is defined and we + are using it, check for and output a single character for `special' + filenames. Return the number of characters we output. */ + +static int +print_filename (to_print, full_pathname) + char *to_print, *full_pathname; +{ + int printed_len, extension_char, slen, tlen; + char *s, c, *new_full_pathname; + const char *dn; + + extension_char = 0; + printed_len = fnprint (to_print); + +#if defined (VISIBLE_STATS) + if (rl_filename_completion_desired && (rl_visible_stats || _rl_complete_mark_directories)) +#else + if (rl_filename_completion_desired && _rl_complete_mark_directories) +#endif + { + /* If to_print != full_pathname, to_print is the basename of the + path passed. In this case, we try to expand the directory + name before checking for the stat character. */ + if (to_print != full_pathname) + { + /* Terminate the directory name. */ + c = to_print[-1]; + to_print[-1] = '\0'; + + /* If setting the last slash in full_pathname to a NUL results in + full_pathname being the empty string, we are trying to complete + files in the root directory. If we pass a null string to the + bash directory completion hook, for example, it will expand it + to the current directory. We just want the `/'. */ + if (full_pathname == 0 || *full_pathname == 0) + dn = "/"; + else if (full_pathname[0] != '/') + dn = full_pathname; + else if (full_pathname[1] == 0) + dn = "//"; /* restore trailing slash to `//' */ + else if (full_pathname[1] == '/' && full_pathname[2] == 0) + dn = "/"; /* don't turn /// into // */ + else + dn = full_pathname; + s = tilde_expand (dn); + if (rl_directory_completion_hook) + (*rl_directory_completion_hook) (&s); + + slen = strlen (s); + tlen = strlen (to_print); + new_full_pathname = (char *)xmalloc (slen + tlen + 2); + strcpy (new_full_pathname, s); + if (s[slen - 1] == '/') + slen--; + else + new_full_pathname[slen] = '/'; + new_full_pathname[slen] = '/'; + strcpy (new_full_pathname + slen + 1, to_print); + +#if defined (VISIBLE_STATS) + if (rl_visible_stats) + extension_char = stat_char (new_full_pathname); + else +#endif + if (path_isdir (new_full_pathname)) + extension_char = '/'; + + free (new_full_pathname); + to_print[-1] = c; + } + else + { + s = tilde_expand (full_pathname); +#if defined (VISIBLE_STATS) + if (rl_visible_stats) + extension_char = stat_char (s); + else +#endif + if (path_isdir (s)) + extension_char = '/'; + } + + free (s); + if (extension_char) + { + putc (extension_char, rl_outstream); + printed_len++; + } + } + + return printed_len; +} + +static char * +rl_quote_filename (s, rtype, qcp) + char *s; + int rtype __attribute__((unused)); + char *qcp; +{ + char *r; + + r = (char *)xmalloc (strlen (s) + 2); + *r = *rl_completer_quote_characters; + strcpy (r + 1, s); + if (qcp) + *qcp = *rl_completer_quote_characters; + return r; +} + +/* Find the bounds of the current word for completion purposes, and leave + rl_point set to the end of the word. This function skips quoted + substrings (characters between matched pairs of characters in + rl_completer_quote_characters). First we try to find an unclosed + quoted substring on which to do matching. If one is not found, we use + the word break characters to find the boundaries of the current word. + We call an application-specific function to decide whether or not a + particular word break character is quoted; if that function returns a + non-zero result, the character does not break a word. This function + returns the opening quote character if we found an unclosed quoted + substring, '\0' otherwise. FP, if non-null, is set to a value saying + which (shell-like) quote characters we found (single quote, double + quote, or backslash) anywhere in the string. DP, if non-null, is set to + the value of the delimiter character that caused a word break. */ + +char +_rl_find_completion_word (fp, dp) + int *fp, *dp; +{ + int scan, end, found_quote, delimiter, pass_next, isbrk; + char quote_char, *brkchars; + + end = rl_point; + found_quote = delimiter = 0; + quote_char = '\0'; + + brkchars = 0; + if (rl_completion_word_break_hook) + brkchars = (*rl_completion_word_break_hook) (); + if (brkchars == 0) + brkchars = rl_completer_word_break_characters; + + if (rl_completer_quote_characters) + { + /* We have a list of characters which can be used in pairs to + quote substrings for the completer. Try to find the start + of an unclosed quoted substring. */ + /* FOUND_QUOTE is set so we know what kind of quotes we found. */ + for (scan = pass_next = 0; scan < end; scan = MB_NEXTCHAR (rl_line_buffer, scan, 1, MB_FIND_ANY)) + { + if (pass_next) + { + pass_next = 0; + continue; + } + + /* Shell-like semantics for single quotes -- don't allow backslash + to quote anything in single quotes, especially not the closing + quote. If you don't like this, take out the check on the value + of quote_char. */ + if (quote_char != '\'' && rl_line_buffer[scan] == '\\') + { + pass_next = 1; + found_quote |= RL_QF_BACKSLASH; + continue; + } + + if (quote_char != '\0') + { + /* Ignore everything until the matching close quote char. */ + if (rl_line_buffer[scan] == quote_char) + { + /* Found matching close. Abandon this substring. */ + quote_char = '\0'; + rl_point = end; + } + } + else if (strchr (rl_completer_quote_characters, rl_line_buffer[scan])) + { + /* Found start of a quoted substring. */ + quote_char = rl_line_buffer[scan]; + rl_point = scan + 1; + /* Shell-like quoting conventions. */ + if (quote_char == '\'') + found_quote |= RL_QF_SINGLE_QUOTE; + else if (quote_char == '"') + found_quote |= RL_QF_DOUBLE_QUOTE; + else + found_quote |= RL_QF_OTHER_QUOTE; + } + } + } + + if (rl_point == end && quote_char == '\0') + { + /* We didn't find an unclosed quoted substring upon which to do + completion, so use the word break characters to find the + substring on which to complete. */ + while ((rl_point = MB_PREVCHAR (rl_line_buffer, rl_point, MB_FIND_ANY))) + { + scan = rl_line_buffer[rl_point]; + + if (strchr (brkchars, scan) == 0) + continue; + + /* Call the application-specific function to tell us whether + this word break character is quoted and should be skipped. */ + if (rl_char_is_quoted_p && found_quote && + (*rl_char_is_quoted_p) (rl_line_buffer, rl_point)) + continue; + + /* Convoluted code, but it avoids an n^2 algorithm with calls + to char_is_quoted. */ + break; + } + } + + /* If we are at an unquoted word break, then advance past it. */ + scan = rl_line_buffer[rl_point]; + + /* If there is an application-specific function to say whether or not + a character is quoted and we found a quote character, let that + function decide whether or not a character is a word break, even + if it is found in rl_completer_word_break_characters. Don't bother + if we're at the end of the line, though. */ + if (scan) + { + if (rl_char_is_quoted_p) + isbrk = (found_quote == 0 || + (*rl_char_is_quoted_p) (rl_line_buffer, rl_point) == 0) && + strchr (brkchars, scan) != 0; + else + isbrk = strchr (brkchars, scan) != 0; + + if (isbrk) + { + /* If the character that caused the word break was a quoting + character, then remember it as the delimiter. */ + if (rl_basic_quote_characters && + strchr (rl_basic_quote_characters, scan) && + (end - rl_point) > 1) + delimiter = scan; + + /* If the character isn't needed to determine something special + about what kind of completion to perform, then advance past it. */ + if (rl_special_prefixes == 0 || strchr (rl_special_prefixes, scan) == 0) + rl_point++; + } + } + + if (fp) + *fp = found_quote; + if (dp) + *dp = delimiter; + + return (quote_char); +} + +static char ** +gen_completion_matches (text, start, end, our_func, found_quote, quote_char) + char *text; + int start, end; + rl_compentry_func_t *our_func; + int found_quote, quote_char; +{ + char **matches; + + rl_completion_found_quote = found_quote; + rl_completion_quote_character = quote_char; + + /* If the user wants to TRY to complete, but then wants to give + up and use the default completion function, they set the + variable rl_attempted_completion_function. */ + if (rl_attempted_completion_function) + { + matches = (*rl_attempted_completion_function) (text, start, end); + + if (matches || rl_attempted_completion_over) + { + rl_attempted_completion_over = 0; + return (matches); + } + } + + /* XXX -- filename dequoting moved into rl_filename_completion_function */ + + matches = rl_completion_matches (text, our_func); + return matches; +} + +/* Filter out duplicates in MATCHES. This frees up the strings in + MATCHES. */ +static char ** +remove_duplicate_matches (matches) + char **matches; +{ + char *lowest_common; + int i, j, newlen; + char dead_slot; + char **temp_array; + + /* Sort the items. */ + for (i = 0; matches[i]; i++) + ; + + /* Sort the array without matches[0], since we need it to + stay in place no matter what. */ + if (i) + qsort (matches+1, i-1, sizeof (char *), (QSFUNC *)_rl_qsort_string_compare); + + /* Remember the lowest common denominator for it may be unique. */ + lowest_common = savestring (matches[0]); + + for (i = newlen = 0; matches[i + 1]; i++) + { + if (strcmp (matches[i], matches[i + 1]) == 0) + { + free (matches[i]); + matches[i] = (char *)&dead_slot; + } + else + newlen++; + } + + /* We have marked all the dead slots with (char *)&dead_slot. + Copy all the non-dead entries into a new array. */ + temp_array = (char **)xmalloc ((3 + newlen) * sizeof (char *)); + for (i = j = 1; matches[i]; i++) + { + if (matches[i] != (char *)&dead_slot) + temp_array[j++] = matches[i]; + } + temp_array[j] = (char *)NULL; + + if (matches[0] != (char *)&dead_slot) + free (matches[0]); + + /* Place the lowest common denominator back in [0]. */ + temp_array[0] = lowest_common; + + /* If there is one string left, and it is identical to the + lowest common denominator, then the LCD is the string to + insert. */ + if (j == 2 && strcmp (temp_array[0], temp_array[1]) == 0) + { + free (temp_array[1]); + temp_array[1] = (char *)NULL; + } + return (temp_array); +} + +/* Find the common prefix of the list of matches, and put it into + matches[0]. */ +static int +compute_lcd_of_matches (match_list, matches, text) + char **match_list; + int matches; + const char *text; +{ + register int i, c1, c2, si; + int low; /* Count of max-matched characters. */ + char *dtext; /* dequoted TEXT, if needed */ +#if defined (HANDLE_MULTIBYTE) + int v; + mbstate_t ps1, ps2; + wchar_t wc1, wc2; +#endif + + /* If only one match, just use that. Otherwise, compare each + member of the list with the next, finding out where they + stop matching. */ + if (matches == 1) + { + match_list[0] = match_list[1]; + match_list[1] = (char *)NULL; + return 1; + } + + for (i = 1, low = 100000; i < matches; i++) + { +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + memset (&ps1, 0, sizeof (mbstate_t)); + memset (&ps2, 0, sizeof (mbstate_t)); + } +#endif + if (_rl_completion_case_fold) + { + for (si = 0; + (c1 = _rl_to_lower(match_list[i][si])) && + (c2 = _rl_to_lower(match_list[i + 1][si])); + si++) +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + v = mbrtowc (&wc1, match_list[i]+si, strlen (match_list[i]+si), &ps1); + mbrtowc (&wc2, match_list[i+1]+si, strlen (match_list[i+1]+si), &ps2); + wc1 = towlower (wc1); + wc2 = towlower (wc2); + if (wc1 != wc2) + break; + else if (v > 1) + si += v - 1; + } + else +#endif + if (c1 != c2) + break; + } + else + { + for (si = 0; + (c1 = match_list[i][si]) && + (c2 = match_list[i + 1][si]); + si++) +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + mbstate_t ps_back; + ps_back = ps1; + if (!_rl_compare_chars (match_list[i], si, &ps1, match_list[i+1], si, &ps2)) + break; + else if ((v = _rl_get_char_len (&match_list[i][si], &ps_back)) > 1) + si += v - 1; + } + else +#endif + if (c1 != c2) + break; + } + + if (low > si) + low = si; + } + + /* If there were multiple matches, but none matched up to even the + first character, and the user typed something, use that as the + value of matches[0]. */ + if (low == 0 && text && *text) + { + match_list[0] = (char *)xmalloc (strlen (text) + 1); + strcpy (match_list[0], text); + } + else + { + match_list[0] = (char *)xmalloc (low + 1); + + /* XXX - this might need changes in the presence of multibyte chars */ + + /* If we are ignoring case, try to preserve the case of the string + the user typed in the face of multiple matches differing in case. */ + if (_rl_completion_case_fold) + { + /* We're making an assumption here: + IF we're completing filenames AND + the application has defined a filename dequoting function AND + we found a quote character AND + the application has requested filename quoting + THEN + we assume that TEXT was dequoted before checking against + the file system and needs to be dequoted here before we + check against the list of matches + FI */ + dtext = (char *)NULL; + if (rl_filename_completion_desired && + rl_filename_dequoting_function && + rl_completion_found_quote && + rl_filename_quoting_desired) + { + dtext = (*rl_filename_dequoting_function) ((char *)text, rl_completion_quote_character); + text = dtext; + } + + /* sort the list to get consistent answers. */ + qsort (match_list+1, matches, sizeof(char *), (QSFUNC *)_rl_qsort_string_compare); + + si = strlen (text); + if (si <= low) + { + for (i = 1; i <= matches; i++) + if (strncmp (match_list[i], text, si) == 0) + { + strncpy (match_list[0], match_list[i], low); + break; + } + /* no casematch, use first entry */ + if (i > matches) + strncpy (match_list[0], match_list[1], low); + } + else + /* otherwise, just use the text the user typed. */ + strncpy (match_list[0], text, low); + + FREE (dtext); + } + else + strncpy (match_list[0], match_list[1], low); + + match_list[0][low] = '\0'; + } + + return matches; +} + +static int +postprocess_matches (matchesp, matching_filenames) + char ***matchesp; + int matching_filenames; +{ + char *t, **matches, **temp_matches; + int nmatch, i; + + matches = *matchesp; + + if (matches == 0) + return 0; + + /* It seems to me that in all the cases we handle we would like + to ignore duplicate possiblilities. Scan for the text to + insert being identical to the other completions. */ + if (rl_ignore_completion_duplicates) + { + temp_matches = remove_duplicate_matches (matches); + free (matches); + matches = temp_matches; + } + + /* If we are matching filenames, then here is our chance to + do clever processing by re-examining the list. Call the + ignore function with the array as a parameter. It can + munge the array, deleting matches as it desires. */ + if (rl_ignore_some_completions_function && matching_filenames) + { + for (nmatch = 1; matches[nmatch]; nmatch++) + ; + (void)(*rl_ignore_some_completions_function) (matches); + if (matches == 0 || matches[0] == 0) + { + FREE (matches); + *matchesp = (char **)0; + return 0; + } + else + { + /* If we removed some matches, recompute the common prefix. */ + for (i = 1; matches[i]; i++) + ; + if (i > 1 && i < nmatch) + { + t = matches[0]; + compute_lcd_of_matches (matches, i - 1, t); + FREE (t); + } + } + } + + *matchesp = matches; + return (1); +} + +/* A convenience function for displaying a list of strings in + columnar format on readline's output stream. MATCHES is the list + of strings, in argv format, LEN is the number of strings in MATCHES, + and MAX is the length of the longest string in MATCHES. */ +void +rl_display_match_list (matches, len, max) + char **matches; + int len, max; +{ + int count, limit, printed_len, lines; + int i, j, k, l; + char *temp; + + /* How many items of MAX length can we fit in the screen window? */ + max += 2; + limit = _rl_screenwidth / max; + if (limit != 1 && (limit * max == _rl_screenwidth)) + limit--; + + /* Avoid a possible floating exception. If max > _rl_screenwidth, + limit will be 0 and a divide-by-zero fault will result. */ + if (limit == 0) + limit = 1; + + /* How many iterations of the printing loop? */ + count = (len + (limit - 1)) / limit; + + /* Watch out for special case. If LEN is less than LIMIT, then + just do the inner printing loop. + 0 < len <= limit implies count = 1. */ + + /* Sort the items if they are not already sorted. */ + if (rl_ignore_completion_duplicates == 0) + qsort (matches + 1, len, sizeof (char *), (QSFUNC *)_rl_qsort_string_compare); + + rl_crlf (); + + lines = 0; + if (_rl_print_completions_horizontally == 0) + { + /* Print the sorted items, up-and-down alphabetically, like ls. */ + for (i = 1; i <= count; i++) + { + for (j = 0, l = i; j < limit; j++) + { + if (l > len || matches[l] == 0) + break; + else + { + temp = printable_part (matches[l]); + printed_len = print_filename (temp, matches[l]); + + if (j + 1 < limit) + for (k = 0; k < max - printed_len; k++) + putc (' ', rl_outstream); + } + l += count; + } + rl_crlf (); + lines++; + if (_rl_page_completions && lines >= (_rl_screenheight - 1) && i < count) + { + lines = _rl_internal_pager (lines); + if (lines < 0) + return; + } + } + } + else + { + /* Print the sorted items, across alphabetically, like ls -x. */ + for (i = 1; matches[i]; i++) + { + temp = printable_part (matches[i]); + printed_len = print_filename (temp, matches[i]); + /* Have we reached the end of this line? */ + if (matches[i+1]) + { + if (i && (limit > 1) && (i % limit) == 0) + { + rl_crlf (); + lines++; + if (_rl_page_completions && lines >= _rl_screenheight - 1) + { + lines = _rl_internal_pager (lines); + if (lines < 0) + return; + } + } + else + for (k = 0; k < max - printed_len; k++) + putc (' ', rl_outstream); + } + } + rl_crlf (); + } +} + +/* Display MATCHES, a list of matching filenames in argv format. This + handles the simple case -- a single match -- first. If there is more + than one match, we compute the number of strings in the list and the + length of the longest string, which will be needed by the display + function. If the application wants to handle displaying the list of + matches itself, it sets RL_COMPLETION_DISPLAY_MATCHES_HOOK to the + address of a function, and we just call it. If we're handling the + display ourselves, we just call rl_display_match_list. We also check + that the list of matches doesn't exceed the user-settable threshold, + and ask the user if he wants to see the list if there are more matches + than RL_COMPLETION_QUERY_ITEMS. */ +static void +display_matches (matches) + char **matches; +{ + int len, max, i; + char *temp; + + /* Move to the last visible line of a possibly-multiple-line command. */ + _rl_move_vert (_rl_vis_botlin); + + /* Handle simple case first. What if there is only one answer? */ + if (matches[1] == 0) + { + temp = printable_part (matches[0]); + rl_crlf (); + print_filename (temp, matches[0]); + rl_crlf (); + + rl_forced_update_display (); + rl_display_fixed = 1; + + return; + } + + /* There is more than one answer. Find out how many there are, + and find the maximum printed length of a single entry. */ + for (max = 0, i = 1; matches[i]; i++) + { + temp = printable_part (matches[i]); + len = fnwidth (temp); + + if (len > max) + max = len; + } + + len = i - 1; + + /* If the caller has defined a display hook, then call that now. */ + if (rl_completion_display_matches_hook) + { + (*rl_completion_display_matches_hook) (matches, len, max); + return; + } + + /* If there are many items, then ask the user if she really wants to + see them all. */ + if (rl_completion_query_items > 0 && len >= rl_completion_query_items) + { + rl_crlf (); + fprintf (rl_outstream, "Display all %d possibilities? (y or n)", len); + fflush (rl_outstream); + if (get_y_or_n (0) == 0) + { + rl_crlf (); + + rl_forced_update_display (); + rl_display_fixed = 1; + + return; + } + } + + rl_display_match_list (matches, len, max); + + rl_forced_update_display (); + rl_display_fixed = 1; +} + +static char * +make_quoted_replacement (match, mtype, qc) + char *match; + int mtype; + char *qc; /* Pointer to quoting character, if any */ +{ + int should_quote, do_replace; + char *replacement; + + /* If we are doing completion on quoted substrings, and any matches + contain any of the completer_word_break_characters, then auto- + matically prepend the substring with a quote character (just pick + the first one from the list of such) if it does not already begin + with a quote string. FIXME: Need to remove any such automatically + inserted quote character when it no longer is necessary, such as + if we change the string we are completing on and the new set of + matches don't require a quoted substring. */ + replacement = match; + + should_quote = match && rl_completer_quote_characters && + rl_filename_completion_desired && + rl_filename_quoting_desired; + + if (should_quote) + should_quote = should_quote && (!qc || !*qc || + (rl_completer_quote_characters && strchr (rl_completer_quote_characters, *qc))); + + if (should_quote) + { + /* If there is a single match, see if we need to quote it. + This also checks whether the common prefix of several + matches needs to be quoted. */ + should_quote = rl_filename_quote_characters + ? (_rl_strpbrk (match, rl_filename_quote_characters) != 0) + : 0; + + do_replace = should_quote ? mtype : NO_MATCH; + /* Quote the replacement, since we found an embedded + word break character in a potential match. */ + if (do_replace != NO_MATCH && rl_filename_quoting_function) + replacement = (*rl_filename_quoting_function) (match, do_replace, qc); + } + return (replacement); +} + +static void +insert_match (match, start, mtype, qc) + char *match; + int start, mtype; + char *qc; +{ + char *replacement; + char oqc; + + oqc = qc ? *qc : '\0'; + replacement = make_quoted_replacement (match, mtype, qc); + + /* Now insert the match. */ + if (replacement) + { + /* Don't double an opening quote character. */ + if (qc && *qc && start && rl_line_buffer[start - 1] == *qc && + replacement[0] == *qc) + start--; + /* If make_quoted_replacement changed the quoting character, remove + the opening quote and insert the (fully-quoted) replacement. */ + else if (qc && (*qc != oqc) && start && rl_line_buffer[start - 1] == oqc && + replacement[0] != oqc) + start--; + _rl_replace_text (replacement, start, rl_point - 1); + if (replacement != match) + free (replacement); + } +} + +/* Append any necessary closing quote and a separator character to the + just-inserted match. If the user has specified that directories + should be marked by a trailing `/', append one of those instead. The + default trailing character is a space. Returns the number of characters + appended. If NONTRIVIAL_MATCH is set, we test for a symlink (if the OS + has them) and don't add a suffix for a symlink to a directory. A + nontrivial match is one that actually adds to the word being completed. + The variable rl_completion_mark_symlink_dirs controls this behavior + (it's initially set to the what the user has chosen, indicated by the + value of _rl_complete_mark_symlink_dirs, but may be modified by an + application's completion function). */ +static int +append_to_match (text, delimiter, quote_char, nontrivial_match) + char *text; + int delimiter, quote_char, nontrivial_match; +{ + char temp_string[4], *filename; + int temp_string_index, s; + struct stat finfo; + + temp_string_index = 0; + if (quote_char && rl_point && rl_completion_suppress_quote == 0 && + rl_line_buffer[rl_point - 1] != quote_char) + temp_string[temp_string_index++] = quote_char; + + if (delimiter) + temp_string[temp_string_index++] = delimiter; + else if (rl_completion_suppress_append == 0 && rl_completion_append_character) + temp_string[temp_string_index++] = rl_completion_append_character; + + temp_string[temp_string_index++] = '\0'; + + if (rl_filename_completion_desired) + { + filename = tilde_expand (text); + s = (nontrivial_match && rl_completion_mark_symlink_dirs == 0) + ? LSTAT (filename, &finfo) + : stat (filename, &finfo); + if (s == 0 && S_ISDIR (finfo.st_mode)) + { + if (_rl_complete_mark_directories /* && rl_completion_suppress_append == 0 */) + { + /* This is clumsy. Avoid putting in a double slash if point + is at the end of the line and the previous character is a + slash. */ + if (rl_point && rl_line_buffer[rl_point] == '\0' && rl_line_buffer[rl_point - 1] == '/') + ; + else if (rl_line_buffer[rl_point] != '/') + rl_insert_text ("/"); + } + } +#ifdef S_ISLNK + /* Don't add anything if the filename is a symlink and resolves to a + directory. */ + else if (s == 0 && S_ISLNK (finfo.st_mode) && + stat (filename, &finfo) == 0 && S_ISDIR (finfo.st_mode)) + ; +#endif + else + { + if (rl_point == rl_end && temp_string_index) + rl_insert_text (temp_string); + } + free (filename); + } + else + { + if (rl_point == rl_end && temp_string_index) + rl_insert_text (temp_string); + } + + return (temp_string_index); +} + +static void +insert_all_matches (matches, point, qc) + char **matches; + int point; + char *qc; +{ + int i; + char *rp; + + rl_begin_undo_group (); + /* remove any opening quote character; make_quoted_replacement will add + it back. */ + if (qc && *qc && point && rl_line_buffer[point - 1] == *qc) + point--; + rl_delete_text (point, rl_point); + rl_point = point; + + if (matches[1]) + { + for (i = 1; matches[i]; i++) + { + rp = make_quoted_replacement (matches[i], SINGLE_MATCH, qc); + rl_insert_text (rp); + rl_insert_text (" "); + if (rp != matches[i]) + free (rp); + } + } + else + { + rp = make_quoted_replacement (matches[0], SINGLE_MATCH, qc); + rl_insert_text (rp); + rl_insert_text (" "); + if (rp != matches[0]) + free (rp); + } + rl_end_undo_group (); +} + +void +_rl_free_match_list (matches) + char **matches; +{ + register int i; + + if (matches == 0) + return; + + for (i = 0; matches[i]; i++) + free (matches[i]); + free (matches); +} + +/* Complete the word at or before point. + WHAT_TO_DO says what to do with the completion. + `?' means list the possible completions. + TAB means do standard completion. + `*' means insert all of the possible completions. + `!' means to do standard completion, and list all possible completions if + there is more than one. + `@' means to do standard completion, and list all possible completions if + there is more than one and partial completion is not possible. */ +int +rl_complete_internal (what_to_do) + int what_to_do; +{ + char **matches; + rl_compentry_func_t *our_func; + int start, end, delimiter, found_quote, i, nontrivial_lcd; + char *text, *saved_line_buffer; + char quote_char; + + RL_SETSTATE(RL_STATE_COMPLETING); + + set_completion_defaults (what_to_do); + + saved_line_buffer = rl_line_buffer ? savestring (rl_line_buffer) : (char *)NULL; + our_func = rl_completion_entry_function + ? rl_completion_entry_function + : rl_filename_completion_function; + /* We now look backwards for the start of a filename/variable word. */ + end = rl_point; + found_quote = delimiter = 0; + quote_char = '\0'; + + if (rl_point) + /* This (possibly) changes rl_point. If it returns a non-zero char, + we know we have an open quote. */ + quote_char = _rl_find_completion_word (&found_quote, &delimiter); + + start = rl_point; + rl_point = end; + + text = rl_copy_text (start, end); + matches = gen_completion_matches (text, start, end, our_func, found_quote, quote_char); + /* nontrivial_lcd is set if the common prefix adds something to the word + being completed. */ + nontrivial_lcd = matches && strcmp (text, matches[0]) != 0; + free (text); + + if (matches == 0) + { + rl_ding (); + FREE (saved_line_buffer); + completion_changed_buffer = 0; + RL_UNSETSTATE(RL_STATE_COMPLETING); + return (0); + } + + /* If we are matching filenames, the attempted completion function will + have set rl_filename_completion_desired to a non-zero value. The basic + rl_filename_completion_function does this. */ + i = rl_filename_completion_desired; + + if (postprocess_matches (&matches, i) == 0) + { + rl_ding (); + FREE (saved_line_buffer); + completion_changed_buffer = 0; + RL_UNSETSTATE(RL_STATE_COMPLETING); + return (0); + } + + switch (what_to_do) + { + case TAB: + case '!': + case '@': + /* Insert the first match with proper quoting. */ + if (*matches[0]) + insert_match (matches[0], start, matches[1] ? MULT_MATCH : SINGLE_MATCH, "e_char); + + /* If there are more matches, ring the bell to indicate. + If we are in vi mode, Posix.2 says to not ring the bell. + If the `show-all-if-ambiguous' variable is set, display + all the matches immediately. Otherwise, if this was the + only match, and we are hacking files, check the file to + see if it was a directory. If so, and the `mark-directories' + variable is set, add a '/' to the name. If not, and we + are at the end of the line, then add a space. */ + if (matches[1]) + { + if (what_to_do == '!') + { + display_matches (matches); + break; + } + else if (what_to_do == '@') + { + if (nontrivial_lcd == 0) + display_matches (matches); + break; + } + else if (rl_editing_mode != vi_mode) + rl_ding (); /* There are other matches remaining. */ + } + else + append_to_match (matches[0], delimiter, quote_char, nontrivial_lcd); + + break; + + case '*': + insert_all_matches (matches, start, "e_char); + break; + + case '?': + display_matches (matches); + break; + + default: + fprintf (stderr, "\r\nreadline: bad value %d for what_to_do in rl_complete\n", what_to_do); + rl_ding (); + FREE (saved_line_buffer); + RL_UNSETSTATE(RL_STATE_COMPLETING); + return 1; + } + + _rl_free_match_list (matches); + + /* Check to see if the line has changed through all of this manipulation. */ + if (saved_line_buffer) + { + completion_changed_buffer = strcmp (rl_line_buffer, saved_line_buffer) != 0; + free (saved_line_buffer); + } + + RL_UNSETSTATE(RL_STATE_COMPLETING); + return 0; +} + +/***************************************************************/ +/* */ +/* Application-callable completion match generator functions */ +/* */ +/***************************************************************/ + +/* Return an array of (char *) which is a list of completions for TEXT. + If there are no completions, return a NULL pointer. + The first entry in the returned array is the substitution for TEXT. + The remaining entries are the possible completions. + The array is terminated with a NULL pointer. + + ENTRY_FUNCTION is a function of two args, and returns a (char *). + The first argument is TEXT. + The second is a state argument; it should be zero on the first call, and + non-zero on subsequent calls. It returns a NULL pointer to the caller + when there are no more matches. + */ +char ** +rl_completion_matches (text, entry_function) + const char *text; + rl_compentry_func_t *entry_function; +{ + /* Number of slots in match_list. */ + int match_list_size; + + /* The list of matches. */ + char **match_list; + + /* Number of matches actually found. */ + int matches; + + /* Temporary string binder. */ + char *string; + + matches = 0; + match_list_size = 10; + match_list = (char **)xmalloc ((match_list_size + 1) * sizeof (char *)); + match_list[1] = (char *)NULL; + + while ((string = (*entry_function) (text, matches))) + { + if (matches + 1 == match_list_size) + match_list = (char **)xrealloc + (match_list, ((match_list_size += 10) + 1) * sizeof (char *)); + + match_list[++matches] = string; + match_list[matches + 1] = (char *)NULL; + } + + /* If there were any matches, then look through them finding out the + lowest common denominator. That then becomes match_list[0]. */ + if (matches) + compute_lcd_of_matches (match_list, matches, text); + else /* There were no matches. */ + { + free (match_list); + match_list = (char **)NULL; + } + return (match_list); +} + +/* A completion function for usernames. + TEXT contains a partial username preceded by a random + character (usually `~'). */ +char * +rl_username_completion_function (text, state) + const char *text; + int state; +{ +#if defined (__WIN32__) || defined (__OPENNT) + return (char *)NULL; +#else /* !__WIN32__ && !__OPENNT) */ + static char *username = (char *)NULL; + static struct passwd *entry; + static int first_char, first_char_loc; + char *value; +#if defined (HAVE_GETPWENT) + static int namelen; +#endif + + if (state == 0) + { + FREE (username); + + first_char = *text; + first_char_loc = first_char == '~'; + + username = savestring (&text[first_char_loc]); +#if defined (HAVE_GETPWENT) + namelen = strlen (username); +#endif + setpwent (); + } + +#if defined (HAVE_GETPWENT) + while (entry = getpwent ()) + { + /* Null usernames should result in all users as possible completions. */ + if (namelen == 0 || (STREQN (username, entry->pw_name, namelen))) + break; + } +#endif + + if (entry == 0) + { +#if defined (HAVE_GETPWENT) + endpwent (); +#endif + return ((char *)NULL); + } + else + { + value = (char *)xmalloc (2 + strlen (entry->pw_name)); + + *value = *text; + + strcpy (value + first_char_loc, entry->pw_name); + + if (first_char == '~') + rl_filename_completion_desired = 1; + + return (value); + } +#endif /* !__WIN32__ && !__OPENNT */ +} + +/* Okay, now we write the entry_function for filename completion. In the + general case. Note that completion in the shell is a little different + because of all the pathnames that must be followed when looking up the + completion for a command. */ +char * +rl_filename_completion_function (text, state) + const char *text; + int state; +{ + static DIR *directory = (DIR *)NULL; + static char *filename = (char *)NULL; + static char *dirname = (char *)NULL; + static char *users_dirname = (char *)NULL; + static int filename_len; + char *temp; + int dirlen; + struct dirent *entry; + + /* If we don't have any state, then do some initialization. */ + if (state == 0) + { + /* If we were interrupted before closing the directory or reading + all of its contents, close it. */ + if (directory) + { + closedir (directory); + directory = (DIR *)NULL; + } + FREE (dirname); + FREE (filename); + FREE (users_dirname); + + filename = savestring (text); + if (*text == 0) + text = "."; + dirname = savestring (text); + + temp = strrchr (dirname, '/'); + +#if defined (__MSDOS__) + /* special hack for //X/... */ + if (dirname[0] == '/' && dirname[1] == '/' && ISALPHA ((unsigned char)dirname[2]) && dirname[3] == '/') + temp = strrchr (dirname + 3, '/'); +#endif + + if (temp) + { + strcpy (filename, ++temp); + *temp = '\0'; + } +#if defined (__MSDOS__) + /* searches from current directory on the drive */ + else if (ISALPHA ((unsigned char)dirname[0]) && dirname[1] == ':') + { + strcpy (filename, dirname + 2); + dirname[2] = '\0'; + } +#endif + else + { + dirname[0] = '.'; + dirname[1] = '\0'; + } + + /* We aren't done yet. We also support the "~user" syntax. */ + + /* Save the version of the directory that the user typed. */ + users_dirname = savestring (dirname); + + if (*dirname == '~') + { + temp = tilde_expand (dirname); + free (dirname); + dirname = temp; + } + + if (rl_directory_rewrite_hook) + (*rl_directory_rewrite_hook) (&dirname); + + /* The directory completion hook should perform any necessary + dequoting. */ + if (rl_directory_completion_hook && (*rl_directory_completion_hook) (&dirname)) + { + free (users_dirname); + users_dirname = savestring (dirname); + } + else if (rl_completion_found_quote && rl_filename_dequoting_function) + { + /* delete single and double quotes */ + temp = (*rl_filename_dequoting_function) (users_dirname, rl_completion_quote_character); + free (users_dirname); + users_dirname = temp; + } + directory = opendir (dirname); + + /* Now dequote a non-null filename. */ + if (filename && *filename && rl_completion_found_quote && rl_filename_dequoting_function) + { + /* delete single and double quotes */ + temp = (*rl_filename_dequoting_function) (filename, rl_completion_quote_character); + free (filename); + filename = temp; + } + filename_len = strlen (filename); + + rl_filename_completion_desired = 1; + } + + /* At this point we should entertain the possibility of hacking wildcarded + filenames, like /usr/man/man<WILD>/te<TAB>. If the directory name + contains globbing characters, then build an array of directories, and + then map over that list while completing. */ + /* *** UNIMPLEMENTED *** */ + + /* Now that we have some state, we can read the directory. */ + + entry = (struct dirent *)NULL; + while (directory && (entry = readdir (directory))) + { + /* Special case for no filename. If the user has disabled the + `match-hidden-files' variable, skip filenames beginning with `.'. + All other entries except "." and ".." match. */ + if (filename_len == 0) + { + if (_rl_match_hidden_files == 0 && HIDDEN_FILE (entry->d_name)) + continue; + + if (entry->d_name[0] != '.' || + (entry->d_name[1] && + (entry->d_name[1] != '.' || entry->d_name[2]))) + break; + } + else + { + /* Otherwise, if these match up to the length of filename, then + it is a match. */ + if (_rl_completion_case_fold) + { + if ((_rl_to_lower (entry->d_name[0]) == _rl_to_lower (filename[0])) && + (((int)D_NAMLEN (entry)) >= filename_len) && + (_rl_strnicmp (filename, entry->d_name, filename_len) == 0)) + break; + } + else + { + if ((entry->d_name[0] == filename[0]) && + (((int)D_NAMLEN (entry)) >= filename_len) && + (strncmp (filename, entry->d_name, filename_len) == 0)) + break; + } + } + } + + if (entry == 0) + { + if (directory) + { + closedir (directory); + directory = (DIR *)NULL; + } + if (dirname) + { + free (dirname); + dirname = (char *)NULL; + } + if (filename) + { + free (filename); + filename = (char *)NULL; + } + if (users_dirname) + { + free (users_dirname); + users_dirname = (char *)NULL; + } + + return (char *)NULL; + } + else + { + /* dirname && (strcmp (dirname, ".") != 0) */ + if (dirname && (dirname[0] != '.' || dirname[1])) + { + if (rl_complete_with_tilde_expansion && *users_dirname == '~') + { + dirlen = strlen (dirname); + temp = (char *)xmalloc (2 + dirlen + D_NAMLEN (entry)); + strcpy (temp, dirname); + /* Canonicalization cuts off any final slash present. We + may need to add it back. */ + if (dirname[dirlen - 1] != '/') + { + temp[dirlen++] = '/'; + temp[dirlen] = '\0'; + } + } + else + { + dirlen = strlen (users_dirname); + temp = (char *)xmalloc (2 + dirlen + D_NAMLEN (entry)); + strcpy (temp, users_dirname); + /* Make sure that temp has a trailing slash here. */ + if (users_dirname[dirlen - 1] != '/') + temp[dirlen++] = '/'; + } + + strcpy (temp + dirlen, entry->d_name); + } + else + temp = savestring (entry->d_name); + + return (temp); + } +} + +/* An initial implementation of a menu completion function a la tcsh. The + first time (if the last readline command was not rl_menu_complete), we + generate the list of matches. This code is very similar to the code in + rl_complete_internal -- there should be a way to combine the two. Then, + for each item in the list of matches, we insert the match in an undoable + fashion, with the appropriate character appended (this happens on the + second and subsequent consecutive calls to rl_menu_complete). When we + hit the end of the match list, we restore the original unmatched text, + ring the bell, and reset the counter to zero. */ +int +rl_menu_complete (count, ignore) + int count, ignore __attribute__((unused)); +{ + rl_compentry_func_t *our_func; + int matching_filenames, found_quote; + + static char *orig_text; + static char **matches = (char **)0; + static int match_list_index = 0; + static int match_list_size = 0; + static int orig_start, orig_end; + static char quote_char; + static int delimiter; + + /* The first time through, we generate the list of matches and set things + up to insert them. */ + if (rl_last_func != rl_menu_complete) + { + /* Clean up from previous call, if any. */ + FREE (orig_text); + if (matches) + _rl_free_match_list (matches); + + match_list_index = match_list_size = 0; + matches = (char **)NULL; + + /* Only the completion entry function can change these. */ + set_completion_defaults ('%'); + + our_func = rl_completion_entry_function + ? rl_completion_entry_function + : rl_filename_completion_function; + + /* We now look backwards for the start of a filename/variable word. */ + orig_end = rl_point; + found_quote = delimiter = 0; + quote_char = '\0'; + + if (rl_point) + /* This (possibly) changes rl_point. If it returns a non-zero char, + we know we have an open quote. */ + quote_char = _rl_find_completion_word (&found_quote, &delimiter); + + orig_start = rl_point; + rl_point = orig_end; + + orig_text = rl_copy_text (orig_start, orig_end); + matches = gen_completion_matches (orig_text, orig_start, orig_end, + our_func, found_quote, quote_char); + + /* If we are matching filenames, the attempted completion function will + have set rl_filename_completion_desired to a non-zero value. The basic + rl_filename_completion_function does this. */ + matching_filenames = rl_filename_completion_desired; + + if (matches == 0 || postprocess_matches (&matches, matching_filenames) == 0) + { + rl_ding (); + FREE (matches); + matches = (char **)0; + FREE (orig_text); + orig_text = (char *)0; + completion_changed_buffer = 0; + return (0); + } + + for (match_list_size = 0; matches[match_list_size]; match_list_size++) + ; + /* matches[0] is lcd if match_list_size > 1, but the circular buffer + code below should take care of it. */ + } + + /* Now we have the list of matches. Replace the text between + rl_line_buffer[orig_start] and rl_line_buffer[rl_point] with + matches[match_list_index], and add any necessary closing char. */ + + if (matches == 0 || match_list_size == 0) + { + rl_ding (); + FREE (matches); + matches = (char **)0; + completion_changed_buffer = 0; + return (0); + } + + match_list_index += count; + if (match_list_index < 0) + match_list_index += match_list_size; + else + match_list_index %= match_list_size; + + if (match_list_index == 0 && match_list_size > 1) + { + rl_ding (); + insert_match (orig_text, orig_start, MULT_MATCH, "e_char); + } + else + { + insert_match (matches[match_list_index], orig_start, SINGLE_MATCH, "e_char); + append_to_match (matches[match_list_index], delimiter, quote_char, + strcmp (orig_text, matches[match_list_index])); + } + + completion_changed_buffer = 1; + return (0); +} diff --git a/extra/readline/config_readline.h b/extra/readline/config_readline.h new file mode 100644 index 00000000000..9aa464958bb --- /dev/null +++ b/extra/readline/config_readline.h @@ -0,0 +1,37 @@ +/* + config-readline.h Maintained by hand. Contains the readline specific + parts from config.h.in in readline 4.3 +*/ + +#if defined (HAVE_CONFIG_H) +# include <config.h> +#endif + +#ifdef NOT_YET /* causes problem on MacOSX */ +/* to get wcwidth() defined */ +#define _XOPEN_SOURCE 600 +#define _XOPEN_SOURCE_EXTENDED +#define _XOPEN_ +#endif + +/* + Ultrix botches type-ahead when switching from canonical to + non-canonical mode, at least through version 4.3 +*/ +#if !defined (HAVE_TERMIOS_H) || !defined (HAVE_TCGETATTR) || defined (ultrix) +# define TERMIOS_MISSING +#endif + +#if defined (STRCOLL_BROKEN) +# undef HAVE_STRCOLL +#endif + +#if defined (__STDC__) && defined (HAVE_STDARG_H) +# define PREFER_STDARG +# define USE_VARARGS +#else +# if defined (HAVE_VARARGS_H) +# define PREFER_VARARGS +# define USE_VARARGS +# endif +#endif diff --git a/extra/readline/configure.in b/extra/readline/configure.in new file mode 100644 index 00000000000..949da72933e --- /dev/null +++ b/extra/readline/configure.in @@ -0,0 +1,296 @@ +dnl +dnl Configure script for readline library +dnl +dnl report bugs to chet@po.cwru.edu +dnl +dnl Process this file with autoconf to produce a configure script. + +# Copyright (C) 1987-2005 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program 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 General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. + +AC_REVISION([for Readline 5.2, version 2.61]) + +AC_INIT(readline, 5.2, bug-readline@gnu.org) + +dnl make sure we are using a recent autoconf version +AC_PREREQ(2.50) + +AC_CONFIG_SRCDIR(readline.h) +AC_CONFIG_AUX_DIR(./support) +AC_CONFIG_HEADERS(config.h) + +dnl update the value of RL_READLINE_VERSION in readline.h when this changes +LIBVERSION=5.2 + +AC_CANONICAL_HOST + +dnl configure defaults +opt_curses=no +opt_purify=no + +dnl arguments to configure +AC_ARG_WITH(curses, AC_HELP_STRING([--with-curses], [use the curses library instead of the termcap library]), opt_curses=$withval) +AC_ARG_WITH(purify, AC_HELP_STRING([--with-purify], [configure to postprocess with purify]), opt_purify=$withval) + +if test "$opt_curses" = "yes"; then + prefer_curses=yes +fi + +if test "$opt_purify" = yes; then + PURIFY="purify" +else + PURIFY= +fi + +dnl option parsing for optional features +opt_multibyte=yes +opt_static_libs=yes +opt_shared_libs=yes + +AC_ARG_ENABLE(multibyte, AC_HELP_STRING([--enable-multibyte], [enable multibyte characters if OS supports them]), opt_multibyte=$enableval) +AC_ARG_ENABLE(shared, AC_HELP_STRING([--enable-shared], [build shared libraries [[default=YES]]]), opt_shared_libs=$enableval) +AC_ARG_ENABLE(static, AC_HELP_STRING([--enable-static], [build static libraries [[default=YES]]]), opt_static_libs=$enableval) + +if test $opt_multibyte = no; then +AC_DEFINE(NO_MULTIBYTE_SUPPORT) +fi + +dnl load up the cross-building cache file -- add more cases and cache +dnl files as necessary + +dnl Note that host and target machine are the same, and different than the +dnl build machine. + +CROSS_COMPILE= +if test "x$cross_compiling" = "xyes"; then + case "${host}" in + *-cygwin*) + cross_cache=${srcdir}/cross-build/cygwin.cache + ;; + *-mingw*) + cross_cache=${srcdir}/cross-build/mingw.cache + ;; + i[[3456]]86-*-beos*) + cross_cache=${srcdir}/cross-build/x86-beos.cache + ;; + *) echo "configure: cross-compiling for $host is not supported" >&2 + ;; + esac + if test -n "${cross_cache}" && test -r "${cross_cache}"; then + echo "loading cross-build cache file ${cross_cache}" + . ${cross_cache} + fi + unset cross_cache + CROSS_COMPILE='-DCROSS_COMPILING' + AC_SUBST(CROSS_COMPILE) +fi + +echo "" +echo "Beginning configuration for readline-$LIBVERSION for ${host_cpu}-${host_vendor}-${host_os}" +echo "" + +# We want these before the checks, so the checks can modify their values. +test -z "$CFLAGS" && CFLAGS=-g auto_cflags=1 + +AC_PROG_MAKE_SET +AC_PROG_CC +dnl AC_AIX +AC_MINIX + +# If we're using gcc and the user hasn't specified CFLAGS, add -O to CFLAGS. +test -n "$GCC" && test -n "$auto_cflags" && CFLAGS="$CFLAGS -O" + +AC_PROG_GCC_TRADITIONAL +AC_PROG_INSTALL +AC_CHECK_PROG(AR, ar, , ar) +dnl Set default for ARFLAGS, since autoconf does not have a macro for it. +dnl This allows people to set it when running configure or make +test -n "$ARFLAGS" || ARFLAGS="cr" +AC_PROG_RANLIB + +MAKE_SHELL=/bin/sh +AC_SUBST(MAKE_SHELL) + +AC_C_CONST +AC_C_PROTOTYPES +AC_C_CHAR_UNSIGNED + +AC_TYPE_SIGNAL + +AC_TYPE_SIZE_T +AC_CHECK_TYPE(ssize_t, int) + +AC_HEADER_STDC + +AC_HEADER_STAT +AC_HEADER_DIRENT + +AC_CHECK_FUNCS(fcntl kill lstat) +AC_CHECK_FUNCS(memmove putenv select setenv setlocale \ + strcasecmp strpbrk tcgetattr vsnprintf) +AC_CHECK_FUNCS(isascii isxdigit) +AC_CHECK_FUNCS(getpwent getpwnam getpwuid) + +AC_FUNC_STRCOLL + +AC_CHECK_HEADERS(fcntl.h unistd.h stdlib.h varargs.h stdarg.h string.h strings.h \ + limits.h locale.h pwd.h memory.h termcap.h termios.h termio.h) +AC_CHECK_HEADERS(sys/pte.h sys/stream.h sys/select.h sys/file.h) + +AC_CHECK_HEADERS(sys/ptem.h,,, +[[ +#if HAVE_SYS_STREAM_H +# include <sys/stream.h> +#endif +]]) + +BASH_SYS_SIGNAL_VINTAGE +BASH_SYS_REINSTALL_SIGHANDLERS + +BASH_FUNC_POSIX_SETJMP +BASH_FUNC_LSTAT +BASH_FUNC_STRCOLL +BASH_FUNC_CTYPE_NONASCII + +BASH_CHECK_GETPW_FUNCS + +AC_HEADER_TIOCGWINSZ + +BASH_TYPE_SIGHANDLER +BASH_HAVE_TIOCSTAT +BASH_HAVE_FIONREAD +BASH_CHECK_SPEED_T +BASH_STRUCT_WINSIZE +BASH_STRUCT_DIRENT_D_INO +BASH_STRUCT_DIRENT_D_FILENO + +dnl yuck +case "$host_os" in +aix*) prefer_curses=yes ;; +esac +BASH_CHECK_LIB_TERMCAP +if test "$TERMCAP_LIB" = "./lib/termcap/libtermcap.a"; then + if test "$prefer_curses" = yes; then + TERMCAP_LIB=-lcurses + else + TERMCAP_LIB=-ltermcap #default + fi +fi + +BASH_CHECK_MULTIBYTE + +case "$host_cpu" in +*cray*) LOCAL_CFLAGS=-DCRAY ;; +*s390*) LOCAL_CFLAGS=-fsigned-char ;; +esac + +case "$host_os" in +isc*) LOCAL_CFLAGS=-Disc386 ;; +esac + +# shared library configuration section +# +# Shared object configuration section. These values are generated by +# ${srcdir}/support/shobj-conf +# +if test -f ${srcdir}/support/shobj-conf; then + AC_MSG_CHECKING(configuration for building shared libraries) + eval `TERMCAP_LIB=$TERMCAP_LIB ${CONFIG_SHELL-/bin/sh} ${srcdir}/support/shobj-conf -C "${CC}" -c ${host_cpu} -o ${host_os} -v ${host_vendor}` + +# case "$SHLIB_LIBS" in +# *curses*|*termcap*|*termlib*) ;; +# *) SHLIB_LIBS="$SHLIB_LIBS $TERMCAP_LIB" ;; +# esac + + AC_SUBST(SHOBJ_CC) + AC_SUBST(SHOBJ_CFLAGS) + AC_SUBST(SHOBJ_LD) + AC_SUBST(SHOBJ_LDFLAGS) + AC_SUBST(SHOBJ_XLDFLAGS) + AC_SUBST(SHOBJ_LIBS) + AC_SUBST(SHOBJ_STATUS) + AC_SUBST(SHLIB_STATUS) + AC_SUBST(SHLIB_XLDFLAGS) + AC_SUBST(SHLIB_DOT) + AC_SUBST(SHLIB_LIBPREF) + AC_SUBST(SHLIB_LIBSUFF) + AC_SUBST(SHLIB_LIBVERSION) + AC_SUBST(SHLIB_DLLVERSION) + AC_SUBST(SHLIB_LIBS) + AC_MSG_RESULT($SHLIB_STATUS) + + # SHLIB_STATUS is either `supported' or `unsupported'. If it's + # `unsupported', turn off any default shared library building + if test "$SHLIB_STATUS" = 'unsupported'; then + opt_shared_libs=no + fi + + # shared library versioning + # quoted for m4 so I can use character classes + SHLIB_MAJOR=[`expr "$LIBVERSION" : '\([0-9]\)\..*'`] + SHLIB_MINOR=[`expr "$LIBVERSION" : '[0-9]\.\([0-9]\).*'`] + AC_SUBST(SHLIB_MAJOR) + AC_SUBST(SHLIB_MINOR) +fi + +if test "$opt_static_libs" = "yes"; then + STATIC_TARGET=static + STATIC_INSTALL_TARGET=install-static +fi +if test "$opt_shared_libs" = "yes"; then + SHARED_TARGET=shared + SHARED_INSTALL_TARGET=install-shared +fi + +AC_SUBST(STATIC_TARGET) +AC_SUBST(SHARED_TARGET) +AC_SUBST(STATIC_INSTALL_TARGET) +AC_SUBST(SHARED_INSTALL_TARGET) + +case "$host_os" in +msdosdjgpp*) BUILD_DIR=`pwd.exe` ;; # to prevent //d/path/file +*) BUILD_DIR=`pwd` ;; +esac + +case "$BUILD_DIR" in +*\ *) BUILD_DIR=`echo "$BUILD_DIR" | sed 's: :\\\\ :g'` ;; +*) ;; +esac + +AC_SUBST(PURIFY) +AC_SUBST(BUILD_DIR) + +AC_SUBST(CFLAGS) +AC_SUBST(LOCAL_CFLAGS) +AC_SUBST(LOCAL_LDFLAGS) +AC_SUBST(LOCAL_DEFS) + +AC_SUBST(AR) +AC_SUBST(ARFLAGS) + +AC_SUBST(host_cpu) +AC_SUBST(host_os) + +AC_SUBST(LIBVERSION) + +AC_SUBST(TERMCAP_LIB) + +AC_OUTPUT([Makefile doc/Makefile examples/Makefile shlib/Makefile], +[ +# Makefile uses this timestamp file to record whether config.h is up to date. +echo > stamp-h +]) diff --git a/extra/readline/display.c b/extra/readline/display.c new file mode 100644 index 00000000000..4226cdae5a2 --- /dev/null +++ b/extra/readline/display.c @@ -0,0 +1,2452 @@ +/* display.c -- readline redisplay facility. */ + +/* Copyright (C) 1987-2006 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <sys/types.h> + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +#include "posixstat.h" + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#include <stdio.h> + +/* System-specific feature definitions and include files. */ +#include "rldefs.h" +#include "rlmbutil.h" + +/* Termcap library stuff. */ +#include "tcap.h" + +/* Some standard library routines. */ +#include "readline.h" +#include "history.h" + +#include "rlprivate.h" +#include "xmalloc.h" + +#if !defined (strchr) && !defined (__STDC__) +extern char *strchr (), *strrchr (); +#endif /* !strchr && !__STDC__ */ + +static void update_line PARAMS((char *, char *, int, int, int, int)); +static void space_to_eol PARAMS((int)); +static void delete_chars PARAMS((int)); +static void insert_some_chars PARAMS((char *, int, int)); +static void cr PARAMS((void)); + +#if defined (HANDLE_MULTIBYTE) +static int _rl_col_width PARAMS((const char *, int, int)); +static int *_rl_wrapped_line; +#else +# define _rl_col_width(l, s, e) (((e) <= (s)) ? 0 : (e) - (s)) +#endif + +static int *inv_lbreaks, *vis_lbreaks; +static int inv_lbsize, vis_lbsize; + +/* Heuristic used to decide whether it is faster to move from CUR to NEW + by backing up or outputting a carriage return and moving forward. CUR + and NEW are either both buffer positions or absolute screen positions. */ +#define CR_FASTER(new, cur) (((new) + 1) < ((cur) - (new))) + +/* _rl_last_c_pos is an absolute cursor position in multibyte locales and a + buffer index in others. This macro is used when deciding whether the + current cursor position is in the middle of a prompt string containing + invisible characters. */ +#define PROMPT_ENDING_INDEX \ + ((MB_CUR_MAX > 1 && rl_byte_oriented == 0) ? prompt_physical_chars : prompt_last_invisible+1) + + +/* **************************************************************** */ +/* */ +/* Display stuff */ +/* */ +/* **************************************************************** */ + +/* This is the stuff that is hard for me. I never seem to write good + display routines in C. Let's see how I do this time. */ + +/* (PWP) Well... Good for a simple line updater, but totally ignores + the problems of input lines longer than the screen width. + + update_line and the code that calls it makes a multiple line, + automatically wrapping line update. Careful attention needs + to be paid to the vertical position variables. */ + +/* Keep two buffers; one which reflects the current contents of the + screen, and the other to draw what we think the new contents should + be. Then compare the buffers, and make whatever changes to the + screen itself that we should. Finally, make the buffer that we + just drew into be the one which reflects the current contents of the + screen, and place the cursor where it belongs. + + Commands that want to can fix the display themselves, and then let + this function know that the display has been fixed by setting the + RL_DISPLAY_FIXED variable. This is good for efficiency. */ + +/* Application-specific redisplay function. */ +rl_voidfunc_t *rl_redisplay_function = rl_redisplay; + +/* Global variables declared here. */ +/* What YOU turn on when you have handled all redisplay yourself. */ +int rl_display_fixed = 0; + +int _rl_suppress_redisplay = 0; +int _rl_want_redisplay = 0; + +/* The stuff that gets printed out before the actual text of the line. + This is usually pointing to rl_prompt. */ +const char *rl_display_prompt = (const char *)NULL; + +/* Pseudo-global variables declared here. */ + +/* The visible cursor position. If you print some text, adjust this. */ +/* NOTE: _rl_last_c_pos is used as a buffer index when not in a locale + supporting multibyte characters, and an absolute cursor position when + in such a locale. This is an artifact of the donated multibyte support. + Care must be taken when modifying its value. */ +int _rl_last_c_pos = 0; +int _rl_last_v_pos = 0; + +static int cpos_adjusted; +static int cpos_buffer_position; + +/* Number of lines currently on screen minus 1. */ +int _rl_vis_botlin = 0; + +/* Variables used only in this file. */ +/* The last left edge of text that was displayed. This is used when + doing horizontal scrolling. It shifts in thirds of a screenwidth. */ +static int last_lmargin; + +/* The line display buffers. One is the line currently displayed on + the screen. The other is the line about to be displayed. */ +static char *visible_line = (char *)NULL; +static char *invisible_line = (char *)NULL; + +/* A buffer for `modeline' messages. */ +static char msg_buf[128]; + +/* Non-zero forces the redisplay even if we thought it was unnecessary. */ +static int forced_display; + +/* Default and initial buffer size. Can grow. */ +static int line_size = 1024; + +/* Variables to keep track of the expanded prompt string, which may + include invisible characters. */ + +static char *local_prompt, *local_prompt_prefix; +static int local_prompt_len; +static int prompt_visible_length, prompt_prefix_length; + +/* The number of invisible characters in the line currently being + displayed on the screen. */ +static int visible_wrap_offset; + +/* The number of invisible characters in the prompt string. Static so it + can be shared between rl_redisplay and update_line */ +static int wrap_offset; + +/* The index of the last invisible character in the prompt string. */ +static int prompt_last_invisible; + +/* The length (buffer offset) of the first line of the last (possibly + multi-line) buffer displayed on the screen. */ +static int visible_first_line_len; + +/* Number of invisible characters on the first physical line of the prompt. + Only valid when the number of physical characters in the prompt exceeds + (or is equal to) _rl_screenwidth. */ +static int prompt_invis_chars_first_line; + +static int prompt_physical_chars; + +/* Variables to save and restore prompt and display information. */ + +/* These are getting numerous enough that it's time to create a struct. */ + +static char *saved_local_prompt; +static char *saved_local_prefix; +static int saved_last_invisible; +static int saved_visible_length; +static int saved_prefix_length; +static int saved_local_length; +static int saved_invis_chars_first_line; +static int saved_physical_chars; + +/* Expand the prompt string S and return the number of visible + characters in *LP, if LP is not null. This is currently more-or-less + a placeholder for expansion. LIP, if non-null is a place to store the + index of the last invisible character in the returned string. NIFLP, + if non-zero, is a place to store the number of invisible characters in + the first prompt line. The previous are used as byte counts -- indexes + into a character buffer. */ + +/* Current implementation: + \001 (^A) start non-visible characters + \002 (^B) end non-visible characters + all characters except \001 and \002 (following a \001) are copied to + the returned string; all characters except those between \001 and + \002 are assumed to be `visible'. */ + +static char * +expand_prompt (pmt, lp, lip, niflp, vlp) + char *pmt; + int *lp, *lip, *niflp, *vlp; +{ + char *r, *ret, *p, *igstart; + int l, rl, last, ignoring, ninvis, invfl, invflset, physchars; +#if defined (HANDLE_MULTIBYTE) + int ind, pind; +#endif + + /* Short-circuit if we can. */ + if ((MB_CUR_MAX <= 1 || rl_byte_oriented) && strchr (pmt, RL_PROMPT_START_IGNORE) == 0) + { + r = savestring (pmt); + if (lp) + *lp = strlen (r); + if (lip) + *lip = 0; + if (niflp) + *niflp = 0; + if (vlp) + *vlp = lp ? *lp : (int)strlen (r); + return r; + } + + l = strlen (pmt); + r = ret = (char *)xmalloc (l + 1); + + invfl = 0; /* invisible chars in first line of prompt */ + invflset = 0; /* we only want to set invfl once */ + + igstart = 0; + for (rl = ignoring = last = ninvis = physchars = 0, p = pmt; p && *p; p++) + { + /* This code strips the invisible character string markers + RL_PROMPT_START_IGNORE and RL_PROMPT_END_IGNORE */ + if (ignoring == 0 && *p == RL_PROMPT_START_IGNORE) /* XXX - check ignoring? */ + { + ignoring = 1; + igstart = p; + continue; + } + else if (ignoring && *p == RL_PROMPT_END_IGNORE) + { + ignoring = 0; + if (p != (igstart + 1)) + last = r - ret - 1; + continue; + } + else + { +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + pind = p - pmt; + ind = _rl_find_next_mbchar (pmt, pind, 1, MB_FIND_NONZERO); + l = ind - pind; + while (l--) + *r++ = *p++; + if (!ignoring) + { + rl += ind - pind; + physchars += _rl_col_width (pmt, pind, ind); + } + else + ninvis += ind - pind; + p--; /* compensate for later increment */ + } + else +#endif + { + *r++ = *p; + if (!ignoring) + { + rl++; /* visible length byte counter */ + physchars++; + } + else + ninvis++; /* invisible chars byte counter */ + } + + if (invflset == 0 && rl >= _rl_screenwidth) + { + invfl = ninvis; + invflset = 1; + } + } + } + + if (rl < _rl_screenwidth) + invfl = ninvis; + + *r = '\0'; + if (lp) + *lp = rl; + if (lip) + *lip = last; + if (niflp) + *niflp = invfl; + if (vlp) + *vlp = physchars; + return ret; +} + +/* Just strip out RL_PROMPT_START_IGNORE and RL_PROMPT_END_IGNORE from + PMT and return the rest of PMT. */ +char * +_rl_strip_prompt (pmt) + char *pmt; +{ + char *ret; + + ret = expand_prompt (pmt, (int *)NULL, (int *)NULL, (int *)NULL, (int *)NULL); + return ret; +} + +/* + * Expand the prompt string into the various display components, if + * necessary. + * + * local_prompt = expanded last line of string in rl_display_prompt + * (portion after the final newline) + * local_prompt_prefix = portion before last newline of rl_display_prompt, + * expanded via expand_prompt + * prompt_visible_length = number of visible characters in local_prompt + * prompt_prefix_length = number of visible characters in local_prompt_prefix + * + * This function is called once per call to readline(). It may also be + * called arbitrarily to expand the primary prompt. + * + * The return value is the number of visible characters on the last line + * of the (possibly multi-line) prompt. + */ +int +rl_expand_prompt (prompt) + char *prompt; +{ + char *p, *t; + int c; + + /* Clear out any saved values. */ + FREE (local_prompt); + FREE (local_prompt_prefix); + + local_prompt = local_prompt_prefix = (char *)0; + local_prompt_len = 0; + prompt_last_invisible = prompt_invis_chars_first_line = 0; + prompt_visible_length = prompt_physical_chars = 0; + + if (prompt == 0 || *prompt == 0) + return (0); + + p = strrchr (prompt, '\n'); + if (!p) + { + /* The prompt is only one logical line, though it might wrap. */ + local_prompt = expand_prompt (prompt, &prompt_visible_length, + &prompt_last_invisible, + &prompt_invis_chars_first_line, + &prompt_physical_chars); + local_prompt_prefix = (char *)0; + local_prompt_len = local_prompt ? strlen (local_prompt) : 0; + return (prompt_visible_length); + } + else + { + /* The prompt spans multiple lines. */ + t = ++p; + local_prompt = expand_prompt (p, &prompt_visible_length, + &prompt_last_invisible, + (int *)NULL, + &prompt_physical_chars); + c = *t; *t = '\0'; + /* The portion of the prompt string up to and including the + final newline is now null-terminated. */ + local_prompt_prefix = expand_prompt (prompt, &prompt_prefix_length, + (int *)NULL, + &prompt_invis_chars_first_line, + (int *)NULL); + *t = c; + local_prompt_len = local_prompt ? strlen (local_prompt) : 0; + return (prompt_prefix_length); + } +} + +/* Initialize the VISIBLE_LINE and INVISIBLE_LINE arrays, and their associated + arrays of line break markers. MINSIZE is the minimum size of VISIBLE_LINE + and INVISIBLE_LINE; if it is greater than LINE_SIZE, LINE_SIZE is + increased. If the lines have already been allocated, this ensures that + they can hold at least MINSIZE characters. */ +static void +init_line_structures (minsize) + int minsize; +{ + register int n; + + if (invisible_line == 0) /* initialize it */ + { + if (line_size < minsize) + line_size = minsize; + visible_line = (char *)xmalloc (line_size); + invisible_line = (char *)xmalloc (line_size); + } + else if (line_size < minsize) /* ensure it can hold MINSIZE chars */ + { + line_size *= 2; + if (line_size < minsize) + line_size = minsize; + visible_line = (char *)xrealloc (visible_line, line_size); + invisible_line = (char *)xrealloc (invisible_line, line_size); + } + + for (n = minsize; n < line_size; n++) + { + visible_line[n] = 0; + invisible_line[n] = 1; + } + + if (vis_lbreaks == 0) + { + /* should be enough. */ + inv_lbsize = vis_lbsize = 256; + inv_lbreaks = (int *)xmalloc (inv_lbsize * sizeof (int)); + vis_lbreaks = (int *)xmalloc (vis_lbsize * sizeof (int)); +#if defined (HANDLE_MULTIBYTE) + _rl_wrapped_line = (int *)xmalloc (vis_lbsize * sizeof (int)); +#endif + inv_lbreaks[0] = vis_lbreaks[0] = 0; + } +} + +/* Basic redisplay algorithm. */ +void +rl_redisplay () +{ + register int in, out, c, linenum, cursor_linenum; + register char *line; + int inv_botlin, lb_linenum, o_cpos; + int newlines, lpos, temp, modmark; + const char *prompt_this_line; +#if defined (HANDLE_MULTIBYTE) + int num, n0= 0; + wchar_t wc; + size_t wc_bytes; + int wc_width= 0; + mbstate_t ps; + int _rl_wrapped_multicolumn = 0; +#endif + + if (!readline_echoing_p) + return; + + if (!rl_display_prompt) + rl_display_prompt = ""; + + if (invisible_line == 0 || vis_lbreaks == 0) + { + init_line_structures (0); + rl_on_new_line (); + } + + /* Draw the line into the buffer. */ + cpos_buffer_position = -1; + + line = invisible_line; + out = inv_botlin = 0; + + /* Mark the line as modified or not. We only do this for history + lines. */ + modmark = 0; + if (_rl_mark_modified_lines && current_history () && rl_undo_list) + { + line[out++] = '*'; + line[out] = '\0'; + modmark = 1; + } + + /* If someone thought that the redisplay was handled, but the currently + visible line has a different modification state than the one about + to become visible, then correct the caller's misconception. */ + if (visible_line[0] != invisible_line[0]) + rl_display_fixed = 0; + + /* If the prompt to be displayed is the `primary' readline prompt (the + one passed to readline()), use the values we have already expanded. + If not, use what's already in rl_display_prompt. WRAP_OFFSET is the + number of non-visible characters in the prompt string. */ + if (rl_display_prompt == rl_prompt || local_prompt) + { + if (local_prompt_prefix && forced_display) + _rl_output_some_chars (local_prompt_prefix, strlen (local_prompt_prefix)); + + if (local_prompt_len > 0) + { + temp = local_prompt_len + out + 2; + if (temp >= line_size) + { + line_size = (temp + 1024) - (temp % 1024); + visible_line = (char *)xrealloc (visible_line, line_size); + line = invisible_line = (char *)xrealloc (invisible_line, line_size); + } + strncpy (line + out, local_prompt, local_prompt_len); + out += local_prompt_len; + } + line[out] = '\0'; + wrap_offset = local_prompt_len - prompt_visible_length; + } + else + { + int pmtlen; + prompt_this_line = strrchr (rl_display_prompt, '\n'); + if (!prompt_this_line) + prompt_this_line = rl_display_prompt; + else + { + prompt_this_line++; + pmtlen = prompt_this_line - rl_display_prompt; /* temp var */ + if (forced_display) + { + _rl_output_some_chars (rl_display_prompt, pmtlen); + /* Make sure we are at column zero even after a newline, + regardless of the state of terminal output processing. */ + if (pmtlen < 2 || prompt_this_line[-2] != '\r') + cr (); + } + } + + prompt_physical_chars = pmtlen = strlen (prompt_this_line); + temp = pmtlen + out + 2; + if (temp >= line_size) + { + line_size = (temp + 1024) - (temp % 1024); + visible_line = (char *)xrealloc (visible_line, line_size); + line = invisible_line = (char *)xrealloc (invisible_line, line_size); + } + strncpy (line + out, prompt_this_line, pmtlen); + out += pmtlen; + line[out] = '\0'; + wrap_offset = prompt_invis_chars_first_line = 0; + } + +#define CHECK_INV_LBREAKS() \ + do { \ + if (newlines >= (inv_lbsize - 2)) \ + { \ + inv_lbsize *= 2; \ + inv_lbreaks = (int *)xrealloc (inv_lbreaks, inv_lbsize * sizeof (int)); \ + } \ + } while (0) + +#if defined (HANDLE_MULTIBYTE) +#define CHECK_LPOS() \ + do { \ + lpos++; \ + if (lpos >= _rl_screenwidth) \ + { \ + if (newlines >= (inv_lbsize - 2)) \ + { \ + inv_lbsize *= 2; \ + inv_lbreaks = (int *)xrealloc (inv_lbreaks, inv_lbsize * sizeof (int)); \ + _rl_wrapped_line = (int *)xrealloc (_rl_wrapped_line, inv_lbsize * sizeof (int)); \ + } \ + inv_lbreaks[++newlines] = out; \ + _rl_wrapped_line[newlines] = _rl_wrapped_multicolumn; \ + lpos = 0; \ + } \ + } while (0) +#else +#define CHECK_LPOS() \ + do { \ + lpos++; \ + if (lpos >= _rl_screenwidth) \ + { \ + if (newlines >= (inv_lbsize - 2)) \ + { \ + inv_lbsize *= 2; \ + inv_lbreaks = (int *)xrealloc (inv_lbreaks, inv_lbsize * sizeof (int)); \ + } \ + inv_lbreaks[++newlines] = out; \ + lpos = 0; \ + } \ + } while (0) +#endif + + /* inv_lbreaks[i] is where line i starts in the buffer. */ + inv_lbreaks[newlines = 0] = 0; +#if 0 + lpos = out - wrap_offset; +#else + lpos = prompt_physical_chars + modmark; +#endif + +#if defined (HANDLE_MULTIBYTE) + memset (_rl_wrapped_line, 0, vis_lbsize); + num = 0; +#endif + + /* prompt_invis_chars_first_line is the number of invisible characters in + the first physical line of the prompt. + wrap_offset - prompt_invis_chars_first_line is the number of invis + chars on the second line. */ + + /* what if lpos is already >= _rl_screenwidth before we start drawing the + contents of the command line? */ + while (lpos >= _rl_screenwidth) + { + /* fix from Darin Johnson <darin@acuson.com> for prompt string with + invisible characters that is longer than the screen width. The + prompt_invis_chars_first_line variable could be made into an array + saying how many invisible characters there are per line, but that's + probably too much work for the benefit gained. How many people have + prompts that exceed two physical lines? + Additional logic fix from Edward Catmur <ed@catmur.co.uk> */ +#if defined (HANDLE_MULTIBYTE) + int z; + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + n0 = num; + temp = local_prompt_len; + while (num < temp) + { + z = _rl_col_width (local_prompt, n0, num); + if (z > _rl_screenwidth) + { + num = _rl_find_prev_mbchar (local_prompt, num, MB_FIND_ANY); + break; + } + else if (z == _rl_screenwidth) + break; + num++; + } + temp = num; + } + else +#endif /* !HANDLE_MULTIBYTE */ + temp = ((newlines + 1) * _rl_screenwidth); + + /* Now account for invisible characters in the current line. */ + temp += ((local_prompt_prefix == 0) ? ((newlines == 0) ? prompt_invis_chars_first_line + : ((newlines == 1) ? wrap_offset : 0)) + : ((newlines == 0) ? wrap_offset :0)); + + inv_lbreaks[++newlines] = temp; +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + lpos -= _rl_col_width (local_prompt, n0, num); + else +#endif + lpos -= _rl_screenwidth; + } + + /* Draw the rest of the line (after the prompt) into invisible_line, keeping + track of where the cursor is (cpos_buffer_position), the number of the line containing + the cursor (lb_linenum), the last line number (inv_botlin). + It maintains an array of line breaks for display (inv_lbreaks). + This handles expanding tabs for display and displaying meta characters. */ + lb_linenum = 0; +#if defined (HANDLE_MULTIBYTE) + in = 0; + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + memset (&ps, 0, sizeof (mbstate_t)); + wc_bytes = mbrtowc (&wc, rl_line_buffer, rl_end, &ps); + } + else + wc_bytes = 1; + while (in < rl_end) +#else + for (in = 0; in < rl_end; in++) +#endif + { + c = (unsigned char)rl_line_buffer[in]; + +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + if (MB_INVALIDCH (wc_bytes)) + { + /* Byte sequence is invalid or shortened. Assume that the + first byte represents a character. */ + wc_bytes = 1; + /* Assume that a character occupies a single column. */ + wc_width = 1; + memset (&ps, 0, sizeof (mbstate_t)); + } + else if (MB_NULLWCH (wc_bytes)) + break; /* Found '\0' */ + else + { + temp = wcwidth (wc); + wc_width = (temp >= 0) ? temp : 1; + } + } +#endif + + if (out + 8 >= line_size) /* XXX - 8 for \t */ + { + line_size *= 2; + visible_line = (char *)xrealloc (visible_line, line_size); + invisible_line = (char *)xrealloc (invisible_line, line_size); + line = invisible_line; + } + + if (in == rl_point) + { + cpos_buffer_position = out; + lb_linenum = newlines; + } + +#if defined (HANDLE_MULTIBYTE) + if (META_CHAR (c) && _rl_output_meta_chars == 0) /* XXX - clean up */ +#else + if (META_CHAR (c)) +#endif + { + if (_rl_output_meta_chars == 0) + { + sprintf (line + out, "\\%o", c); + + if (lpos + 4 >= _rl_screenwidth) + { + temp = _rl_screenwidth - lpos; + CHECK_INV_LBREAKS (); + inv_lbreaks[++newlines] = out + temp; + lpos = 4 - temp; + } + else + lpos += 4; + + out += 4; + } + else + { + line[out++] = c; + CHECK_LPOS(); + } + } +#if defined (DISPLAY_TABS) + else if (c == '\t') + { + register int newout; + +#if 0 + newout = (out | (int)7) + 1; +#else + newout = out + 8 - lpos % 8; +#endif + temp = newout - out; + if (lpos + temp >= _rl_screenwidth) + { + register int temp2; + temp2 = _rl_screenwidth - lpos; + CHECK_INV_LBREAKS (); + inv_lbreaks[++newlines] = out + temp2; + lpos = temp - temp2; + while (out < newout) + line[out++] = ' '; + } + else + { + while (out < newout) + line[out++] = ' '; + lpos += temp; + } + } +#endif + else if (c == '\n' && _rl_horizontal_scroll_mode == 0 && _rl_term_up && *_rl_term_up) + { + line[out++] = '\0'; /* XXX - sentinel */ + CHECK_INV_LBREAKS (); + inv_lbreaks[++newlines] = out; + lpos = 0; + } + else if (CTRL_CHAR (c) || c == RUBOUT) + { + line[out++] = '^'; + CHECK_LPOS(); + line[out++] = CTRL_CHAR (c) ? UNCTRL (c) : '?'; + CHECK_LPOS(); + } + else + { +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + register int i; + + _rl_wrapped_multicolumn = 0; + + if (_rl_screenwidth < lpos + wc_width) + for (i = lpos; i < _rl_screenwidth; i++) + { + /* The space will be removed in update_line() */ + line[out++] = ' '; + _rl_wrapped_multicolumn++; + CHECK_LPOS(); + } + if (in == rl_point) + { + cpos_buffer_position = out; + lb_linenum = newlines; + } + for (i = in; i < in+(int)wc_bytes; i++) + line[out++] = rl_line_buffer[i]; + for (i = 0; i < wc_width; i++) + CHECK_LPOS(); + } + else + { + line[out++] = c; + CHECK_LPOS(); + } +#else + line[out++] = c; + CHECK_LPOS(); +#endif + } + +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + in += wc_bytes; + wc_bytes = mbrtowc (&wc, rl_line_buffer + in, rl_end - in, &ps); + } + else + in++; +#endif + + } + line[out] = '\0'; + if (cpos_buffer_position < 0) + { + cpos_buffer_position = out; + lb_linenum = newlines; + } + + inv_botlin = newlines; + CHECK_INV_LBREAKS (); + inv_lbreaks[newlines+1] = out; + cursor_linenum = lb_linenum; + + /* CPOS_BUFFER_POSITION == position in buffer where cursor should be placed. + CURSOR_LINENUM == line number where the cursor should be placed. */ + + /* PWP: now is when things get a bit hairy. The visible and invisible + line buffers are really multiple lines, which would wrap every + (screenwidth - 1) characters. Go through each in turn, finding + the changed region and updating it. The line order is top to bottom. */ + + /* If we can move the cursor up and down, then use multiple lines, + otherwise, let long lines display in a single terminal line, and + horizontally scroll it. */ + + if (_rl_horizontal_scroll_mode == 0 && _rl_term_up && *_rl_term_up) + { + int nleft, pos, changed_screen_line, tx; + char empty_str[1] = { 0 }; + + if (!rl_display_fixed || forced_display) + { + forced_display = 0; + + /* If we have more than a screenful of material to display, then + only display a screenful. We should display the last screen, + not the first. */ + if (out >= _rl_screenchars) + { + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + out = _rl_find_prev_mbchar (line, _rl_screenchars, MB_FIND_ANY); + else + out = _rl_screenchars - 1; + } + + /* The first line is at character position 0 in the buffer. The + second and subsequent lines start at inv_lbreaks[N], offset by + OFFSET (which has already been calculated above). */ + +#define W_OFFSET(line, offset) ((line) == 0 ? offset : 0) +#define VIS_LLEN(l) ((l) > _rl_vis_botlin ? 0 : (vis_lbreaks[l+1] - vis_lbreaks[l])) +#define INV_LLEN(l) (inv_lbreaks[l+1] - inv_lbreaks[l]) +#define VIS_CHARS(line) (visible_line + vis_lbreaks[line]) +#define VIS_LINE(line) ((line) > _rl_vis_botlin) ? empty_str : VIS_CHARS(line) +#define INV_LINE(line) (invisible_line + inv_lbreaks[line]) + + /* For each line in the buffer, do the updating display. */ + for (linenum = 0; linenum <= inv_botlin; linenum++) + { + /* This can lead us astray if we execute a program that changes + the locale from a non-multibyte to a multibyte one. */ + o_cpos = _rl_last_c_pos; + cpos_adjusted = 0; + update_line (VIS_LINE(linenum), INV_LINE(linenum), linenum, + VIS_LLEN(linenum), INV_LLEN(linenum), inv_botlin); + + /* update_line potentially changes _rl_last_c_pos, but doesn't + take invisible characters into account, since _rl_last_c_pos + is an absolute cursor position in a multibyte locale. See + if compensating here is the right thing, or if we have to + change update_line itself. There is one case in which + update_line adjusts _rl_last_c_pos itself (so it can pass + _rl_move_cursor_relative accurate values); it communicates + this back by setting cpos_adjusted. If we assume that + _rl_last_c_pos is correct (an absolute cursor position) each + time update_line is called, then we can assume in our + calculations that o_cpos does not need to be adjusted by + wrap_offset. */ + if (linenum == 0 && (MB_CUR_MAX > 1 && rl_byte_oriented == 0) && + cpos_adjusted == 0 && + _rl_last_c_pos != o_cpos && + _rl_last_c_pos > wrap_offset && + o_cpos < prompt_last_invisible) + _rl_last_c_pos -= wrap_offset; + + /* If this is the line with the prompt, we might need to + compensate for invisible characters in the new line. Do + this only if there is not more than one new line (which + implies that we completely overwrite the old visible line) + and the new line is shorter than the old. Make sure we are + at the end of the new line before clearing. */ + if (linenum == 0 && + inv_botlin == 0 && _rl_last_c_pos == out && + (wrap_offset > visible_wrap_offset) && + (_rl_last_c_pos < visible_first_line_len)) + { + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + nleft = _rl_screenwidth - _rl_last_c_pos; + else + nleft = _rl_screenwidth + wrap_offset - _rl_last_c_pos; + if (nleft) + _rl_clear_to_eol (nleft); + } + + /* Since the new first line is now visible, save its length. */ + if (linenum == 0) + visible_first_line_len = (inv_botlin > 0) ? inv_lbreaks[1] : out - wrap_offset; + } + + /* We may have deleted some lines. If so, clear the left over + blank ones at the bottom out. */ + if (_rl_vis_botlin > inv_botlin) + { + char *tt; + for (; linenum <= _rl_vis_botlin; linenum++) + { + tt = VIS_CHARS (linenum); + _rl_move_vert (linenum); + _rl_move_cursor_relative (0, tt); + _rl_clear_to_eol + ((linenum == _rl_vis_botlin) ? (int)strlen (tt) : _rl_screenwidth); + } + } + _rl_vis_botlin = inv_botlin; + + /* CHANGED_SCREEN_LINE is set to 1 if we have moved to a + different screen line during this redisplay. */ + changed_screen_line = _rl_last_v_pos != cursor_linenum; + if (changed_screen_line) + { + _rl_move_vert (cursor_linenum); + /* If we moved up to the line with the prompt using _rl_term_up, + the physical cursor position on the screen stays the same, + but the buffer position needs to be adjusted to account + for invisible characters. */ + if ((MB_CUR_MAX == 1 || rl_byte_oriented) && cursor_linenum == 0 && wrap_offset) + _rl_last_c_pos += wrap_offset; + } + + /* We have to reprint the prompt if it contains invisible + characters, since it's not generally OK to just reprint + the characters from the current cursor position. But we + only need to reprint it if the cursor is before the last + invisible character in the prompt string. */ + nleft = prompt_visible_length + wrap_offset; + if (cursor_linenum == 0 && wrap_offset > 0 && _rl_last_c_pos > 0 && +#if 0 + _rl_last_c_pos <= PROMPT_ENDING_INDEX && local_prompt) +#else + _rl_last_c_pos < PROMPT_ENDING_INDEX && local_prompt) +#endif + { +#if defined (__MSDOS__) + putc ('\r', rl_outstream); +#else + if (_rl_term_cr) + tputs (_rl_term_cr, 1, _rl_output_character_function); +#endif + _rl_output_some_chars (local_prompt, nleft); + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + _rl_last_c_pos = _rl_col_width (local_prompt, 0, nleft) - wrap_offset; + else + _rl_last_c_pos = nleft; + } + + /* Where on that line? And where does that line start + in the buffer? */ + pos = inv_lbreaks[cursor_linenum]; + /* nleft == number of characters in the line buffer between the + start of the line and the desired cursor position. */ + nleft = cpos_buffer_position - pos; + + /* NLEFT is now a number of characters in a buffer. When in a + multibyte locale, however, _rl_last_c_pos is an absolute cursor + position that doesn't take invisible characters in the prompt + into account. We use a fudge factor to compensate. */ + + /* Since _rl_backspace() doesn't know about invisible characters in the + prompt, and there's no good way to tell it, we compensate for + those characters here and call _rl_backspace() directly. */ + if (wrap_offset && cursor_linenum == 0 && nleft < _rl_last_c_pos) + { + /* TX == new physical cursor position in multibyte locale. */ + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + tx = _rl_col_width (&visible_line[pos], 0, nleft) - visible_wrap_offset; + else + tx = nleft; + if (_rl_last_c_pos > tx) + { + _rl_backspace (_rl_last_c_pos - tx); /* XXX */ + _rl_last_c_pos = tx; + } + } + + /* We need to note that in a multibyte locale we are dealing with + _rl_last_c_pos as an absolute cursor position, but moving to a + point specified by a buffer position (NLEFT) that doesn't take + invisible characters into account. */ + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + _rl_move_cursor_relative (nleft, &invisible_line[pos]); + else if (nleft != _rl_last_c_pos) + _rl_move_cursor_relative (nleft, &invisible_line[pos]); + } + } + else /* Do horizontal scrolling. */ + { +#define M_OFFSET(margin, offset) ((margin) == 0 ? offset : 0) + int lmargin, ndisp, nleft, phys_c_pos, t; + + /* Always at top line. */ + _rl_last_v_pos = 0; + + /* Compute where in the buffer the displayed line should start. This + will be LMARGIN. */ + + /* The number of characters that will be displayed before the cursor. */ + ndisp = cpos_buffer_position - wrap_offset; + nleft = prompt_visible_length + wrap_offset; + /* Where the new cursor position will be on the screen. This can be + longer than SCREENWIDTH; if it is, lmargin will be adjusted. */ + phys_c_pos = cpos_buffer_position - (last_lmargin ? last_lmargin : wrap_offset); + t = _rl_screenwidth / 3; + + /* If the number of characters had already exceeded the screenwidth, + last_lmargin will be > 0. */ + + /* If the number of characters to be displayed is more than the screen + width, compute the starting offset so that the cursor is about + two-thirds of the way across the screen. */ + if (phys_c_pos > _rl_screenwidth - 2) + { + lmargin = cpos_buffer_position - (2 * t); + if (lmargin < 0) + lmargin = 0; + /* If the left margin would be in the middle of a prompt with + invisible characters, don't display the prompt at all. */ + if (wrap_offset && lmargin > 0 && lmargin < nleft) + lmargin = nleft; + } + else if (ndisp < _rl_screenwidth - 2) /* XXX - was -1 */ + lmargin = 0; + else if (phys_c_pos < 1) + { + /* If we are moving back towards the beginning of the line and + the last margin is no longer correct, compute a new one. */ + lmargin = ((cpos_buffer_position - 1) / t) * t; /* XXX */ + if (wrap_offset && lmargin > 0 && lmargin < nleft) + lmargin = nleft; + } + else + lmargin = last_lmargin; + + /* If the first character on the screen isn't the first character + in the display line, indicate this with a special character. */ + if (lmargin > 0) + line[lmargin] = '<'; + + /* If SCREENWIDTH characters starting at LMARGIN do not encompass + the whole line, indicate that with a special character at the + right edge of the screen. If LMARGIN is 0, we need to take the + wrap offset into account. */ + t = lmargin + M_OFFSET (lmargin, wrap_offset) + _rl_screenwidth; + if (t < out) + line[t - 1] = '>'; + + if (!rl_display_fixed || forced_display || lmargin != last_lmargin) + { + forced_display = 0; + update_line (&visible_line[last_lmargin], + &invisible_line[lmargin], + 0, + _rl_screenwidth + visible_wrap_offset, + _rl_screenwidth + (lmargin ? 0 : wrap_offset), + 0); + + /* If the visible new line is shorter than the old, but the number + of invisible characters is greater, and we are at the end of + the new line, we need to clear to eol. */ + t = _rl_last_c_pos - M_OFFSET (lmargin, wrap_offset); + if ((M_OFFSET (lmargin, wrap_offset) > visible_wrap_offset) && + (_rl_last_c_pos == out) && + t < visible_first_line_len) + { + nleft = _rl_screenwidth - t; + _rl_clear_to_eol (nleft); + } + visible_first_line_len = out - lmargin - M_OFFSET (lmargin, wrap_offset); + if (visible_first_line_len > _rl_screenwidth) + visible_first_line_len = _rl_screenwidth; + + _rl_move_cursor_relative (cpos_buffer_position - lmargin, &invisible_line[lmargin]); + last_lmargin = lmargin; + } + } + fflush (rl_outstream); + + /* Swap visible and non-visible lines. */ + { + char *vtemp = visible_line; + int *itemp = vis_lbreaks, ntemp = vis_lbsize; + + visible_line = invisible_line; + invisible_line = vtemp; + + vis_lbreaks = inv_lbreaks; + inv_lbreaks = itemp; + + vis_lbsize = inv_lbsize; + inv_lbsize = ntemp; + + rl_display_fixed = 0; + /* If we are displaying on a single line, and last_lmargin is > 0, we + are not displaying any invisible characters, so set visible_wrap_offset + to 0. */ + if (_rl_horizontal_scroll_mode && last_lmargin) + visible_wrap_offset = 0; + else + visible_wrap_offset = wrap_offset; + } +} + +/* PWP: update_line() is based on finding the middle difference of each + line on the screen; vis: + + /old first difference + /beginning of line | /old last same /old EOL + v v v v +old: eddie> Oh, my little gruntle-buggy is to me, as lurgid as +new: eddie> Oh, my little buggy says to me, as lurgid as + ^ ^ ^ ^ + \beginning of line | \new last same \new end of line + \new first difference + + All are character pointers for the sake of speed. Special cases for + no differences, as well as for end of line additions must be handled. + + Could be made even smarter, but this works well enough */ +static void +update_line (old, new, current_line, omax, nmax, inv_botlin) + register char *old, *new; + int current_line, omax, nmax, inv_botlin; +{ + register char *ofd, *ols, *oe, *nfd, *nls, *ne; + int temp, lendiff, wsatend, od, nd; + int current_invis_chars; + int col_lendiff, col_temp; +#if defined (HANDLE_MULTIBYTE) + mbstate_t ps_new, ps_old; + int new_offset, old_offset; +#endif + + /* If we're at the right edge of a terminal that supports xn, we're + ready to wrap around, so do so. This fixes problems with knowing + the exact cursor position and cut-and-paste with certain terminal + emulators. In this calculation, TEMP is the physical screen + position of the cursor. */ + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + temp = _rl_last_c_pos; + else + temp = _rl_last_c_pos - W_OFFSET(_rl_last_v_pos, visible_wrap_offset); + if (temp == _rl_screenwidth && _rl_term_autowrap && !_rl_horizontal_scroll_mode + && _rl_last_v_pos == current_line - 1) + { +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + wchar_t wc; + mbstate_t ps; + int tempwidth, bytes; + size_t ret; + + /* This fixes only double-column characters, but if the wrapped + character comsumes more than three columns, spaces will be + inserted in the string buffer. */ + if (_rl_wrapped_line[current_line] > 0) + _rl_clear_to_eol (_rl_wrapped_line[current_line]); + + memset (&ps, 0, sizeof (mbstate_t)); + ret = mbrtowc (&wc, new, MB_CUR_MAX, &ps); + if (MB_INVALIDCH (ret)) + { + tempwidth = 1; + ret = 1; + } + else if (MB_NULLWCH (ret)) + tempwidth = 0; + else + tempwidth = wcwidth (wc); + + if (tempwidth > 0) + { + int count; + bytes = ret; + for (count = 0; count < bytes; count++) + putc (new[count], rl_outstream); + _rl_last_c_pos = tempwidth; + _rl_last_v_pos++; + memset (&ps, 0, sizeof (mbstate_t)); + ret = mbrtowc (&wc, old, MB_CUR_MAX, &ps); + if (ret != 0 && bytes != 0) + { + if (MB_INVALIDCH (ret)) + memmove (old+bytes, old+1, strlen (old+1)); + else + memmove (old+bytes, old+ret, strlen (old+ret)); + memcpy (old, new, bytes); + } + } + else + { + putc (' ', rl_outstream); + _rl_last_c_pos = 1; + _rl_last_v_pos++; + if (old[0] && new[0]) + old[0] = new[0]; + } + } + else +#endif + { + if (new[0]) + putc (new[0], rl_outstream); + else + putc (' ', rl_outstream); + _rl_last_c_pos = 1; + _rl_last_v_pos++; + if (old[0] && new[0]) + old[0] = new[0]; + } + } + + + /* Find first difference. */ +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + /* See if the old line is a subset of the new line, so that the + only change is adding characters. */ + temp = (omax < nmax) ? omax : nmax; + if (memcmp (old, new, temp) == 0) + { + ofd = old + temp; + nfd = new + temp; + } + else + { + memset (&ps_new, 0, sizeof(mbstate_t)); + memset (&ps_old, 0, sizeof(mbstate_t)); + + if (omax == nmax && STREQN (new, old, omax)) + { + ofd = old + omax; + nfd = new + nmax; + } + else + { + new_offset = old_offset = 0; + for (ofd = old, nfd = new; + (ofd - old < omax) && *ofd && + _rl_compare_chars(old, old_offset, &ps_old, new, new_offset, &ps_new); ) + { + old_offset = _rl_find_next_mbchar (old, old_offset, 1, MB_FIND_ANY); + new_offset = _rl_find_next_mbchar (new, new_offset, 1, MB_FIND_ANY); + ofd = old + old_offset; + nfd = new + new_offset; + } + } + } + } + else +#endif + for (ofd = old, nfd = new; + (ofd - old < omax) && *ofd && (*ofd == *nfd); + ofd++, nfd++) + ; + + /* Move to the end of the screen line. ND and OD are used to keep track + of the distance between ne and new and oe and old, respectively, to + move a subtraction out of each loop. */ + for (od = ofd - old, oe = ofd; od < omax && *oe; oe++, od++); + for (nd = nfd - new, ne = nfd; nd < nmax && *ne; ne++, nd++); + + /* If no difference, continue to next line. */ + if (ofd == oe && nfd == ne) + return; + + wsatend = 1; /* flag for trailing whitespace */ + +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + ols = old + _rl_find_prev_mbchar (old, oe - old, MB_FIND_ANY); + nls = new + _rl_find_prev_mbchar (new, ne - new, MB_FIND_ANY); + while ((ols > ofd) && (nls > nfd)) + { + memset (&ps_old, 0, sizeof (mbstate_t)); + memset (&ps_new, 0, sizeof (mbstate_t)); + +#if 0 + /* On advice from jir@yamato.ibm.com */ + _rl_adjust_point (old, ols - old, &ps_old); + _rl_adjust_point (new, nls - new, &ps_new); +#endif + + if (_rl_compare_chars (old, ols - old, &ps_old, new, nls - new, &ps_new) == 0) + break; + + if (*ols == ' ') + wsatend = 0; + + ols = old + _rl_find_prev_mbchar (old, ols - old, MB_FIND_ANY); + nls = new + _rl_find_prev_mbchar (new, nls - new, MB_FIND_ANY); + } + } + else + { +#endif /* HANDLE_MULTIBYTE */ + ols = oe - 1; /* find last same */ + nls = ne - 1; + while ((ols > ofd) && (nls > nfd) && (*ols == *nls)) + { + if (*ols != ' ') + wsatend = 0; + ols--; + nls--; + } +#if defined (HANDLE_MULTIBYTE) + } +#endif + + if (wsatend) + { + ols = oe; + nls = ne; + } +#if defined (HANDLE_MULTIBYTE) + /* This may not work for stateful encoding, but who cares? To handle + stateful encoding properly, we have to scan each string from the + beginning and compare. */ + else if (_rl_compare_chars (ols, 0, NULL, nls, 0, NULL) == 0) +#else + else if (*ols != *nls) +#endif + { + if (*ols) /* don't step past the NUL */ + { + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + ols = old + _rl_find_next_mbchar (old, ols - old, 1, MB_FIND_ANY); + else + ols++; + } + if (*nls) + { + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + nls = new + _rl_find_next_mbchar (new, nls - new, 1, MB_FIND_ANY); + else + nls++; + } + } + + /* count of invisible characters in the current invisible line. */ + current_invis_chars = W_OFFSET (current_line, wrap_offset); + if (_rl_last_v_pos != current_line) + { + _rl_move_vert (current_line); + if ((MB_CUR_MAX == 1 || rl_byte_oriented) && current_line == 0 && visible_wrap_offset) + _rl_last_c_pos += visible_wrap_offset; + } + + /* If this is the first line and there are invisible characters in the + prompt string, and the prompt string has not changed, and the current + cursor position is before the last invisible character in the prompt, + and the index of the character to move to is past the end of the prompt + string, then redraw the entire prompt string. We can only do this + reliably if the terminal supports a `cr' capability. + + This is not an efficiency hack -- there is a problem with redrawing + portions of the prompt string if they contain terminal escape + sequences (like drawing the `unbold' sequence without a corresponding + `bold') that manifests itself on certain terminals. */ + + lendiff = local_prompt_len; + od = ofd - old; /* index of first difference in visible line */ + if (current_line == 0 && !_rl_horizontal_scroll_mode && + _rl_term_cr && lendiff > prompt_visible_length && _rl_last_c_pos > 0 && + od >= lendiff && _rl_last_c_pos < PROMPT_ENDING_INDEX) + { +#if defined (__MSDOS__) + putc ('\r', rl_outstream); +#else + tputs (_rl_term_cr, 1, _rl_output_character_function); +#endif + _rl_output_some_chars (local_prompt, lendiff); + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + /* We take wrap_offset into account here so we can pass correct + information to _rl_move_cursor_relative. */ + _rl_last_c_pos = _rl_col_width (local_prompt, 0, lendiff) - wrap_offset; + cpos_adjusted = 1; + } + else + _rl_last_c_pos = lendiff; + } + + /* When this function returns, _rl_last_c_pos is correct, and an absolute + cursor postion in multibyte mode, but a buffer index when not in a + multibyte locale. */ + _rl_move_cursor_relative (od, old); +#if 1 +#if defined (HANDLE_MULTIBYTE) + /* We need to indicate that the cursor position is correct in the presence of + invisible characters in the prompt string. Let's see if setting this when + we make sure we're at the end of the drawn prompt string works. */ + if (current_line == 0 && MB_CUR_MAX > 1 && rl_byte_oriented == 0 && _rl_last_c_pos == prompt_physical_chars) + cpos_adjusted = 1; +#endif +#endif + + /* if (len (new) > len (old)) + lendiff == difference in buffer + col_lendiff == difference on screen + When not using multibyte characters, these are equal */ + lendiff = (nls - nfd) - (ols - ofd); + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + col_lendiff = _rl_col_width (new, nfd - new, nls - new) - _rl_col_width (old, ofd - old, ols - old); + else + col_lendiff = lendiff; + + /* If we are changing the number of invisible characters in a line, and + the spot of first difference is before the end of the invisible chars, + lendiff needs to be adjusted. */ + if (current_line == 0 && !_rl_horizontal_scroll_mode && + current_invis_chars != visible_wrap_offset) + { + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + lendiff += visible_wrap_offset - current_invis_chars; + col_lendiff += visible_wrap_offset - current_invis_chars; + } + else + { + lendiff += visible_wrap_offset - current_invis_chars; + col_lendiff = lendiff; + } + } + + /* Insert (diff (len (old), len (new)) ch. */ + temp = ne - nfd; + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + col_temp = _rl_col_width (new, nfd - new, ne - new); + else + col_temp = temp; + + if (col_lendiff > 0) /* XXX - was lendiff */ + { + /* Non-zero if we're increasing the number of lines. */ + int gl = current_line >= _rl_vis_botlin && inv_botlin > _rl_vis_botlin; + /* Sometimes it is cheaper to print the characters rather than + use the terminal's capabilities. If we're growing the number + of lines, make sure we actually cause the new line to wrap + around on auto-wrapping terminals. */ + if (_rl_terminal_can_insert && ((2 * col_temp) >= col_lendiff || _rl_term_IC) && (!_rl_term_autowrap || !gl)) + { + /* If lendiff > prompt_visible_length and _rl_last_c_pos == 0 and + _rl_horizontal_scroll_mode == 1, inserting the characters with + _rl_term_IC or _rl_term_ic will screw up the screen because of the + invisible characters. We need to just draw them. */ + if (*ols && (!_rl_horizontal_scroll_mode || _rl_last_c_pos > 0 || + lendiff <= prompt_visible_length || !current_invis_chars)) + { + insert_some_chars (nfd, lendiff, col_lendiff); + _rl_last_c_pos += col_lendiff; + } + else if ((MB_CUR_MAX == 1 || rl_byte_oriented != 0) && *ols == 0 && lendiff > 0) + { + /* At the end of a line the characters do not have to + be "inserted". They can just be placed on the screen. */ + /* However, this screws up the rest of this block, which + assumes you've done the insert because you can. */ + _rl_output_some_chars (nfd, lendiff); + _rl_last_c_pos += col_lendiff; + } + else + { + /* We have horizontal scrolling and we are not inserting at + the end. We have invisible characters in this line. This + is a dumb update. */ + _rl_output_some_chars (nfd, temp); + _rl_last_c_pos += col_temp; + return; + } + /* Copy (new) chars to screen from first diff to last match. */ + temp = nls - nfd; + if ((temp - lendiff) > 0) + { + _rl_output_some_chars (nfd + lendiff, temp - lendiff); +#if 1 + /* XXX -- this bears closer inspection. Fixes a redisplay bug + reported against bash-3.0-alpha by Andreas Schwab involving + multibyte characters and prompt strings with invisible + characters, but was previously disabled. */ + _rl_last_c_pos += _rl_col_width (nfd+lendiff, 0, temp-col_lendiff); +#else + _rl_last_c_pos += _rl_col_width (nfd+lendiff, 0, temp-lendiff); +#endif + } + } + else + { + /* cannot insert chars, write to EOL */ + _rl_output_some_chars (nfd, temp); + _rl_last_c_pos += col_temp; + /* If we're in a multibyte locale and were before the last invisible + char in the current line (which implies we just output some invisible + characters) we need to adjust _rl_last_c_pos, since it represents + a physical character position. */ + } + } + else /* Delete characters from line. */ + { + /* If possible and inexpensive to use terminal deletion, then do so. */ + if (_rl_term_dc && (2 * col_temp) >= -col_lendiff) + { + /* If all we're doing is erasing the invisible characters in the + prompt string, don't bother. It screws up the assumptions + about what's on the screen. */ + if (_rl_horizontal_scroll_mode && _rl_last_c_pos == 0 && + -lendiff == visible_wrap_offset) + col_lendiff = 0; + + if (col_lendiff) + delete_chars (-col_lendiff); /* delete (diff) characters */ + + /* Copy (new) chars to screen from first diff to last match */ + temp = nls - nfd; + if (temp > 0) + { + _rl_output_some_chars (nfd, temp); + _rl_last_c_pos += _rl_col_width (nfd, 0, temp);; + } + } + /* Otherwise, print over the existing material. */ + else + { + if (temp > 0) + { + _rl_output_some_chars (nfd, temp); + _rl_last_c_pos += col_temp; /* XXX */ + } + lendiff = (oe - old) - (ne - new); + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + col_lendiff = _rl_col_width (old, 0, oe - old) - _rl_col_width (new, 0, ne - new); + else + col_lendiff = lendiff; + + if (col_lendiff) + { + if (_rl_term_autowrap && current_line < inv_botlin) + space_to_eol (col_lendiff); + else + _rl_clear_to_eol (col_lendiff); + } + } + } +} + +/* Tell the update routines that we have moved onto a new (empty) line. */ +int +rl_on_new_line () +{ + if (visible_line) + visible_line[0] = '\0'; + + _rl_last_c_pos = _rl_last_v_pos = 0; + _rl_vis_botlin = last_lmargin = 0; + if (vis_lbreaks) + vis_lbreaks[0] = vis_lbreaks[1] = 0; + visible_wrap_offset = 0; + return 0; +} + +/* Tell the update routines that we have moved onto a new line with the + prompt already displayed. Code originally from the version of readline + distributed with CLISP. rl_expand_prompt must have already been called + (explicitly or implicitly). This still doesn't work exactly right. */ +int +rl_on_new_line_with_prompt () +{ + int prompt_size, i, l, real_screenwidth, newlines; + char *prompt_last_line, *lprompt; + + /* Initialize visible_line and invisible_line to ensure that they can hold + the already-displayed prompt. */ + prompt_size = strlen (rl_prompt) + 1; + init_line_structures (prompt_size); + + /* Make sure the line structures hold the already-displayed prompt for + redisplay. */ + lprompt = local_prompt ? local_prompt : rl_prompt; + strcpy (visible_line, lprompt); + strcpy (invisible_line, lprompt); + + /* If the prompt contains newlines, take the last tail. */ + prompt_last_line = strrchr (rl_prompt, '\n'); + if (!prompt_last_line) + prompt_last_line = rl_prompt; + + l = strlen (prompt_last_line); + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + _rl_last_c_pos = _rl_col_width (prompt_last_line, 0, l); /* XXX */ + else + _rl_last_c_pos = l; + + /* Dissect prompt_last_line into screen lines. Note that here we have + to use the real screenwidth. Readline's notion of screenwidth might be + one less, see terminal.c. */ + real_screenwidth = _rl_screenwidth + (_rl_term_autowrap ? 0 : 1); + _rl_last_v_pos = l / real_screenwidth; + /* If the prompt length is a multiple of real_screenwidth, we don't know + whether the cursor is at the end of the last line, or already at the + beginning of the next line. Output a newline just to be safe. */ + if (l > 0 && (l % real_screenwidth) == 0) + _rl_output_some_chars ("\n", 1); + last_lmargin = 0; + + newlines = 0; i = 0; + while (i <= l) + { + _rl_vis_botlin = newlines; + vis_lbreaks[newlines++] = i; + i += real_screenwidth; + } + vis_lbreaks[newlines] = l; + visible_wrap_offset = 0; + + rl_display_prompt = rl_prompt; /* XXX - make sure it's set */ + + return 0; +} + +/* Actually update the display, period. */ +int +rl_forced_update_display () +{ + register char *temp; + + if (visible_line) + { + temp = visible_line; + while (*temp) + *temp++ = '\0'; + } + rl_on_new_line (); + forced_display++; + (*rl_redisplay_function) (); + return 0; +} + +/* Move the cursor from _rl_last_c_pos to NEW, which are buffer indices. + (Well, when we don't have multibyte characters, _rl_last_c_pos is a + buffer index.) + DATA is the contents of the screen line of interest; i.e., where + the movement is being done. */ +void +_rl_move_cursor_relative (new, data) + int new; + const char *data; +{ + register int i; + int woff; /* number of invisible chars on current line */ + int cpos, dpos; /* current and desired cursor positions */ + + woff = W_OFFSET (_rl_last_v_pos, wrap_offset); + cpos = _rl_last_c_pos; +#if defined (HANDLE_MULTIBYTE) + /* If we have multibyte characters, NEW is indexed by the buffer point in + a multibyte string, but _rl_last_c_pos is the display position. In + this case, NEW's display position is not obvious and must be + calculated. We need to account for invisible characters in this line, + as long as we are past them and they are counted by _rl_col_width. */ + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + dpos = _rl_col_width (data, 0, new); + if (dpos > prompt_last_invisible) /* XXX - don't use woff here */ + { + dpos -= woff; + /* Since this will be assigned to _rl_last_c_pos at the end (more + precisely, _rl_last_c_pos == dpos when this function returns), + let the caller know. */ + cpos_adjusted = 1; + } + } + else +#endif + dpos = new; + + /* If we don't have to do anything, then return. */ + if (cpos == dpos) + return; + + /* It may be faster to output a CR, and then move forwards instead + of moving backwards. */ + /* i == current physical cursor position. */ +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + i = _rl_last_c_pos; + else +#endif + i = _rl_last_c_pos - woff; + if (dpos == 0 || CR_FASTER (dpos, _rl_last_c_pos) || + (_rl_term_autowrap && i == _rl_screenwidth)) + { +#if defined (__MSDOS__) + putc ('\r', rl_outstream); +#else + tputs (_rl_term_cr, 1, _rl_output_character_function); +#endif /* !__MSDOS__ */ + cpos = _rl_last_c_pos = 0; + } + + if (cpos < dpos) + { + /* Move the cursor forward. We do it by printing the command + to move the cursor forward if there is one, else print that + portion of the output buffer again. Which is cheaper? */ + + /* The above comment is left here for posterity. It is faster + to print one character (non-control) than to print a control + sequence telling the terminal to move forward one character. + That kind of control is for people who don't know what the + data is underneath the cursor. */ + + /* However, we need a handle on where the current display position is + in the buffer for the immediately preceding comment to be true. + In multibyte locales, we don't currently have that info available. + Without it, we don't know where the data we have to display begins + in the buffer and we have to go back to the beginning of the screen + line. In this case, we can use the terminal sequence to move forward + if it's available. */ + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + if (_rl_term_forward_char) + { + for (i = cpos; i < dpos; i++) + tputs (_rl_term_forward_char, 1, _rl_output_character_function); + } + else + { + tputs (_rl_term_cr, 1, _rl_output_character_function); + for (i = 0; i < new; i++) + putc (data[i], rl_outstream); + } + } + else + for (i = cpos; i < new; i++) + putc (data[i], rl_outstream); + } + +#if defined (HANDLE_MULTIBYTE) + /* NEW points to the buffer point, but _rl_last_c_pos is the display point. + The byte length of the string is probably bigger than the column width + of the string, which means that if NEW == _rl_last_c_pos, then NEW's + display point is less than _rl_last_c_pos. */ +#endif + else if (cpos > dpos) + _rl_backspace (cpos - dpos); + + _rl_last_c_pos = dpos; +} + +/* PWP: move the cursor up or down. */ +void +_rl_move_vert (to) + int to; +{ + register int delta, i; + + if (_rl_last_v_pos == to || to > _rl_screenheight) + return; + + if ((delta = to - _rl_last_v_pos) > 0) + { + for (i = 0; i < delta; i++) + putc ('\n', rl_outstream); +#if defined (__MSDOS__) + putc ('\r', rl_outstream); +#else + tputs (_rl_term_cr, 1, _rl_output_character_function); +#endif + _rl_last_c_pos = 0; + } + else + { /* delta < 0 */ + if (_rl_term_up && *_rl_term_up) + for (i = 0; i < -delta; i++) + tputs (_rl_term_up, 1, _rl_output_character_function); + } + + _rl_last_v_pos = to; /* Now TO is here */ +} + +/* Physically print C on rl_outstream. This is for functions which know + how to optimize the display. Return the number of characters output. */ +int +rl_show_char (c) + int c; +{ + int n = 1; + if (META_CHAR (c) && (_rl_output_meta_chars == 0)) + { + fprintf (rl_outstream, "M-"); + n += 2; + c = UNMETA (c); + } + +#if defined (DISPLAY_TABS) + if ((CTRL_CHAR (c) && c != '\t') || c == RUBOUT) +#else + if (CTRL_CHAR (c) || c == RUBOUT) +#endif /* !DISPLAY_TABS */ + { + fprintf (rl_outstream, "C-"); + n += 2; + c = CTRL_CHAR (c) ? UNCTRL (c) : '?'; + } + + putc (c, rl_outstream); + fflush (rl_outstream); + return n; +} + +int +rl_character_len (c, pos) + register int c, pos; +{ + unsigned char uc; + + uc = (unsigned char)c; + + if (META_CHAR_FOR_UCHAR(uc)) + return ((_rl_output_meta_chars == 0) ? 4 : 1); + + if (uc == '\t') + { +#if defined (DISPLAY_TABS) + return (((pos | 7) + 1) - pos); +#else + return (2); +#endif /* !DISPLAY_TABS */ + } + + if (CTRL_CHAR (c) || c == RUBOUT) + return (2); + + return ((ISPRINT (uc)) ? 1 : 2); +} +/* How to print things in the "echo-area". The prompt is treated as a + mini-modeline. */ +static int msg_saved_prompt = 0; + +#if defined (USE_VARARGS) +int +#if defined (PREFER_STDARG) +rl_message (const char *format, ...) +#else +rl_message (va_alist) + va_dcl +#endif +{ + va_list args; +#if defined (PREFER_VARARGS) + char *format; +#endif + +#if defined (PREFER_STDARG) + va_start (args, format); +#else + va_start (args); + format = va_arg (args, char *); +#endif + +#if defined (HAVE_VSNPRINTF) + vsnprintf (msg_buf, sizeof (msg_buf) - 1, format, args); +#else + vsprintf (msg_buf, format, args); + msg_buf[sizeof(msg_buf) - 1] = '\0'; /* overflow? */ +#endif + va_end (args); + + if (saved_local_prompt == 0) + { + rl_save_prompt (); + msg_saved_prompt = 1; + } + rl_display_prompt = msg_buf; + local_prompt = expand_prompt (msg_buf, &prompt_visible_length, + &prompt_last_invisible, + &prompt_invis_chars_first_line, + &prompt_physical_chars); + local_prompt_prefix = (char *)NULL; + local_prompt_len = local_prompt ? strlen (local_prompt) : 0; + (*rl_redisplay_function) (); + + return 0; +} +#else /* !USE_VARARGS */ +int +rl_message (format, arg1, arg2) + char *format; +{ + sprintf (msg_buf, format, arg1, arg2); + msg_buf[sizeof(msg_buf) - 1] = '\0'; /* overflow? */ + + rl_display_prompt = msg_buf; + if (saved_local_prompt == 0) + { + rl_save_prompt (); + msg_saved_prompt = 1; + } + local_prompt = expand_prompt (msg_buf, &prompt_visible_length, + &prompt_last_invisible, + &prompt_invis_chars_first_line, + &prompt_physical_chars); + local_prompt_prefix = (char *)NULL; + local_prompt_len = local_prompt ? strlen (local_prompt) : 0; + (*rl_redisplay_function) (); + + return 0; +} +#endif /* !USE_VARARGS */ + +/* How to clear things from the "echo-area". */ +int +rl_clear_message () +{ + rl_display_prompt = rl_prompt; + if (msg_saved_prompt) + { + rl_restore_prompt (); + msg_saved_prompt = 0; + } + (*rl_redisplay_function) (); + return 0; +} + +int +rl_reset_line_state () +{ + rl_on_new_line (); + + rl_display_prompt = rl_prompt ? rl_prompt : ""; + forced_display = 1; + return 0; +} + +void +rl_save_prompt () +{ + saved_local_prompt = local_prompt; + saved_local_prefix = local_prompt_prefix; + saved_prefix_length = prompt_prefix_length; + saved_local_length = local_prompt_len; + saved_last_invisible = prompt_last_invisible; + saved_visible_length = prompt_visible_length; + saved_invis_chars_first_line = prompt_invis_chars_first_line; + saved_physical_chars = prompt_physical_chars; + + local_prompt = local_prompt_prefix = (char *)0; + local_prompt_len = 0; + prompt_last_invisible = prompt_visible_length = prompt_prefix_length = 0; + prompt_invis_chars_first_line = prompt_physical_chars = 0; +} + +void +rl_restore_prompt () +{ + FREE (local_prompt); + FREE (local_prompt_prefix); + + local_prompt = saved_local_prompt; + local_prompt_prefix = saved_local_prefix; + local_prompt_len = saved_local_length; + prompt_prefix_length = saved_prefix_length; + prompt_last_invisible = saved_last_invisible; + prompt_visible_length = saved_visible_length; + prompt_invis_chars_first_line = saved_invis_chars_first_line; + prompt_physical_chars = saved_physical_chars; + + /* can test saved_local_prompt to see if prompt info has been saved. */ + saved_local_prompt = saved_local_prefix = (char *)0; + saved_local_length = 0; + saved_last_invisible = saved_visible_length = saved_prefix_length = 0; + saved_invis_chars_first_line = saved_physical_chars = 0; +} + +char * +_rl_make_prompt_for_search (pchar) + int pchar; +{ + int len; + char *pmt, *p; + + rl_save_prompt (); + + /* We've saved the prompt, and can do anything with the various prompt + strings we need before they're restored. We want the unexpanded + portion of the prompt string after any final newline. */ + p = rl_prompt ? strrchr (rl_prompt, '\n') : 0; + if (p == 0) + { + len = (rl_prompt && *rl_prompt) ? strlen (rl_prompt) : 0; + pmt = (char *)xmalloc (len + 2); + if (len) + strcpy (pmt, rl_prompt); + pmt[len] = pchar; + pmt[len+1] = '\0'; + } + else + { + p++; + len = strlen (p); + pmt = (char *)xmalloc (len + 2); + if (len) + strcpy (pmt, p); + pmt[len] = pchar; + pmt[len+1] = '\0'; + } + + /* will be overwritten by expand_prompt, called from rl_message */ + prompt_physical_chars = saved_physical_chars + 1; + return pmt; +} + +/* Quick redisplay hack when erasing characters at the end of the line. */ +void +_rl_erase_at_end_of_line (l) + int l; +{ + register int i; + + _rl_backspace (l); + for (i = 0; i < l; i++) + putc (' ', rl_outstream); + _rl_backspace (l); + for (i = 0; i < l; i++) + visible_line[--_rl_last_c_pos] = '\0'; + rl_display_fixed++; +} + +/* Clear to the end of the line. COUNT is the minimum + number of character spaces to clear, */ +void +_rl_clear_to_eol (count) + int count; +{ + if (_rl_term_clreol) + tputs (_rl_term_clreol, 1, _rl_output_character_function); + else if (count) + space_to_eol (count); +} + +/* Clear to the end of the line using spaces. COUNT is the minimum + number of character spaces to clear, */ +static void +space_to_eol (count) + int count; +{ + register int i; + + for (i = 0; i < count; i++) + putc (' ', rl_outstream); + + _rl_last_c_pos += count; +} + +void +_rl_clear_screen () +{ + if (_rl_term_clrpag) + tputs (_rl_term_clrpag, 1, _rl_output_character_function); + else + rl_crlf (); +} + +/* Insert COUNT characters from STRING to the output stream at column COL. */ +static void +insert_some_chars (string, count, col) + char *string; + int count, col; +{ +#if defined (__MSDOS__) || defined (__MINGW32__) + _rl_output_some_chars (string, count); +#else + /* DEBUGGING */ + if (MB_CUR_MAX == 1 || rl_byte_oriented) + if (count != col) + fprintf(stderr, "readline: debug: insert_some_chars: count (%d) != col (%d)\n", count, col); + + /* If IC is defined, then we do not have to "enter" insert mode. */ + if (_rl_term_IC) + { + char *buffer; + + buffer = tgoto (_rl_term_IC, 0, col); + tputs (buffer, 1, _rl_output_character_function); + _rl_output_some_chars (string, count); + } + else + { + register int i; + + /* If we have to turn on insert-mode, then do so. */ + if (_rl_term_im && *_rl_term_im) + tputs (_rl_term_im, 1, _rl_output_character_function); + + /* If there is a special command for inserting characters, then + use that first to open up the space. */ + if (_rl_term_ic && *_rl_term_ic) + { + for (i = col; i--; ) + tputs (_rl_term_ic, 1, _rl_output_character_function); + } + + /* Print the text. */ + _rl_output_some_chars (string, count); + + /* If there is a string to turn off insert mode, we had best use + it now. */ + if (_rl_term_ei && *_rl_term_ei) + tputs (_rl_term_ei, 1, _rl_output_character_function); + } +#endif /* __MSDOS__ || __MINGW32__ */ +} + +/* Delete COUNT characters from the display line. */ +static void +delete_chars (count) + int count; +{ + if (count > _rl_screenwidth) /* XXX */ + return; + +#if !defined (__MSDOS__) && !defined (__MINGW32__) + if (_rl_term_DC && *_rl_term_DC) + { + char *buffer; + buffer = tgoto (_rl_term_DC, count, count); + tputs (buffer, count, _rl_output_character_function); + } + else + { + if (_rl_term_dc && *_rl_term_dc) + while (count--) + tputs (_rl_term_dc, 1, _rl_output_character_function); + } +#endif /* !__MSDOS__ && !__MINGW32__ */ +} + +void +_rl_update_final () +{ + int full_lines; + + full_lines = 0; + /* If the cursor is the only thing on an otherwise-blank last line, + compensate so we don't print an extra CRLF. */ + if (_rl_vis_botlin && _rl_last_c_pos == 0 && + visible_line[vis_lbreaks[_rl_vis_botlin]] == 0) + { + _rl_vis_botlin--; + full_lines = 1; + } + _rl_move_vert (_rl_vis_botlin); + /* If we've wrapped lines, remove the final xterm line-wrap flag. */ + if (full_lines && _rl_term_autowrap && (VIS_LLEN(_rl_vis_botlin) == _rl_screenwidth)) + { + char *last_line; + + last_line = &visible_line[vis_lbreaks[_rl_vis_botlin]]; + cpos_buffer_position = -1; /* don't know where we are in buffer */ + _rl_move_cursor_relative (_rl_screenwidth - 1, last_line); /* XXX */ + _rl_clear_to_eol (0); + putc (last_line[_rl_screenwidth - 1], rl_outstream); + } + _rl_vis_botlin = 0; + rl_crlf (); + fflush (rl_outstream); + rl_display_fixed++; +} + +/* Move to the start of the current line. */ +static void +cr () +{ + if (_rl_term_cr) + { +#if defined (__MSDOS__) + putc ('\r', rl_outstream); +#else + tputs (_rl_term_cr, 1, _rl_output_character_function); +#endif + _rl_last_c_pos = 0; + } +} + +/* Redraw the last line of a multi-line prompt that may possibly contain + terminal escape sequences. Called with the cursor at column 0 of the + line to draw the prompt on. */ +static void +redraw_prompt (t) + char *t; +{ + const char *oldp; + + oldp = rl_display_prompt; + rl_save_prompt (); + + rl_display_prompt = t; + local_prompt = expand_prompt (t, &prompt_visible_length, + &prompt_last_invisible, + &prompt_invis_chars_first_line, + &prompt_physical_chars); + local_prompt_prefix = (char *)NULL; + local_prompt_len = local_prompt ? strlen (local_prompt) : 0; + + rl_forced_update_display (); + + rl_display_prompt = oldp; + rl_restore_prompt(); +} + +/* Redisplay the current line after a SIGWINCH is received. */ +void +_rl_redisplay_after_sigwinch () +{ + char *t; + + /* Clear the current line and put the cursor at column 0. Make sure + the right thing happens if we have wrapped to a new screen line. */ + if (_rl_term_cr) + { +#if defined (__MSDOS__) + putc ('\r', rl_outstream); +#else + tputs (_rl_term_cr, 1, _rl_output_character_function); +#endif + _rl_last_c_pos = 0; +#if defined (__MSDOS__) + space_to_eol (_rl_screenwidth); + putc ('\r', rl_outstream); +#else + if (_rl_term_clreol) + tputs (_rl_term_clreol, 1, _rl_output_character_function); + else + { + space_to_eol (_rl_screenwidth); + tputs (_rl_term_cr, 1, _rl_output_character_function); + } +#endif + if (_rl_last_v_pos > 0) + _rl_move_vert (0); + } + else + rl_crlf (); + + /* Redraw only the last line of a multi-line prompt. */ + t = strrchr (rl_display_prompt, '\n'); + if (t) + redraw_prompt (++t); + else + rl_forced_update_display (); +} + +void +_rl_clean_up_for_exit () +{ + if (readline_echoing_p) + { + _rl_move_vert (_rl_vis_botlin); + _rl_vis_botlin = 0; + fflush (rl_outstream); + rl_restart_output (1, 0); + } +} + +void +_rl_erase_entire_line () +{ + cr (); + _rl_clear_to_eol (0); + cr (); + fflush (rl_outstream); +} + +/* return the `current display line' of the cursor -- the number of lines to + move up to get to the first screen line of the current readline line. */ +int +_rl_current_display_line () +{ + int ret, nleft; + + /* Find out whether or not there might be invisible characters in the + editing buffer. */ + if (rl_display_prompt == rl_prompt) + nleft = _rl_last_c_pos - _rl_screenwidth - rl_visible_prompt_length; + else + nleft = _rl_last_c_pos - _rl_screenwidth; + + if (nleft > 0) + ret = 1 + nleft / _rl_screenwidth; + else + ret = 0; + + return ret; +} + +#if defined (HANDLE_MULTIBYTE) +/* Calculate the number of screen columns occupied by STR from START to END. + In the case of multibyte characters with stateful encoding, we have to + scan from the beginning of the string to take the state into account. */ +static int +_rl_col_width (str, start, end) + const char *str; + int start, end; +{ + wchar_t wc; + mbstate_t ps; + int tmp, point, width, max; + + if (end <= start) + return 0; + + memset (&ps, 0, sizeof (mbstate_t)); + + point = 0; + max = end; + + while (point < start) + { + tmp = mbrlen (str + point, max, &ps); + if (MB_INVALIDCH ((size_t)tmp)) + { + /* In this case, the bytes are invalid or too short to compose a + multibyte character, so we assume that the first byte represents + a single character. */ + point++; + max--; + + /* Clear the state of the byte sequence, because in this case the + effect of mbstate is undefined. */ + memset (&ps, 0, sizeof (mbstate_t)); + } + else if (MB_NULLWCH (tmp)) + break; /* Found '\0' */ + else + { + point += tmp; + max -= tmp; + } + } + + /* If START is not a byte that starts a character, then POINT will be + greater than START. In this case, assume that (POINT - START) gives + a byte count that is the number of columns of difference. */ + width = point - start; + + while (point < end) + { + tmp = mbrtowc (&wc, str + point, max, &ps); + if (MB_INVALIDCH ((size_t)tmp)) + { + /* In this case, the bytes are invalid or too short to compose a + multibyte character, so we assume that the first byte represents + a single character. */ + point++; + max--; + + /* and assume that the byte occupies a single column. */ + width++; + + /* Clear the state of the byte sequence, because in this case the + effect of mbstate is undefined. */ + memset (&ps, 0, sizeof (mbstate_t)); + } + else if (MB_NULLWCH (tmp)) + break; /* Found '\0' */ + else + { + point += tmp; + max -= tmp; + tmp = wcwidth(wc); + width += (tmp >= 0) ? tmp : 1; + } + } + + width += point - end; + + return width; +} +#endif /* HANDLE_MULTIBYTE */ diff --git a/extra/readline/emacs_keymap.c b/extra/readline/emacs_keymap.c new file mode 100644 index 00000000000..a42443f39d6 --- /dev/null +++ b/extra/readline/emacs_keymap.c @@ -0,0 +1,873 @@ +/* emacs_keymap.c -- the keymap for emacs_mode in readline (). */ + +/* Copyright (C) 1987, 1989, 1992 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#if !defined (BUFSIZ) +#include <stdio.h> +#endif /* !BUFSIZ */ + +#include "readline.h" + +/* An array of function pointers, one for each possible key. + If the type byte is ISKMAP, then the pointer is the address of + a keymap. */ + +KEYMAP_ENTRY_ARRAY emacs_standard_keymap = { + + /* Control keys. */ + { ISFUNC, rl_set_mark }, /* Control-@ */ + { ISFUNC, rl_beg_of_line }, /* Control-a */ + { ISFUNC, rl_backward_char }, /* Control-b */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-c */ + { ISFUNC, rl_delete }, /* Control-d */ + { ISFUNC, rl_end_of_line }, /* Control-e */ + { ISFUNC, rl_forward_char }, /* Control-f */ + { ISFUNC, rl_abort }, /* Control-g */ + { ISFUNC, rl_rubout }, /* Control-h */ + { ISFUNC, rl_complete }, /* Control-i */ + { ISFUNC, rl_newline }, /* Control-j */ + { ISFUNC, rl_kill_line }, /* Control-k */ + { ISFUNC, rl_clear_screen }, /* Control-l */ + { ISFUNC, rl_newline }, /* Control-m */ + { ISFUNC, rl_get_next_history }, /* Control-n */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-o */ + { ISFUNC, rl_get_previous_history }, /* Control-p */ + { ISFUNC, rl_quoted_insert }, /* Control-q */ + { ISFUNC, rl_reverse_search_history }, /* Control-r */ + { ISFUNC, rl_forward_search_history }, /* Control-s */ + { ISFUNC, rl_transpose_chars }, /* Control-t */ + { ISFUNC, rl_unix_line_discard }, /* Control-u */ + { ISFUNC, rl_quoted_insert }, /* Control-v */ + { ISFUNC, rl_unix_word_rubout }, /* Control-w */ + { ISKMAP, (rl_command_func_t *)emacs_ctlx_keymap }, /* Control-x */ + { ISFUNC, rl_yank }, /* Control-y */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-z */ + { ISKMAP, (rl_command_func_t *)emacs_meta_keymap }, /* Control-[ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-\ */ + { ISFUNC, rl_char_search }, /* Control-] */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-^ */ + { ISFUNC, rl_undo_command }, /* Control-_ */ + + /* The start of printing characters. */ + { ISFUNC, rl_insert }, /* SPACE */ + { ISFUNC, rl_insert }, /* ! */ + { ISFUNC, rl_insert }, /* " */ + { ISFUNC, rl_insert }, /* # */ + { ISFUNC, rl_insert }, /* $ */ + { ISFUNC, rl_insert }, /* % */ + { ISFUNC, rl_insert }, /* & */ + { ISFUNC, rl_insert }, /* ' */ + { ISFUNC, rl_insert }, /* ( */ + { ISFUNC, rl_insert }, /* ) */ + { ISFUNC, rl_insert }, /* * */ + { ISFUNC, rl_insert }, /* + */ + { ISFUNC, rl_insert }, /* , */ + { ISFUNC, rl_insert }, /* - */ + { ISFUNC, rl_insert }, /* . */ + { ISFUNC, rl_insert }, /* / */ + + /* Regular digits. */ + { ISFUNC, rl_insert }, /* 0 */ + { ISFUNC, rl_insert }, /* 1 */ + { ISFUNC, rl_insert }, /* 2 */ + { ISFUNC, rl_insert }, /* 3 */ + { ISFUNC, rl_insert }, /* 4 */ + { ISFUNC, rl_insert }, /* 5 */ + { ISFUNC, rl_insert }, /* 6 */ + { ISFUNC, rl_insert }, /* 7 */ + { ISFUNC, rl_insert }, /* 8 */ + { ISFUNC, rl_insert }, /* 9 */ + + /* A little more punctuation. */ + { ISFUNC, rl_insert }, /* : */ + { ISFUNC, rl_insert }, /* ; */ + { ISFUNC, rl_insert }, /* < */ + { ISFUNC, rl_insert }, /* = */ + { ISFUNC, rl_insert }, /* > */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* @ */ + + /* Uppercase alphabet. */ + { ISFUNC, rl_insert }, /* A */ + { ISFUNC, rl_insert }, /* B */ + { ISFUNC, rl_insert }, /* C */ + { ISFUNC, rl_insert }, /* D */ + { ISFUNC, rl_insert }, /* E */ + { ISFUNC, rl_insert }, /* F */ + { ISFUNC, rl_insert }, /* G */ + { ISFUNC, rl_insert }, /* H */ + { ISFUNC, rl_insert }, /* I */ + { ISFUNC, rl_insert }, /* J */ + { ISFUNC, rl_insert }, /* K */ + { ISFUNC, rl_insert }, /* L */ + { ISFUNC, rl_insert }, /* M */ + { ISFUNC, rl_insert }, /* N */ + { ISFUNC, rl_insert }, /* O */ + { ISFUNC, rl_insert }, /* P */ + { ISFUNC, rl_insert }, /* Q */ + { ISFUNC, rl_insert }, /* R */ + { ISFUNC, rl_insert }, /* S */ + { ISFUNC, rl_insert }, /* T */ + { ISFUNC, rl_insert }, /* U */ + { ISFUNC, rl_insert }, /* V */ + { ISFUNC, rl_insert }, /* W */ + { ISFUNC, rl_insert }, /* X */ + { ISFUNC, rl_insert }, /* Y */ + { ISFUNC, rl_insert }, /* Z */ + + /* Some more punctuation. */ + { ISFUNC, rl_insert }, /* [ */ + { ISFUNC, rl_insert }, /* \ */ + { ISFUNC, rl_insert }, /* ] */ + { ISFUNC, rl_insert }, /* ^ */ + { ISFUNC, rl_insert }, /* _ */ + { ISFUNC, rl_insert }, /* ` */ + + /* Lowercase alphabet. */ + { ISFUNC, rl_insert }, /* a */ + { ISFUNC, rl_insert }, /* b */ + { ISFUNC, rl_insert }, /* c */ + { ISFUNC, rl_insert }, /* d */ + { ISFUNC, rl_insert }, /* e */ + { ISFUNC, rl_insert }, /* f */ + { ISFUNC, rl_insert }, /* g */ + { ISFUNC, rl_insert }, /* h */ + { ISFUNC, rl_insert }, /* i */ + { ISFUNC, rl_insert }, /* j */ + { ISFUNC, rl_insert }, /* k */ + { ISFUNC, rl_insert }, /* l */ + { ISFUNC, rl_insert }, /* m */ + { ISFUNC, rl_insert }, /* n */ + { ISFUNC, rl_insert }, /* o */ + { ISFUNC, rl_insert }, /* p */ + { ISFUNC, rl_insert }, /* q */ + { ISFUNC, rl_insert }, /* r */ + { ISFUNC, rl_insert }, /* s */ + { ISFUNC, rl_insert }, /* t */ + { ISFUNC, rl_insert }, /* u */ + { ISFUNC, rl_insert }, /* v */ + { ISFUNC, rl_insert }, /* w */ + { ISFUNC, rl_insert }, /* x */ + { ISFUNC, rl_insert }, /* y */ + { ISFUNC, rl_insert }, /* z */ + + /* Final punctuation. */ + { ISFUNC, rl_insert }, /* { */ + { ISFUNC, rl_insert }, /* | */ + { ISFUNC, rl_insert }, /* } */ + { ISFUNC, rl_insert }, /* ~ */ + { ISFUNC, rl_rubout }, /* RUBOUT */ + +#if KEYMAP_SIZE > 128 + /* Pure 8-bit characters (128 - 159). + These might be used in some + character sets. */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + + /* ISO Latin-1 characters (160 - 255) */ + { ISFUNC, rl_insert }, /* No-break space */ + { ISFUNC, rl_insert }, /* Inverted exclamation mark */ + { ISFUNC, rl_insert }, /* Cent sign */ + { ISFUNC, rl_insert }, /* Pound sign */ + { ISFUNC, rl_insert }, /* Currency sign */ + { ISFUNC, rl_insert }, /* Yen sign */ + { ISFUNC, rl_insert }, /* Broken bar */ + { ISFUNC, rl_insert }, /* Section sign */ + { ISFUNC, rl_insert }, /* Diaeresis */ + { ISFUNC, rl_insert }, /* Copyright sign */ + { ISFUNC, rl_insert }, /* Feminine ordinal indicator */ + { ISFUNC, rl_insert }, /* Left pointing double angle quotation mark */ + { ISFUNC, rl_insert }, /* Not sign */ + { ISFUNC, rl_insert }, /* Soft hyphen */ + { ISFUNC, rl_insert }, /* Registered sign */ + { ISFUNC, rl_insert }, /* Macron */ + { ISFUNC, rl_insert }, /* Degree sign */ + { ISFUNC, rl_insert }, /* Plus-minus sign */ + { ISFUNC, rl_insert }, /* Superscript two */ + { ISFUNC, rl_insert }, /* Superscript three */ + { ISFUNC, rl_insert }, /* Acute accent */ + { ISFUNC, rl_insert }, /* Micro sign */ + { ISFUNC, rl_insert }, /* Pilcrow sign */ + { ISFUNC, rl_insert }, /* Middle dot */ + { ISFUNC, rl_insert }, /* Cedilla */ + { ISFUNC, rl_insert }, /* Superscript one */ + { ISFUNC, rl_insert }, /* Masculine ordinal indicator */ + { ISFUNC, rl_insert }, /* Right pointing double angle quotation mark */ + { ISFUNC, rl_insert }, /* Vulgar fraction one quarter */ + { ISFUNC, rl_insert }, /* Vulgar fraction one half */ + { ISFUNC, rl_insert }, /* Vulgar fraction three quarters */ + { ISFUNC, rl_insert }, /* Inverted questionk mark */ + { ISFUNC, rl_insert }, /* Latin capital letter a with grave */ + { ISFUNC, rl_insert }, /* Latin capital letter a with acute */ + { ISFUNC, rl_insert }, /* Latin capital letter a with circumflex */ + { ISFUNC, rl_insert }, /* Latin capital letter a with tilde */ + { ISFUNC, rl_insert }, /* Latin capital letter a with diaeresis */ + { ISFUNC, rl_insert }, /* Latin capital letter a with ring above */ + { ISFUNC, rl_insert }, /* Latin capital letter ae */ + { ISFUNC, rl_insert }, /* Latin capital letter c with cedilla */ + { ISFUNC, rl_insert }, /* Latin capital letter e with grave */ + { ISFUNC, rl_insert }, /* Latin capital letter e with acute */ + { ISFUNC, rl_insert }, /* Latin capital letter e with circumflex */ + { ISFUNC, rl_insert }, /* Latin capital letter e with diaeresis */ + { ISFUNC, rl_insert }, /* Latin capital letter i with grave */ + { ISFUNC, rl_insert }, /* Latin capital letter i with acute */ + { ISFUNC, rl_insert }, /* Latin capital letter i with circumflex */ + { ISFUNC, rl_insert }, /* Latin capital letter i with diaeresis */ + { ISFUNC, rl_insert }, /* Latin capital letter eth (Icelandic) */ + { ISFUNC, rl_insert }, /* Latin capital letter n with tilde */ + { ISFUNC, rl_insert }, /* Latin capital letter o with grave */ + { ISFUNC, rl_insert }, /* Latin capital letter o with acute */ + { ISFUNC, rl_insert }, /* Latin capital letter o with circumflex */ + { ISFUNC, rl_insert }, /* Latin capital letter o with tilde */ + { ISFUNC, rl_insert }, /* Latin capital letter o with diaeresis */ + { ISFUNC, rl_insert }, /* Multiplication sign */ + { ISFUNC, rl_insert }, /* Latin capital letter o with stroke */ + { ISFUNC, rl_insert }, /* Latin capital letter u with grave */ + { ISFUNC, rl_insert }, /* Latin capital letter u with acute */ + { ISFUNC, rl_insert }, /* Latin capital letter u with circumflex */ + { ISFUNC, rl_insert }, /* Latin capital letter u with diaeresis */ + { ISFUNC, rl_insert }, /* Latin capital letter Y with acute */ + { ISFUNC, rl_insert }, /* Latin capital letter thorn (Icelandic) */ + { ISFUNC, rl_insert }, /* Latin small letter sharp s (German) */ + { ISFUNC, rl_insert }, /* Latin small letter a with grave */ + { ISFUNC, rl_insert }, /* Latin small letter a with acute */ + { ISFUNC, rl_insert }, /* Latin small letter a with circumflex */ + { ISFUNC, rl_insert }, /* Latin small letter a with tilde */ + { ISFUNC, rl_insert }, /* Latin small letter a with diaeresis */ + { ISFUNC, rl_insert }, /* Latin small letter a with ring above */ + { ISFUNC, rl_insert }, /* Latin small letter ae */ + { ISFUNC, rl_insert }, /* Latin small letter c with cedilla */ + { ISFUNC, rl_insert }, /* Latin small letter e with grave */ + { ISFUNC, rl_insert }, /* Latin small letter e with acute */ + { ISFUNC, rl_insert }, /* Latin small letter e with circumflex */ + { ISFUNC, rl_insert }, /* Latin small letter e with diaeresis */ + { ISFUNC, rl_insert }, /* Latin small letter i with grave */ + { ISFUNC, rl_insert }, /* Latin small letter i with acute */ + { ISFUNC, rl_insert }, /* Latin small letter i with circumflex */ + { ISFUNC, rl_insert }, /* Latin small letter i with diaeresis */ + { ISFUNC, rl_insert }, /* Latin small letter eth (Icelandic) */ + { ISFUNC, rl_insert }, /* Latin small letter n with tilde */ + { ISFUNC, rl_insert }, /* Latin small letter o with grave */ + { ISFUNC, rl_insert }, /* Latin small letter o with acute */ + { ISFUNC, rl_insert }, /* Latin small letter o with circumflex */ + { ISFUNC, rl_insert }, /* Latin small letter o with tilde */ + { ISFUNC, rl_insert }, /* Latin small letter o with diaeresis */ + { ISFUNC, rl_insert }, /* Division sign */ + { ISFUNC, rl_insert }, /* Latin small letter o with stroke */ + { ISFUNC, rl_insert }, /* Latin small letter u with grave */ + { ISFUNC, rl_insert }, /* Latin small letter u with acute */ + { ISFUNC, rl_insert }, /* Latin small letter u with circumflex */ + { ISFUNC, rl_insert }, /* Latin small letter u with diaeresis */ + { ISFUNC, rl_insert }, /* Latin small letter y with acute */ + { ISFUNC, rl_insert }, /* Latin small letter thorn (Icelandic) */ + { ISFUNC, rl_insert } /* Latin small letter y with diaeresis */ +#endif /* KEYMAP_SIZE > 128 */ +}; + +KEYMAP_ENTRY_ARRAY emacs_meta_keymap = { + + /* Meta keys. Just like above, but the high bit is set. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-@ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-a */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-b */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-c */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-d */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-e */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-f */ + { ISFUNC, rl_abort }, /* Meta-Control-g */ + { ISFUNC, rl_backward_kill_word }, /* Meta-Control-h */ + { ISFUNC, rl_tab_insert }, /* Meta-Control-i */ + { ISFUNC, rl_vi_editing_mode }, /* Meta-Control-j */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-k */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-l */ + { ISFUNC, rl_vi_editing_mode }, /* Meta-Control-m */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-n */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-o */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-p */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-q */ + { ISFUNC, rl_revert_line }, /* Meta-Control-r */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-s */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-t */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-u */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-v */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-w */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-x */ + { ISFUNC, rl_yank_nth_arg }, /* Meta-Control-y */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-z */ + + { ISFUNC, rl_complete }, /* Meta-Control-[ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-\ */ + { ISFUNC, rl_backward_char_search }, /* Meta-Control-] */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-^ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-Control-_ */ + + /* The start of printing characters. */ + { ISFUNC, rl_set_mark }, /* Meta-SPACE */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-! */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-" */ + { ISFUNC, rl_insert_comment }, /* Meta-# */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-$ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-% */ + { ISFUNC, rl_tilde_expand }, /* Meta-& */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-' */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-( */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-) */ + { ISFUNC, rl_insert_completions }, /* Meta-* */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-+ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-, */ + { ISFUNC, rl_digit_argument }, /* Meta-- */ + { ISFUNC, rl_yank_last_arg}, /* Meta-. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-/ */ + + /* Regular digits. */ + { ISFUNC, rl_digit_argument }, /* Meta-0 */ + { ISFUNC, rl_digit_argument }, /* Meta-1 */ + { ISFUNC, rl_digit_argument }, /* Meta-2 */ + { ISFUNC, rl_digit_argument }, /* Meta-3 */ + { ISFUNC, rl_digit_argument }, /* Meta-4 */ + { ISFUNC, rl_digit_argument }, /* Meta-5 */ + { ISFUNC, rl_digit_argument }, /* Meta-6 */ + { ISFUNC, rl_digit_argument }, /* Meta-7 */ + { ISFUNC, rl_digit_argument }, /* Meta-8 */ + { ISFUNC, rl_digit_argument }, /* Meta-9 */ + + /* A little more punctuation. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-: */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-; */ + { ISFUNC, rl_beginning_of_history }, /* Meta-< */ + { ISFUNC, rl_possible_completions }, /* Meta-= */ + { ISFUNC, rl_end_of_history }, /* Meta-> */ + { ISFUNC, rl_possible_completions }, /* Meta-? */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-@ */ + + /* Uppercase alphabet. */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-A */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-B */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-C */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-D */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-E */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-F */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-G */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-H */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-I */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-J */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-K */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-L */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-M */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-N */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-O */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-P */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-Q */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-R */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-S */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-T */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-U */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-V */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-W */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-X */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-Y */ + { ISFUNC, rl_do_lowercase_version }, /* Meta-Z */ + + /* Some more punctuation. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-[ */ /* was rl_arrow_keys */ + { ISFUNC, rl_delete_horizontal_space }, /* Meta-\ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-] */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-^ */ + { ISFUNC, rl_yank_last_arg }, /* Meta-_ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-` */ + + /* Lowercase alphabet. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-a */ + { ISFUNC, rl_backward_word }, /* Meta-b */ + { ISFUNC, rl_capitalize_word }, /* Meta-c */ + { ISFUNC, rl_kill_word }, /* Meta-d */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-e */ + { ISFUNC, rl_forward_word }, /* Meta-f */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-g */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-h */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-i */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-j */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-k */ + { ISFUNC, rl_downcase_word }, /* Meta-l */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-m */ + { ISFUNC, rl_noninc_forward_search }, /* Meta-n */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-o */ /* was rl_arrow_keys */ + { ISFUNC, rl_noninc_reverse_search }, /* Meta-p */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-q */ + { ISFUNC, rl_revert_line }, /* Meta-r */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-s */ + { ISFUNC, rl_transpose_words }, /* Meta-t */ + { ISFUNC, rl_upcase_word }, /* Meta-u */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-v */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-w */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-x */ + { ISFUNC, rl_yank_pop }, /* Meta-y */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-z */ + + /* Final punctuation. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-{ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-| */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-} */ + { ISFUNC, rl_tilde_expand }, /* Meta-~ */ + { ISFUNC, rl_backward_kill_word }, /* Meta-rubout */ + +#if KEYMAP_SIZE > 128 + /* Undefined keys. */ + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 } +#endif /* KEYMAP_SIZE > 128 */ +}; + +KEYMAP_ENTRY_ARRAY emacs_ctlx_keymap = { + + /* Control keys. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-@ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-a */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-b */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-c */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-d */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-e */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-f */ + { ISFUNC, rl_abort }, /* Control-g */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-h */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-i */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-j */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-k */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-l */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-m */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-n */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-o */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-p */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-q */ + { ISFUNC, rl_re_read_init_file }, /* Control-r */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-s */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-t */ + { ISFUNC, rl_undo_command }, /* Control-u */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-v */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-w */ + { ISFUNC, rl_exchange_point_and_mark }, /* Control-x */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-y */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-z */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-[ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-\ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-] */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-^ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-_ */ + + /* The start of printing characters. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* SPACE */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ! */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* " */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* # */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* $ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* % */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* & */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ' */ + { ISFUNC, rl_start_kbd_macro }, /* ( */ + { ISFUNC, rl_end_kbd_macro }, /* ) */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* * */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* + */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* , */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* - */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* . */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* / */ + + /* Regular digits. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* 0 */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* 1 */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* 2 */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* 3 */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* 4 */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* 5 */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* 6 */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* 7 */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* 8 */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* 9 */ + + /* A little more punctuation. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* : */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ; */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* < */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* = */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* > */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ? */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* @ */ + + /* Uppercase alphabet. */ + { ISFUNC, rl_do_lowercase_version }, /* A */ + { ISFUNC, rl_do_lowercase_version }, /* B */ + { ISFUNC, rl_do_lowercase_version }, /* C */ + { ISFUNC, rl_do_lowercase_version }, /* D */ + { ISFUNC, rl_do_lowercase_version }, /* E */ + { ISFUNC, rl_do_lowercase_version }, /* F */ + { ISFUNC, rl_do_lowercase_version }, /* G */ + { ISFUNC, rl_do_lowercase_version }, /* H */ + { ISFUNC, rl_do_lowercase_version }, /* I */ + { ISFUNC, rl_do_lowercase_version }, /* J */ + { ISFUNC, rl_do_lowercase_version }, /* K */ + { ISFUNC, rl_do_lowercase_version }, /* L */ + { ISFUNC, rl_do_lowercase_version }, /* M */ + { ISFUNC, rl_do_lowercase_version }, /* N */ + { ISFUNC, rl_do_lowercase_version }, /* O */ + { ISFUNC, rl_do_lowercase_version }, /* P */ + { ISFUNC, rl_do_lowercase_version }, /* Q */ + { ISFUNC, rl_do_lowercase_version }, /* R */ + { ISFUNC, rl_do_lowercase_version }, /* S */ + { ISFUNC, rl_do_lowercase_version }, /* T */ + { ISFUNC, rl_do_lowercase_version }, /* U */ + { ISFUNC, rl_do_lowercase_version }, /* V */ + { ISFUNC, rl_do_lowercase_version }, /* W */ + { ISFUNC, rl_do_lowercase_version }, /* X */ + { ISFUNC, rl_do_lowercase_version }, /* Y */ + { ISFUNC, rl_do_lowercase_version }, /* Z */ + + /* Some more punctuation. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* [ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* \ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ] */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ^ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* _ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ` */ + + /* Lowercase alphabet. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* a */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* b */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* c */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* d */ + { ISFUNC, rl_call_last_kbd_macro }, /* e */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* f */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* g */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* h */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* i */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* j */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* k */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* l */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* m */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* n */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* o */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* p */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* q */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* r */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* s */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* t */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* u */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* v */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* w */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* x */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* y */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* z */ + + /* Final punctuation. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* { */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* | */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* } */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ~ */ + { ISFUNC, rl_backward_kill_line }, /* RUBOUT */ + +#if KEYMAP_SIZE > 128 + /* Undefined keys. */ + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 } +#endif /* KEYMAP_SIZE > 128 */ +}; diff --git a/extra/readline/funmap.c b/extra/readline/funmap.c new file mode 100644 index 00000000000..d3a537dfacd --- /dev/null +++ b/extra/readline/funmap.c @@ -0,0 +1,255 @@ +/* funmap.c -- attach names to functions. */ + +/* Copyright (C) 1987, 1989, 1992 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#if !defined (BUFSIZ) +#include <stdio.h> +#endif /* BUFSIZ */ + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#include "rlconf.h" +#include "readline.h" + +#include "xmalloc.h" + +#ifdef __STDC__ +typedef int QSFUNC (const void *, const void *); +#else +typedef int QSFUNC (); +#endif + +extern int _rl_qsort_string_compare PARAMS((char **, char **)); + +FUNMAP **funmap; +static int funmap_size; +static int funmap_entry; + +/* After initializing the function map, this is the index of the first + program specific function. */ +int funmap_program_specific_entry_start; + +static FUNMAP default_funmap[] = { + { "abort", rl_abort }, + { "accept-line", rl_newline }, + { "arrow-key-prefix", rl_arrow_keys }, + { "backward-byte", rl_backward_byte }, + { "backward-char", rl_backward_char }, + { "backward-delete-char", rl_rubout }, + { "backward-kill-line", rl_backward_kill_line }, + { "backward-kill-word", rl_backward_kill_word }, + { "backward-word", rl_backward_word }, + { "beginning-of-history", rl_beginning_of_history }, + { "beginning-of-line", rl_beg_of_line }, + { "call-last-kbd-macro", rl_call_last_kbd_macro }, + { "capitalize-word", rl_capitalize_word }, + { "character-search", rl_char_search }, + { "character-search-backward", rl_backward_char_search }, + { "clear-screen", rl_clear_screen }, + { "complete", rl_complete }, + { "copy-backward-word", rl_copy_backward_word }, + { "copy-forward-word", rl_copy_forward_word }, + { "copy-region-as-kill", rl_copy_region_to_kill }, + { "delete-char", rl_delete }, + { "delete-char-or-list", rl_delete_or_show_completions }, + { "delete-horizontal-space", rl_delete_horizontal_space }, + { "digit-argument", rl_digit_argument }, + { "do-lowercase-version", rl_do_lowercase_version }, + { "downcase-word", rl_downcase_word }, + { "dump-functions", rl_dump_functions }, + { "dump-macros", rl_dump_macros }, + { "dump-variables", rl_dump_variables }, + { "emacs-editing-mode", rl_emacs_editing_mode }, + { "end-kbd-macro", rl_end_kbd_macro }, + { "end-of-history", rl_end_of_history }, + { "end-of-line", rl_end_of_line }, + { "exchange-point-and-mark", rl_exchange_point_and_mark }, + { "forward-backward-delete-char", rl_rubout_or_delete }, + { "forward-byte", rl_forward_byte }, + { "forward-char", rl_forward_char }, + { "forward-search-history", rl_forward_search_history }, + { "forward-word", rl_forward_word }, + { "history-search-backward", rl_history_search_backward }, + { "history-search-forward", rl_history_search_forward }, + { "insert-comment", rl_insert_comment }, + { "insert-completions", rl_insert_completions }, + { "kill-whole-line", rl_kill_full_line }, + { "kill-line", rl_kill_line }, + { "kill-region", rl_kill_region }, + { "kill-word", rl_kill_word }, + { "menu-complete", rl_menu_complete }, + { "next-history", rl_get_next_history }, + { "non-incremental-forward-search-history", rl_noninc_forward_search }, + { "non-incremental-reverse-search-history", rl_noninc_reverse_search }, + { "non-incremental-forward-search-history-again", rl_noninc_forward_search_again }, + { "non-incremental-reverse-search-history-again", rl_noninc_reverse_search_again }, + { "overwrite-mode", rl_overwrite_mode }, +#ifdef __CYGWIN__ + { "paste-from-clipboard", rl_paste_from_clipboard }, +#endif + { "possible-completions", rl_possible_completions }, + { "previous-history", rl_get_previous_history }, + { "quoted-insert", rl_quoted_insert }, + { "re-read-init-file", rl_re_read_init_file }, + { "redraw-current-line", rl_refresh_line}, + { "reverse-search-history", rl_reverse_search_history }, + { "revert-line", rl_revert_line }, + { "self-insert", rl_insert }, + { "set-mark", rl_set_mark }, + { "start-kbd-macro", rl_start_kbd_macro }, + { "tab-insert", rl_tab_insert }, + { "tilde-expand", rl_tilde_expand }, + { "transpose-chars", rl_transpose_chars }, + { "transpose-words", rl_transpose_words }, + { "tty-status", rl_tty_status }, + { "undo", rl_undo_command }, + { "universal-argument", rl_universal_argument }, + { "unix-filename-rubout", rl_unix_filename_rubout }, + { "unix-line-discard", rl_unix_line_discard }, + { "unix-word-rubout", rl_unix_word_rubout }, + { "upcase-word", rl_upcase_word }, + { "yank", rl_yank }, + { "yank-last-arg", rl_yank_last_arg }, + { "yank-nth-arg", rl_yank_nth_arg }, + { "yank-pop", rl_yank_pop }, + +#if defined (VI_MODE) + { "vi-append-eol", rl_vi_append_eol }, + { "vi-append-mode", rl_vi_append_mode }, + { "vi-arg-digit", rl_vi_arg_digit }, + { "vi-back-to-indent", rl_vi_back_to_indent }, + { "vi-bWord", rl_vi_bWord }, + { "vi-bword", rl_vi_bword }, + { "vi-change-case", rl_vi_change_case }, + { "vi-change-char", rl_vi_change_char }, + { "vi-change-to", rl_vi_change_to }, + { "vi-char-search", rl_vi_char_search }, + { "vi-column", rl_vi_column }, + { "vi-complete", rl_vi_complete }, + { "vi-delete", rl_vi_delete }, + { "vi-delete-to", rl_vi_delete_to }, + { "vi-eWord", rl_vi_eWord }, + { "vi-editing-mode", rl_vi_editing_mode }, + { "vi-end-word", rl_vi_end_word }, + { "vi-eof-maybe", rl_vi_eof_maybe }, + { "vi-eword", rl_vi_eword }, + { "vi-fWord", rl_vi_fWord }, + { "vi-fetch-history", rl_vi_fetch_history }, + { "vi-first-print", rl_vi_first_print }, + { "vi-fword", rl_vi_fword }, + { "vi-goto-mark", rl_vi_goto_mark }, + { "vi-insert-beg", rl_vi_insert_beg }, + { "vi-insertion-mode", rl_vi_insertion_mode }, + { "vi-match", rl_vi_match }, + { "vi-movement-mode", rl_vi_movement_mode }, + { "vi-next-word", rl_vi_next_word }, + { "vi-overstrike", rl_vi_overstrike }, + { "vi-overstrike-delete", rl_vi_overstrike_delete }, + { "vi-prev-word", rl_vi_prev_word }, + { "vi-put", rl_vi_put }, + { "vi-redo", rl_vi_redo }, + { "vi-replace", rl_vi_replace }, + { "vi-rubout", rl_vi_rubout }, + { "vi-search", rl_vi_search }, + { "vi-search-again", rl_vi_search_again }, + { "vi-set-mark", rl_vi_set_mark }, + { "vi-subst", rl_vi_subst }, + { "vi-tilde-expand", rl_vi_tilde_expand }, + { "vi-yank-arg", rl_vi_yank_arg }, + { "vi-yank-to", rl_vi_yank_to }, +#endif /* VI_MODE */ + + {(char *)NULL, (rl_command_func_t *)NULL } +}; + +int +rl_add_funmap_entry (name, function) + const char *name; + rl_command_func_t *function; +{ + if (funmap_entry + 2 >= funmap_size) + { + funmap_size += 64; + funmap = (FUNMAP **)xrealloc (funmap, funmap_size * sizeof (FUNMAP *)); + } + + funmap[funmap_entry] = (FUNMAP *)xmalloc (sizeof (FUNMAP)); + funmap[funmap_entry]->name = name; + funmap[funmap_entry]->function = function; + + funmap[++funmap_entry] = (FUNMAP *)NULL; + return funmap_entry; +} + +static int funmap_initialized; + +/* Make the funmap contain all of the default entries. */ +void +rl_initialize_funmap () +{ + register int i; + + if (funmap_initialized) + return; + + for (i = 0; default_funmap[i].name; i++) + rl_add_funmap_entry (default_funmap[i].name, default_funmap[i].function); + + funmap_initialized = 1; + funmap_program_specific_entry_start = i; +} + +/* Produce a NULL terminated array of known function names. The array + is sorted. The array itself is allocated, but not the strings inside. + You should free () the array when you done, but not the pointrs. */ +const char ** +rl_funmap_names () +{ + const char **result; + int result_size, result_index; + + /* Make sure that the function map has been initialized. */ + rl_initialize_funmap (); + + for (result_index = result_size = 0, result = (const char **)NULL; funmap[result_index]; result_index++) + { + if (result_index + 2 > result_size) + { + result_size += 20; + result = (const char **)xrealloc (result, result_size * sizeof (char *)); + } + + result[result_index] = funmap[result_index]->name; + result[result_index + 1] = (char *)NULL; + } + + qsort (result, result_index, sizeof (char *), (QSFUNC *)_rl_qsort_string_compare); + return (result); +} diff --git a/extra/readline/histexpand.c b/extra/readline/histexpand.c new file mode 100644 index 00000000000..73286614d55 --- /dev/null +++ b/extra/readline/histexpand.c @@ -0,0 +1,1592 @@ +/* histexpand.c -- history expansion. */ + +/* Copyright (C) 1989-2004 Free Software Foundation, Inc. + + This file contains the GNU History Library (the Library), a set of + routines for managing the text of previously typed lines. + + The Library is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + The 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 + General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <stdio.h> + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#if defined (HAVE_UNISTD_H) +# ifndef _MINIX +# include <sys/types.h> +# endif +# include <unistd.h> +#endif + +#include "rlmbutil.h" + +#include "history.h" +#include "histlib.h" + +#include "rlshell.h" +#include "xmalloc.h" + +#define HISTORY_WORD_DELIMITERS " \t\n;&()|<>" +#define HISTORY_QUOTE_CHARACTERS "\"'`" + +#define slashify_in_quotes "\\`\"$" + +typedef int _hist_search_func_t PARAMS((const char *, int)); + +static char error_pointer; + +static char *subst_lhs; +static char *subst_rhs; +static int subst_lhs_len; +static int subst_rhs_len; + +static char *get_history_word_specifier PARAMS((char *, char *, int *)); +static char *history_find_word PARAMS((char *, int)); +static int history_tokenize_word PARAMS((const char *, int)); +static char *history_substring PARAMS((const char *, int, int)); + +static char *quote_breaks PARAMS((char *)); + +/* Variables exported by this file. */ +/* The character that represents the start of a history expansion + request. This is usually `!'. */ +char history_expansion_char = '!'; + +/* The character that invokes word substitution if found at the start of + a line. This is usually `^'. */ +char history_subst_char = '^'; + +/* During tokenization, if this character is seen as the first character + of a word, then it, and all subsequent characters upto a newline are + ignored. For a Bourne shell, this should be '#'. Bash special cases + the interactive comment character to not be a comment delimiter. */ +char history_comment_char = '\0'; + +/* The list of characters which inhibit the expansion of text if found + immediately following history_expansion_char. */ +const char *history_no_expand_chars = " \t\n\r="; + +/* If set to a non-zero value, single quotes inhibit history expansion. + The default is 0. */ +int history_quotes_inhibit_expansion = 0; + +/* Used to split words by history_tokenize_internal. */ +const char *history_word_delimiters = HISTORY_WORD_DELIMITERS; + +/* If set, this points to a function that is called to verify that a + particular history expansion should be performed. */ +rl_linebuf_func_t *history_inhibit_expansion_function; + +/* **************************************************************** */ +/* */ +/* History Expansion */ +/* */ +/* **************************************************************** */ + +/* Hairy history expansion on text, not tokens. This is of general + use, and thus belongs in this library. */ + +/* The last string searched for by a !?string? search. */ +static char *search_string; + +/* The last string matched by a !?string? search. */ +static char *search_match; + +/* Return the event specified at TEXT + OFFSET modifying OFFSET to + point to after the event specifier. Just a pointer to the history + line is returned; NULL is returned in the event of a bad specifier. + You pass STRING with *INDEX equal to the history_expansion_char that + begins this specification. + DELIMITING_QUOTE is a character that is allowed to end the string + specification for what to search for in addition to the normal + characters `:', ` ', `\t', `\n', and sometimes `?'. + So you might call this function like: + line = get_history_event ("!echo:p", &index, 0); */ +char * +get_history_event (string, caller_index, delimiting_quote) + const char *string; + int *caller_index; + int delimiting_quote; +{ + register int i; + register char c; + HIST_ENTRY *entry; + int which, sign, local_index, substring_okay; + _hist_search_func_t *search_func; + char *temp; + + /* The event can be specified in a number of ways. + + !! the previous command + !n command line N + !-n current command-line minus N + !str the most recent command starting with STR + !?str[?] + the most recent command containing STR + + All values N are determined via HISTORY_BASE. */ + + i = *caller_index; + + if (string[i] != history_expansion_char) + return ((char *)NULL); + + /* Move on to the specification. */ + i++; + + sign = 1; + substring_okay = 0; + +#define RETURN_ENTRY(e, w) \ + return ((e = history_get (w)) ? e->line : (char *)NULL) + + /* Handle !! case. */ + if (string[i] == history_expansion_char) + { + i++; + which = history_base + (history_length - 1); + *caller_index = i; + RETURN_ENTRY (entry, which); + } + + /* Hack case of numeric line specification. */ + if (string[i] == '-') + { + sign = -1; + i++; + } + + if (_rl_digit_p (string[i])) + { + /* Get the extent of the digits and compute the value. */ + for (which = 0; _rl_digit_p (string[i]); i++) + which = (which * 10) + _rl_digit_value (string[i]); + + *caller_index = i; + + if (sign < 0) + which = (history_length + history_base) - which; + + RETURN_ENTRY (entry, which); + } + + /* This must be something to search for. If the spec begins with + a '?', then the string may be anywhere on the line. Otherwise, + the string must be found at the start of a line. */ + if (string[i] == '?') + { + substring_okay++; + i++; + } + + /* Only a closing `?' or a newline delimit a substring search string. */ + for (local_index = i; (c = string[i]); i++) + { +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + int v; + mbstate_t ps; + + memset (&ps, 0, sizeof (mbstate_t)); + /* These produce warnings because we're passing a const string to a + function that takes a non-const string. */ + _rl_adjust_point ((char *)string, i, &ps); + if ((v = _rl_get_char_len ((char *)string + i, &ps)) > 1) + { + i += v - 1; + continue; + } + } + +#endif /* HANDLE_MULTIBYTE */ + if ((!substring_okay && (whitespace (c) || c == ':' || + (history_search_delimiter_chars && member (c, history_search_delimiter_chars)) || + string[i] == delimiting_quote)) || + string[i] == '\n' || + (substring_okay && string[i] == '?')) + break; + } + + which = i - local_index; + temp = (char *)xmalloc (1 + which); + if (which) + strncpy (temp, string + local_index, which); + temp[which] = '\0'; + + if (substring_okay && string[i] == '?') + i++; + + *caller_index = i; + +#define FAIL_SEARCH() \ + do { \ + history_offset = history_length; free (temp) ; return (char *)NULL; \ + } while (0) + + /* If there is no search string, try to use the previous search string, + if one exists. If not, fail immediately. */ + if (*temp == '\0' && substring_okay) + { + if (search_string) + { + free (temp); + temp = savestring (search_string); + } + else + FAIL_SEARCH (); + } + + search_func = substring_okay ? history_search : history_search_prefix; + while (1) + { + local_index = (*search_func) (temp, -1); + + if (local_index < 0) + FAIL_SEARCH (); + + if (local_index == 0 || substring_okay) + { + entry = current_history (); + history_offset = history_length; + + /* If this was a substring search, then remember the + string that we matched for word substitution. */ + if (substring_okay) + { + FREE (search_string); + search_string = temp; + + FREE (search_match); + search_match = history_find_word (entry->line, local_index); + } + else + free (temp); + + return (entry->line); + } + + if (history_offset) + history_offset--; + else + FAIL_SEARCH (); + } +#undef FAIL_SEARCH +#undef RETURN_ENTRY +} + +/* Function for extracting single-quoted strings. Used for inhibiting + history expansion within single quotes. */ + +/* Extract the contents of STRING as if it is enclosed in single quotes. + SINDEX, when passed in, is the offset of the character immediately + following the opening single quote; on exit, SINDEX is left pointing + to the closing single quote. */ +static void +hist_string_extract_single_quoted (string, sindex) + char *string; + int *sindex; +{ + register int i; + + for (i = *sindex; string[i] && string[i] != '\''; i++) + ; + + *sindex = i; +} + +static char * +quote_breaks (s) + char *s; +{ + register char *p, *r; + char *ret; + int len = 3; + + for (p = s; p && *p; p++, len++) + { + if (*p == '\'') + len += 3; + else if (whitespace (*p) || *p == '\n') + len += 2; + } + + r = ret = (char *)xmalloc (len); + *r++ = '\''; + for (p = s; p && *p; ) + { + if (*p == '\'') + { + *r++ = '\''; + *r++ = '\\'; + *r++ = '\''; + *r++ = '\''; + p++; + } + else if (whitespace (*p) || *p == '\n') + { + *r++ = '\''; + *r++ = *p++; + *r++ = '\''; + } + else + *r++ = *p++; + } + *r++ = '\''; + *r = '\0'; + return ret; +} + +static char * +hist_error(s, start, current, errtype) + char *s; + int start, current, errtype; +{ + char *temp; + const char *emsg; + int ll, elen; + + ll = current - start; + + switch (errtype) + { + case EVENT_NOT_FOUND: + emsg = "event not found"; + elen = 15; + break; + case BAD_WORD_SPEC: + emsg = "bad word specifier"; + elen = 18; + break; + case SUBST_FAILED: + emsg = "substitution failed"; + elen = 19; + break; + case BAD_MODIFIER: + emsg = "unrecognized history modifier"; + elen = 29; + break; + case NO_PREV_SUBST: + emsg = "no previous substitution"; + elen = 24; + break; + default: + emsg = "unknown expansion error"; + elen = 23; + break; + } + + temp = (char *)xmalloc (ll + elen + 3); + strncpy (temp, s + start, ll); + temp[ll] = ':'; + temp[ll + 1] = ' '; + strcpy (temp + ll + 2, emsg); + return (temp); +} + +/* Get a history substitution string from STR starting at *IPTR + and return it. The length is returned in LENPTR. + + A backslash can quote the delimiter. If the string is the + empty string, the previous pattern is used. If there is + no previous pattern for the lhs, the last history search + string is used. + + If IS_RHS is 1, we ignore empty strings and set the pattern + to "" anyway. subst_lhs is not changed if the lhs is empty; + subst_rhs is allowed to be set to the empty string. */ + +static char * +get_subst_pattern (str, iptr, delimiter, is_rhs, lenptr) + char *str; + int *iptr, delimiter, is_rhs, *lenptr; +{ + register int si, i, j, k; + char *s; +#if defined (HANDLE_MULTIBYTE) + mbstate_t ps; +#endif + + s = (char *)NULL; + i = *iptr; + +#if defined (HANDLE_MULTIBYTE) + memset (&ps, 0, sizeof (mbstate_t)); + _rl_adjust_point (str, i, &ps); +#endif + + for (si = i; str[si] && str[si] != delimiter; si++) +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + int v; + if ((v = _rl_get_char_len (str + si, &ps)) > 1) + si += v - 1; + else if (str[si] == '\\' && str[si + 1] == delimiter) + si++; + } + else +#endif /* HANDLE_MULTIBYTE */ + if (str[si] == '\\' && str[si + 1] == delimiter) + si++; + + if (si > i || is_rhs) + { + s = (char *)xmalloc (si - i + 1); + for (j = 0, k = i; k < si; j++, k++) + { + /* Remove a backslash quoting the search string delimiter. */ + if (str[k] == '\\' && str[k + 1] == delimiter) + k++; + s[j] = str[k]; + } + s[j] = '\0'; + if (lenptr) + *lenptr = j; + } + + i = si; + if (str[i]) + i++; + *iptr = i; + + return s; +} + +static void +postproc_subst_rhs () +{ + char *new; + int i, j, new_size; + + new = (char *)xmalloc (new_size = subst_rhs_len + subst_lhs_len); + for (i = j = 0; i < subst_rhs_len; i++) + { + if (subst_rhs[i] == '&') + { + if (j + subst_lhs_len >= new_size) + new = (char *)xrealloc (new, (new_size = new_size * 2 + subst_lhs_len)); + strcpy (new + j, subst_lhs); + j += subst_lhs_len; + } + else + { + /* a single backslash protects the `&' from lhs interpolation */ + if (subst_rhs[i] == '\\' && subst_rhs[i + 1] == '&') + i++; + if (j >= new_size) + new = (char *)xrealloc (new, new_size *= 2); + new[j++] = subst_rhs[i]; + } + } + new[j] = '\0'; + free (subst_rhs); + subst_rhs = new; + subst_rhs_len = j; +} + +/* Expand the bulk of a history specifier starting at STRING[START]. + Returns 0 if everything is OK, -1 if an error occurred, and 1 + if the `p' modifier was supplied and the caller should just print + the returned string. Returns the new index into string in + *END_INDEX_PTR, and the expanded specifier in *RET_STRING. */ +static int +history_expand_internal (string, start, end_index_ptr, ret_string, current_line) + char *string; + int start, *end_index_ptr; + char **ret_string; + char *current_line; /* for !# */ +{ + int i, n, starting_index; + int substitute_globally, subst_bywords, want_quotes, print_only; + char *event, *temp, *result, *tstr, *t, c, *word_spec; + int result_len; +#if defined (HANDLE_MULTIBYTE) + mbstate_t ps; + + memset (&ps, 0, sizeof (mbstate_t)); +#endif + + result = (char *)xmalloc (result_len = 128); + + i = start; + + /* If it is followed by something that starts a word specifier, + then !! is implied as the event specifier. */ + + if (member (string[i + 1], ":$*%^")) + { + char fake_s[3]; + int fake_i = 0; + i++; + fake_s[0] = fake_s[1] = history_expansion_char; + fake_s[2] = '\0'; + event = get_history_event (fake_s, &fake_i, 0); + } + else if (string[i + 1] == '#') + { + i += 2; + event = current_line; + } + else + { + int quoted_search_delimiter = 0; + + /* If the character before this `!' is a double or single + quote, then this expansion takes place inside of the + quoted string. If we have to search for some text ("!foo"), + allow the delimiter to end the search string. */ +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + int ch, l; + l = _rl_find_prev_mbchar (string, i, MB_FIND_ANY); + ch = string[l]; + /* XXX - original patch had i - 1 ??? If i == 0 it would fail. */ + if (i && (ch == '\'' || ch == '"')) + quoted_search_delimiter = ch; + } + else +#endif /* HANDLE_MULTIBYTE */ + if (i && (string[i - 1] == '\'' || string[i - 1] == '"')) + quoted_search_delimiter = string[i - 1]; + + event = get_history_event (string, &i, quoted_search_delimiter); + } + + if (event == 0) + { + *ret_string = hist_error (string, start, i, EVENT_NOT_FOUND); + free (result); + return (-1); + } + + /* If a word specifier is found, then do what that requires. */ + starting_index = i; + word_spec = get_history_word_specifier (string, event, &i); + + /* There is no such thing as a `malformed word specifier'. However, + it is possible for a specifier that has no match. In that case, + we complain. */ + if (word_spec == (char *)&error_pointer) + { + *ret_string = hist_error (string, starting_index, i, BAD_WORD_SPEC); + free (result); + return (-1); + } + + /* If no word specifier, than the thing of interest was the event. */ + temp = word_spec ? savestring (word_spec) : savestring (event); + FREE (word_spec); + + /* Perhaps there are other modifiers involved. Do what they say. */ + want_quotes = substitute_globally = subst_bywords = print_only = 0; + starting_index = i; + + while (string[i] == ':') + { + c = string[i + 1]; + + if (c == 'g' || c == 'a') + { + substitute_globally = 1; + i++; + c = string[i + 1]; + } + else if (c == 'G') + { + subst_bywords = 1; + i++; + c = string[i + 1]; + } + + switch (c) + { + default: + *ret_string = hist_error (string, i+1, i+2, BAD_MODIFIER); + free (result); + free (temp); + return -1; + + case 'q': + want_quotes = 'q'; + break; + + case 'x': + want_quotes = 'x'; + break; + + /* :p means make this the last executed line. So we + return an error state after adding this line to the + history. */ + case 'p': + print_only++; + break; + + /* :t discards all but the last part of the pathname. */ + case 't': + tstr = strrchr (temp, '/'); + if (tstr) + { + tstr++; + t = savestring (tstr); + free (temp); + temp = t; + } + break; + + /* :h discards the last part of a pathname. */ + case 'h': + tstr = strrchr (temp, '/'); + if (tstr) + *tstr = '\0'; + break; + + /* :r discards the suffix. */ + case 'r': + tstr = strrchr (temp, '.'); + if (tstr) + *tstr = '\0'; + break; + + /* :e discards everything but the suffix. */ + case 'e': + tstr = strrchr (temp, '.'); + if (tstr) + { + t = savestring (tstr); + free (temp); + temp = t; + } + break; + + /* :s/this/that substitutes `that' for the first + occurrence of `this'. :gs/this/that substitutes `that' + for each occurrence of `this'. :& repeats the last + substitution. :g& repeats the last substitution + globally. */ + + case '&': + case 's': + { + char *new_event; + int delimiter, failed, si, l_temp, we; + + if (c == 's') + { + if (i + 2 < (int)strlen (string)) + { +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + _rl_adjust_point (string, i + 2, &ps); + if (_rl_get_char_len (string + i + 2, &ps) > 1) + delimiter = 0; + else + delimiter = string[i + 2]; + } + else +#endif /* HANDLE_MULTIBYTE */ + delimiter = string[i + 2]; + } + else + break; /* no search delimiter */ + + i += 3; + + t = get_subst_pattern (string, &i, delimiter, 0, &subst_lhs_len); + /* An empty substitution lhs with no previous substitution + uses the last search string as the lhs. */ + if (t) + { + FREE (subst_lhs); + subst_lhs = t; + } + else if (!subst_lhs) + { + if (search_string && *search_string) + { + subst_lhs = savestring (search_string); + subst_lhs_len = strlen (subst_lhs); + } + else + { + subst_lhs = (char *) NULL; + subst_lhs_len = 0; + } + } + + FREE (subst_rhs); + subst_rhs = get_subst_pattern (string, &i, delimiter, 1, &subst_rhs_len); + + /* If `&' appears in the rhs, it's supposed to be replaced + with the lhs. */ + if (member ('&', subst_rhs)) + postproc_subst_rhs (); + } + else + i += 2; + + /* If there is no lhs, the substitution can't succeed. */ + if (subst_lhs_len == 0) + { + *ret_string = hist_error (string, starting_index, i, NO_PREV_SUBST); + free (result); + free (temp); + return -1; + } + + l_temp = strlen (temp); + /* Ignore impossible cases. */ + if (subst_lhs_len > l_temp) + { + *ret_string = hist_error (string, starting_index, i, SUBST_FAILED); + free (result); + free (temp); + return (-1); + } + + /* Find the first occurrence of THIS in TEMP. */ + /* Substitute SUBST_RHS for SUBST_LHS in TEMP. There are three + cases to consider: + + 1. substitute_globally == subst_bywords == 0 + 2. substitute_globally == 1 && subst_bywords == 0 + 3. substitute_globally == 0 && subst_bywords == 1 + + In the first case, we substitute for the first occurrence only. + In the second case, we substitute for every occurrence. + In the third case, we tokenize into words and substitute the + first occurrence of each word. */ + + si = we = 0; + for (failed = 1; (si + subst_lhs_len) <= l_temp; si++) + { + /* First skip whitespace and find word boundaries if + we're past the end of the word boundary we found + the last time. */ + if (subst_bywords && si > we) + { + for (; temp[si] && whitespace (temp[si]); si++) + ; + we = history_tokenize_word (temp, si); + } + + if (STREQN (temp+si, subst_lhs, subst_lhs_len)) + { + int len = subst_rhs_len - subst_lhs_len + l_temp; + new_event = (char *)xmalloc (1 + len); + strncpy (new_event, temp, si); + strncpy (new_event + si, subst_rhs, subst_rhs_len); + strncpy (new_event + si + subst_rhs_len, + temp + si + subst_lhs_len, + l_temp - (si + subst_lhs_len)); + new_event[len] = '\0'; + free (temp); + temp = new_event; + + failed = 0; + + if (substitute_globally) + { + /* Reported to fix a bug that causes it to skip every + other match when matching a single character. Was + si += subst_rhs_len previously. */ + si += subst_rhs_len - 1; + l_temp = strlen (temp); + substitute_globally++; + continue; + } + else if (subst_bywords) + { + si = we; + l_temp = strlen (temp); + continue; + } + else + break; + } + } + + if (substitute_globally > 1) + { + substitute_globally = 0; + continue; /* don't want to increment i */ + } + + if (failed == 0) + continue; /* don't want to increment i */ + + *ret_string = hist_error (string, starting_index, i, SUBST_FAILED); + free (result); + free (temp); + return (-1); + } + } + i += 2; + } + /* Done with modfiers. */ + /* Believe it or not, we have to back the pointer up by one. */ + --i; + + if (want_quotes) + { + char *x; + + if (want_quotes == 'q') + x = sh_single_quote (temp); + else if (want_quotes == 'x') + x = quote_breaks (temp); + else + x = savestring (temp); + + free (temp); + temp = x; + } + + n = strlen (temp); + if (n >= result_len) + result = (char *)xrealloc (result, n + 2); + strcpy (result, temp); + free (temp); + + *end_index_ptr = i; + *ret_string = result; + return (print_only); +} + +/* Expand the string STRING, placing the result into OUTPUT, a pointer + to a string. Returns: + + -1) If there was an error in expansion. + 0) If no expansions took place (or, if the only change in + the text was the de-slashifying of the history expansion + character) + 1) If expansions did take place + 2) If the `p' modifier was given and the caller should print the result + + If an error ocurred in expansion, then OUTPUT contains a descriptive + error message. */ + +#define ADD_STRING(s) \ + do \ + { \ + int sl = strlen (s); \ + j += sl; \ + if (j >= result_len) \ + { \ + while (j >= result_len) \ + result_len += 128; \ + result = (char *)xrealloc (result, result_len); \ + } \ + strcpy (result + j - sl, s); \ + } \ + while (0) + +#define ADD_CHAR(c) \ + do \ + { \ + if (j >= result_len - 1) \ + result = (char *)xrealloc (result, result_len += 64); \ + result[j++] = c; \ + result[j] = '\0'; \ + } \ + while (0) + +int +history_expand (hstring, output) + char *hstring; + char **output; +{ + register int j; + int i, r, l, passc, cc, modified, eindex, only_printing, dquote; + char *string; + + /* The output string, and its length. */ + int result_len; + char *result; + +#if defined (HANDLE_MULTIBYTE) + char mb[MB_LEN_MAX]; + mbstate_t ps; +#endif + + /* Used when adding the string. */ + char *temp; + + if (output == 0) + return 0; + + /* Setting the history expansion character to 0 inhibits all + history expansion. */ + if (history_expansion_char == 0) + { + *output = savestring (hstring); + return (0); + } + + /* Prepare the buffer for printing error messages. */ + result = (char *)xmalloc (result_len = 256); + result[0] = '\0'; + + only_printing = modified = 0; + l = strlen (hstring); + + /* Grovel the string. Only backslash and single quotes can quote the + history escape character. We also handle arg specifiers. */ + + /* Before we grovel forever, see if the history_expansion_char appears + anywhere within the text. */ + + /* The quick substitution character is a history expansion all right. That + is to say, "^this^that^" is equivalent to "!!:s^this^that^", and in fact, + that is the substitution that we do. */ + if (hstring[0] == history_subst_char) + { + string = (char *)xmalloc (l + 5); + + string[0] = string[1] = history_expansion_char; + string[2] = ':'; + string[3] = 's'; + strcpy (string + 4, hstring); + l += 4; + } + else + { +#if defined (HANDLE_MULTIBYTE) + memset (&ps, 0, sizeof (mbstate_t)); +#endif + + string = hstring; + /* If not quick substitution, still maybe have to do expansion. */ + + /* `!' followed by one of the characters in history_no_expand_chars + is NOT an expansion. */ + for (i = dquote = 0; string[i]; i++) + { +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + int v; + v = _rl_get_char_len (string + i, &ps); + if (v > 1) + { + i += v - 1; + continue; + } + } +#endif /* HANDLE_MULTIBYTE */ + + cc = string[i + 1]; + /* The history_comment_char, if set, appearing at the beginning + of a word signifies that the rest of the line should not have + history expansion performed on it. + Skip the rest of the line and break out of the loop. */ + if (history_comment_char && string[i] == history_comment_char && + (i == 0 || member (string[i - 1], history_word_delimiters))) + { + while (string[i]) + i++; + break; + } + else if (string[i] == history_expansion_char) + { + if (!cc || member (cc, history_no_expand_chars)) + continue; + /* If the calling application has set + history_inhibit_expansion_function to a function that checks + for special cases that should not be history expanded, + call the function and skip the expansion if it returns a + non-zero value. */ + else if (history_inhibit_expansion_function && + (*history_inhibit_expansion_function) (string, i)) + continue; + else + break; + } + /* Shell-like quoting: allow backslashes to quote double quotes + inside a double-quoted string. */ + else if (dquote && string[i] == '\\' && cc == '"') + i++; + /* More shell-like quoting: if we're paying attention to single + quotes and letting them quote the history expansion character, + then we need to pay attention to double quotes, because single + quotes are not special inside double-quoted strings. */ + else if (history_quotes_inhibit_expansion && string[i] == '"') + { + dquote = 1 - dquote; + } + else if (dquote == 0 && history_quotes_inhibit_expansion && string[i] == '\'') + { + /* If this is bash, single quotes inhibit history expansion. */ + i++; + hist_string_extract_single_quoted (string, &i); + } + else if (history_quotes_inhibit_expansion && string[i] == '\\') + { + /* If this is bash, allow backslashes to quote single + quotes and the history expansion character. */ + if (cc == '\'' || cc == history_expansion_char) + i++; + } + + } + + if (string[i] != history_expansion_char) + { + free (result); + *output = savestring (string); + return (0); + } + } + + /* Extract and perform the substitution. */ + for (passc = dquote = i = j = 0; i < l; i++) + { + int tchar = string[i]; + + if (passc) + { + passc = 0; + ADD_CHAR (tchar); + continue; + } + +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + int k, c; + + c = tchar; + memset (mb, 0, sizeof (mb)); + for (k = 0; k < MB_LEN_MAX; k++) + { + mb[k] = (char)c; + memset (&ps, 0, sizeof (mbstate_t)); + if (_rl_get_char_len (mb, &ps) == -2) + c = string[++i]; + else + break; + } + if (strlen (mb) > 1) + { + ADD_STRING (mb); + break; + } + } +#endif /* HANDLE_MULTIBYTE */ + + if (tchar == history_expansion_char) + tchar = -3; + else if (tchar == history_comment_char) + tchar = -2; + + switch (tchar) + { + default: + ADD_CHAR (string[i]); + break; + + case '\\': + passc++; + ADD_CHAR (tchar); + break; + + case '"': + dquote = 1 - dquote; + ADD_CHAR (tchar); + break; + + case '\'': + { + /* If history_quotes_inhibit_expansion is set, single quotes + inhibit history expansion. */ + if (dquote == 0 && history_quotes_inhibit_expansion) + { + int quote, slen; + + quote = i++; + hist_string_extract_single_quoted (string, &i); + + slen = i - quote + 2; + temp = (char *)xmalloc (slen); + strncpy (temp, string + quote, slen); + temp[slen - 1] = '\0'; + ADD_STRING (temp); + free (temp); + } + else + ADD_CHAR (string[i]); + break; + } + + case -2: /* history_comment_char */ + if (i == 0 || member (string[i - 1], history_word_delimiters)) + { + temp = (char *)xmalloc (l - i + 1); + strcpy (temp, string + i); + ADD_STRING (temp); + free (temp); + i = l; + } + else + ADD_CHAR (string[i]); + break; + + case -3: /* history_expansion_char */ + cc = string[i + 1]; + + /* If the history_expansion_char is followed by one of the + characters in history_no_expand_chars, then it is not a + candidate for expansion of any kind. */ + if (member (cc, history_no_expand_chars)) + { + ADD_CHAR (string[i]); + break; + } + +#if defined (NO_BANG_HASH_MODIFIERS) + /* There is something that is listed as a `word specifier' in csh + documentation which means `the expanded text to this point'. + That is not a word specifier, it is an event specifier. If we + don't want to allow modifiers with `!#', just stick the current + output line in again. */ + if (cc == '#') + { + if (result) + { + temp = (char *)xmalloc (1 + strlen (result)); + strcpy (temp, result); + ADD_STRING (temp); + free (temp); + } + i++; + break; + } +#endif + + r = history_expand_internal (string, i, &eindex, &temp, result); + if (r < 0) + { + *output = temp; + free (result); + if (string != hstring) + free (string); + return -1; + } + else + { + if (temp) + { + modified++; + if (*temp) + ADD_STRING (temp); + free (temp); + } + only_printing = r == 1; + i = eindex; + } + break; + } + } + + *output = result; + if (string != hstring) + free (string); + + if (only_printing) + { +#if 0 + add_history (result); +#endif + return (2); + } + + return (modified != 0); +} + +/* Return a consed string which is the word specified in SPEC, and found + in FROM. NULL is returned if there is no spec. The address of + ERROR_POINTER is returned if the word specified cannot be found. + CALLER_INDEX is the offset in SPEC to start looking; it is updated + to point to just after the last character parsed. */ +static char * +get_history_word_specifier (spec, from, caller_index) + char *spec, *from; + int *caller_index; +{ + register int i = *caller_index; + int first, last; + int expecting_word_spec = 0; + char *result; + + /* The range of words to return doesn't exist yet. */ + first = last = 0; + result = (char *)NULL; + + /* If we found a colon, then this *must* be a word specification. If + it isn't, then it is an error. */ + if (spec[i] == ':') + { + i++; + expecting_word_spec++; + } + + /* Handle special cases first. */ + + /* `%' is the word last searched for. */ + if (spec[i] == '%') + { + *caller_index = i + 1; + return (search_match ? savestring (search_match) : savestring ("")); + } + + /* `*' matches all of the arguments, but not the command. */ + if (spec[i] == '*') + { + *caller_index = i + 1; + result = history_arg_extract (1, '$', from); + return (result ? result : savestring ("")); + } + + /* `$' is last arg. */ + if (spec[i] == '$') + { + *caller_index = i + 1; + return (history_arg_extract ('$', '$', from)); + } + + /* Try to get FIRST and LAST figured out. */ + + if (spec[i] == '-') + first = 0; + else if (spec[i] == '^') + { + first = 1; + i++; + } + else if (_rl_digit_p (spec[i]) && expecting_word_spec) + { + for (first = 0; _rl_digit_p (spec[i]); i++) + first = (first * 10) + _rl_digit_value (spec[i]); + } + else + return ((char *)NULL); /* no valid `first' for word specifier */ + + if (spec[i] == '^' || spec[i] == '*') + { + last = (spec[i] == '^') ? 1 : '$'; /* x* abbreviates x-$ */ + i++; + } + else if (spec[i] != '-') + last = first; + else + { + i++; + + if (_rl_digit_p (spec[i])) + { + for (last = 0; _rl_digit_p (spec[i]); i++) + last = (last * 10) + _rl_digit_value (spec[i]); + } + else if (spec[i] == '$') + { + i++; + last = '$'; + } +#if 0 + else if (!spec[i] || spec[i] == ':') + /* check against `:' because there could be a modifier separator */ +#else + else + /* csh seems to allow anything to terminate the word spec here, + leaving it as an abbreviation. */ +#endif + last = -1; /* x- abbreviates x-$ omitting word `$' */ + } + + *caller_index = i; + + if (last >= first || last == '$' || last < 0) + result = history_arg_extract (first, last, from); + + return (result ? result : (char *)&error_pointer); +} + +/* Extract the args specified, starting at FIRST, and ending at LAST. + The args are taken from STRING. If either FIRST or LAST is < 0, + then make that arg count from the right (subtract from the number of + tokens, so that FIRST = -1 means the next to last token on the line). + If LAST is `$' the last arg from STRING is used. */ +char * +history_arg_extract (first, last, string) + int first, last; + const char *string; +{ + register int i, len; + char *result; + int size, offset; + char **list; + + /* XXX - think about making history_tokenize return a struct array, + each struct in array being a string and a length to avoid the + calls to strlen below. */ + if ((list = history_tokenize (string)) == NULL) + return ((char *)NULL); + + for (len = 0; list[len]; len++) + ; + + if (last < 0) + last = len + last - 1; + + if (first < 0) + first = len + first - 1; + + if (last == '$') + last = len - 1; + + if (first == '$') + first = len - 1; + + last++; + + if (first >= len || last > len || first < 0 || last < 0 || first > last) + result = ((char *)NULL); + else + { + for (size = 0, i = first; i < last; i++) + size += strlen (list[i]) + 1; + result = (char *)xmalloc (size + 1); + result[0] = '\0'; + + for (i = first, offset = 0; i < last; i++) + { + strcpy (result + offset, list[i]); + offset += strlen (list[i]); + if (i + 1 < last) + { + result[offset++] = ' '; + result[offset] = 0; + } + } + } + + for (i = 0; i < len; i++) + free (list[i]); + free (list); + + return (result); +} + +static int +history_tokenize_word (string, ind) + const char *string; + int ind; +{ + register int i; + int delimiter; + + i = ind; + delimiter = 0; + + if (member (string[i], "()\n")) + { + i++; + return i; + } + + if (member (string[i], "<>;&|$")) + { + int peek = string[i + 1]; + + if (peek == string[i] && peek != '$') + { + if (peek == '<' && string[i + 2] == '-') + i++; + else if (peek == '<' && string[i + 2] == '<') + i++; + i += 2; + return i; + } + else + { + if ((peek == '&' && (string[i] == '>' || string[i] == '<')) || + (peek == '>' && string[i] == '&') || + (peek == '(' && (string[i] == '>' || string[i] == '<')) || /* ) */ + (peek == '(' && string[i] == '$')) /* ) */ + { + i += 2; + return i; + } + } + + if (string[i] != '$') + { + i++; + return i; + } + } + + /* Get word from string + i; */ + + if (member (string[i], HISTORY_QUOTE_CHARACTERS)) + delimiter = string[i++]; + + for (; string[i]; i++) + { + if (string[i] == '\\' && string[i + 1] == '\n') + { + i++; + continue; + } + + if (string[i] == '\\' && delimiter != '\'' && + (delimiter != '"' || member (string[i], slashify_in_quotes))) + { + i++; + continue; + } + + if (delimiter && string[i] == delimiter) + { + delimiter = 0; + continue; + } + + if (!delimiter && (member (string[i], history_word_delimiters))) + break; + + if (!delimiter && member (string[i], HISTORY_QUOTE_CHARACTERS)) + delimiter = string[i]; + } + + return i; +} + +static char * +history_substring (string, start, end) + const char *string; + int start, end; +{ + register int len; + register char *result; + + len = end - start; + result = (char *)xmalloc (len + 1); + strncpy (result, string + start, len); + result[len] = '\0'; + return result; +} + +/* Parse STRING into tokens and return an array of strings. If WIND is + not -1 and INDP is not null, we also want the word surrounding index + WIND. The position in the returned array of strings is returned in + *INDP. */ +static char ** +history_tokenize_internal (string, wind, indp) + const char *string; + int wind, *indp; +{ + char **result; + register int i, start, result_index, size; + + /* If we're searching for a string that's not part of a word (e.g., " "), + make sure we set *INDP to a reasonable value. */ + if (indp && wind != -1) + *indp = -1; + + /* Get a token, and stuff it into RESULT. The tokens are split + exactly where the shell would split them. */ + for (i = result_index = size = 0, result = (char **)NULL; string[i]; ) + { + /* Skip leading whitespace. */ + for (; string[i] && whitespace (string[i]); i++) + ; + if (string[i] == 0 || string[i] == history_comment_char) + return (result); + + start = i; + + i = history_tokenize_word (string, start); + + /* If we have a non-whitespace delimiter character (which would not be + skipped by the loop above), use it and any adjacent delimiters to + make a separate field. Any adjacent white space will be skipped the + next time through the loop. */ + if (i == start && history_word_delimiters) + { + i++; + while (string[i] && member (string[i], history_word_delimiters)) + i++; + } + + /* If we are looking for the word in which the character at a + particular index falls, remember it. */ + if (indp && wind != -1 && wind >= start && wind < i) + *indp = result_index; + + if (result_index + 2 >= size) + result = (char **)xrealloc (result, ((size += 10) * sizeof (char *))); + + result[result_index++] = history_substring (string, start, i); + result[result_index] = (char *)NULL; + } + + return (result); +} + +/* Return an array of tokens, much as the shell might. The tokens are + parsed out of STRING. */ +char ** +history_tokenize (string) + const char *string; +{ + return (history_tokenize_internal (string, -1, (int *)NULL)); +} + +/* Find and return the word which contains the character at index IND + in the history line LINE. Used to save the word matched by the + last history !?string? search. */ +static char * +history_find_word (line, ind) + char *line; + int ind; +{ + char **words, *s; + int i, wind; + + words = history_tokenize_internal (line, ind, &wind); + if (wind == -1 || words == 0) + return ((char *)NULL); + s = words[wind]; + for (i = 0; i < wind; i++) + free (words[i]); + for (i = wind + 1; words[i]; i++) + free (words[i]); + free (words); + return s; +} diff --git a/extra/readline/histfile.c b/extra/readline/histfile.c new file mode 100644 index 00000000000..1d433b98be4 --- /dev/null +++ b/extra/readline/histfile.c @@ -0,0 +1,550 @@ +/* histfile.c - functions to manipulate the history file. */ + +/* Copyright (C) 1989-2003 Free Software Foundation, Inc. + + This file contains the GNU History Library (the Library), a set of + routines for managing the text of previously typed lines. + + The Library is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + The 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 + General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* The goal is to make the implementation transparent, so that you + don't have to know what data types are used, just what functions + you can call. I think I have done that. */ + +#define READLINE_LIBRARY + +#if defined (__TANDEM) +# include <floss.h> +#endif + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <stdio.h> + +#include <sys/types.h> +#if ! defined (_MINIX) && defined (HAVE_SYS_FILE_H) +# include <sys/file.h> +#endif +#include "posixstat.h" +#include <fcntl.h> + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif + +#if defined (__EMX__) || defined (__CYGWIN__) +# undef HAVE_MMAP +#endif + +#ifdef HISTORY_USE_MMAP +# include <sys/mman.h> + +# ifdef MAP_FILE +# define MAP_RFLAGS (MAP_FILE|MAP_PRIVATE) +# define MAP_WFLAGS (MAP_FILE|MAP_SHARED) +# else +# define MAP_RFLAGS MAP_PRIVATE +# define MAP_WFLAGS MAP_SHARED +# endif + +# ifndef MAP_FAILED +# define MAP_FAILED ((void *)-1) +# endif + +#endif /* HISTORY_USE_MMAP */ + +/* If we're compiling for __EMX__ (OS/2) or __CYGWIN__ (cygwin32 environment + on win 95/98/nt), we want to open files with O_BINARY mode so that there + is no \n -> \r\n conversion performed. On other systems, we don't want to + mess around with O_BINARY at all, so we ensure that it's defined to 0. */ +#if defined (__EMX__) || defined (__CYGWIN__) +# ifndef O_BINARY +# define O_BINARY 0 +# endif +#else /* !__EMX__ && !__CYGWIN__ */ +# undef O_BINARY +# define O_BINARY 0 +#endif /* !__EMX__ && !__CYGWIN__ */ + +#include <errno.h> +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +#include "history.h" +#include "histlib.h" + +#include "rlshell.h" +#include "xmalloc.h" + +/* If non-zero, we write timestamps to the history file in history_do_write() */ +int history_write_timestamps = 0; + +/* Does S look like the beginning of a history timestamp entry? Placeholder + for more extensive tests. */ +#define HIST_TIMESTAMP_START(s) (*(s) == history_comment_char) + +/* Return the string that should be used in the place of this + filename. This only matters when you don't specify the + filename to read_history (), or write_history (). */ +static char * +history_filename (filename) + const char *filename; +{ + char *return_val; + const char *home; + int home_len; + + return_val = filename ? savestring (filename) : (char *)NULL; + + if (return_val) + return (return_val); + + home = sh_get_env_value ("HOME"); + + if (home == 0) + { + home = "."; + home_len = 1; + } + else + home_len = strlen (home); + + return_val = (char *)xmalloc (2 + home_len + 8); /* strlen(".history") == 8 */ + strcpy (return_val, home); + return_val[home_len] = '/'; +#if defined (__MSDOS__) + strcpy (return_val + home_len + 1, "_history"); +#else + strcpy (return_val + home_len + 1, ".history"); +#endif + + return (return_val); +} + +/* Add the contents of FILENAME to the history list, a line at a time. + If FILENAME is NULL, then read from ~/.history. Returns 0 if + successful, or errno if not. */ +int +read_history (filename) + const char *filename; +{ + return (read_history_range (filename, 0, -1)); +} + +/* Read a range of lines from FILENAME, adding them to the history list. + Start reading at the FROM'th line and end at the TO'th. If FROM + is zero, start at the beginning. If TO is less than FROM, read + until the end of the file. If FILENAME is NULL, then read from + ~/.history. Returns 0 if successful, or errno if not. */ +int +read_history_range (filename, from, to) + const char *filename; + int from, to; +{ + register char *line_start, *line_end, *p; + char *input, *buffer, *bufend, *last_ts; + int file, current_line, chars_read; + struct stat finfo; + size_t file_size; +#if defined (EFBIG) + int overflow_errno = EFBIG; +#elif defined (EOVERFLOW) + int overflow_errno = EOVERFLOW; +#else + int overflow_errno = EIO; +#endif + + buffer = last_ts = (char *)NULL; + input = history_filename (filename); + file = open (input, O_RDONLY|O_BINARY, 0666); + + if ((file < 0) || (fstat (file, &finfo) == -1)) + goto error_and_exit; + + file_size = (size_t)finfo.st_size; + + /* check for overflow on very large files */ + if ((sizeof(off_t) > sizeof(size_t) && finfo.st_size > (off_t)(size_t)~0) || + file_size + 1 < file_size) + { + errno = overflow_errno; + goto error_and_exit; + } + +#ifdef HISTORY_USE_MMAP + /* We map read/write and private so we can change newlines to NULs without + affecting the underlying object. */ + buffer = (char *)mmap (0, file_size, PROT_READ|PROT_WRITE, MAP_RFLAGS, file, 0); + if ((void *)buffer == MAP_FAILED) + { + errno = overflow_errno; + goto error_and_exit; + } + chars_read = file_size; +#else + buffer = (char *)malloc (file_size + 1); + if (buffer == 0) + { + errno = overflow_errno; + goto error_and_exit; + } + + chars_read = read (file, buffer, file_size); +#endif + if (chars_read < 0) + { + error_and_exit: + if (errno != 0) + chars_read = errno; + else + chars_read = EIO; + if (file >= 0) + close (file); + + FREE (input); +#ifndef HISTORY_USE_MMAP + FREE (buffer); +#endif + + return (chars_read); + } + + close (file); + + /* Set TO to larger than end of file if negative. */ + if (to < 0) + to = chars_read; + + /* Start at beginning of file, work to end. */ + bufend = buffer + chars_read; + current_line = 0; + + /* Skip lines until we are at FROM. */ + for (line_start = line_end = buffer; line_end < bufend && current_line < from; line_end++) + if (*line_end == '\n') + { + p = line_end + 1; + /* If we see something we think is a timestamp, continue with this + line. We should check more extensively here... */ + if (HIST_TIMESTAMP_START(p) == 0) + current_line++; + line_start = p; + } + + /* If there are lines left to gobble, then gobble them now. */ + for (line_end = line_start; line_end < bufend; line_end++) + if (*line_end == '\n') + { + /* Change to allow Windows-like \r\n end of line delimiter. */ + if (line_end > line_start && line_end[-1] == '\r') + line_end[-1] = '\0'; + else + *line_end = '\0'; + + if (*line_start) + { + if (HIST_TIMESTAMP_START(line_start) == 0) + { + add_history (line_start); + if (last_ts) + { + add_history_time (last_ts); + last_ts = NULL; + } + } + else + { + last_ts = line_start; + current_line--; + } + } + + current_line++; + + if (current_line >= to) + break; + + line_start = line_end + 1; + } + + FREE (input); +#ifndef HISTORY_USE_MMAP + FREE (buffer); +#else + munmap (buffer, file_size); +#endif + + return (0); +} + +/* Truncate the history file FNAME, leaving only LINES trailing lines. + If FNAME is NULL, then use ~/.history. Returns 0 on success, errno + on failure. */ +int +history_truncate_file (fname, lines) + const char *fname; + int lines; +{ + char *buffer, *filename, *bp, *bp1; /* bp1 == bp+1 */ + int file, chars_read, rv; + struct stat finfo; + size_t file_size; + size_t bytes_written; + + buffer = (char *)NULL; + filename = history_filename (fname); + file = open (filename, O_RDONLY|O_BINARY, 0666); + rv = 0; + + /* Don't try to truncate non-regular files. */ + if (file == -1 || fstat (file, &finfo) == -1) + { + rv = errno; + if (file != -1) + close (file); + goto truncate_exit; + } + + if (S_ISREG (finfo.st_mode) == 0) + { + close (file); +#ifdef EFTYPE + rv = EFTYPE; +#else + rv = EINVAL; +#endif + goto truncate_exit; + } + + file_size = (size_t)finfo.st_size; + + /* check for overflow on very large files */ + if ((sizeof(off_t) > sizeof(size_t) && finfo.st_size > (off_t)(size_t)~0) || + file_size + 1 < file_size) + { + close (file); +#if defined (EFBIG) + rv = errno = EFBIG; +#elif defined (EOVERFLOW) + rv = errno = EOVERFLOW; +#else + rv = errno = EINVAL; +#endif + goto truncate_exit; + } + + buffer = (char *)malloc (file_size + 1); + if (buffer == 0) + { + close (file); + goto truncate_exit; + } + + chars_read = read (file, buffer, file_size); + close (file); + + if (chars_read <= 0) + { + rv = (chars_read < 0) ? errno : 0; + goto truncate_exit; + } + + /* Count backwards from the end of buffer until we have passed + LINES lines. bp1 is set funny initially. But since bp[1] can't + be a comment character (since it's off the end) and *bp can't be + both a newline and the history comment character, it should be OK. */ + for (bp1 = bp = buffer + chars_read - 1; lines && bp > buffer; bp--) + { + if (*bp == '\n' && HIST_TIMESTAMP_START(bp1) == 0) + lines--; + bp1 = bp; + } + + /* If this is the first line, then the file contains exactly the + number of lines we want to truncate to, so we don't need to do + anything. It's the first line if we don't find a newline between + the current value of i and 0. Otherwise, write from the start of + this line until the end of the buffer. */ + for ( ; bp > buffer; bp--) + { + if (*bp == '\n' && HIST_TIMESTAMP_START(bp1) == 0) + { + bp++; + break; + } + bp1 = bp; + } + + /* Write only if there are more lines in the file than we want to + truncate to. */ + if (bp > buffer && ((file = open (filename, O_WRONLY|O_TRUNC|O_BINARY, 0600)) != -1)) + { + bytes_written= write (file, bp, chars_read - (bp - buffer)); + (void) bytes_written; + +#if defined (__BEOS__) + /* BeOS ignores O_TRUNC. */ + ftruncate (file, chars_read - (bp - buffer)); +#endif + + close (file); + } + + truncate_exit: + + FREE (buffer); + + free (filename); + return rv; +} + +/* Workhorse function for writing history. Writes NELEMENT entries + from the history list to FILENAME. OVERWRITE is non-zero if you + wish to replace FILENAME with the entries. */ +static int +history_do_write (filename, nelements, overwrite) + const char *filename; + int nelements, overwrite; +{ + register int i; + char *output; + int file, mode, rv; +#ifdef HISTORY_USE_MMAP + size_t cursize; + + mode = overwrite ? O_RDWR|O_CREAT|O_TRUNC|O_BINARY : O_RDWR|O_APPEND|O_BINARY; +#else + mode = overwrite ? O_WRONLY|O_CREAT|O_TRUNC|O_BINARY : O_WRONLY|O_APPEND|O_BINARY; +#endif + output = history_filename (filename); + rv = 0; + + if ((file = open (output, mode, 0600)) == -1) + { + FREE (output); + return (errno); + } + +#ifdef HISTORY_USE_MMAP + cursize = overwrite ? 0 : lseek (file, 0, SEEK_END); +#endif + + if (nelements > history_length) + nelements = history_length; + + /* Build a buffer of all the lines to write, and write them in one syscall. + Suggested by Peter Ho (peter@robosts.oxford.ac.uk). */ + { + HIST_ENTRY **the_history; /* local */ + register int j; + int buffer_size; + char *buffer; + + the_history = history_list (); + /* Calculate the total number of bytes to write. */ + for (buffer_size = 0, i = history_length - nelements; i < history_length; i++) +#if 0 + buffer_size += 2 + HISTENT_BYTES (the_history[i]); +#else + { + if (history_write_timestamps && the_history[i]->timestamp && the_history[i]->timestamp[0]) + buffer_size += strlen (the_history[i]->timestamp) + 1; + buffer_size += strlen (the_history[i]->line) + 1; + } +#endif + + /* Allocate the buffer, and fill it. */ +#ifdef HISTORY_USE_MMAP + if (ftruncate (file, buffer_size+cursize) == -1) + goto mmap_error; + buffer = (char *)mmap (0, buffer_size, PROT_READ|PROT_WRITE, MAP_WFLAGS, file, cursize); + if ((void *)buffer == MAP_FAILED) + { +mmap_error: + rv = errno; + FREE (output); + close (file); + return rv; + } +#else + buffer = (char *)malloc (buffer_size); + if (buffer == 0) + { + rv = errno; + FREE (output); + close (file); + return rv; + } +#endif + + for (j = 0, i = history_length - nelements; i < history_length; i++) + { + if (history_write_timestamps && the_history[i]->timestamp && the_history[i]->timestamp[0]) + { + strcpy (buffer + j, the_history[i]->timestamp); + j += strlen (the_history[i]->timestamp); + buffer[j++] = '\n'; + } + strcpy (buffer + j, the_history[i]->line); + j += strlen (the_history[i]->line); + buffer[j++] = '\n'; + } + +#ifdef HISTORY_USE_MMAP + if (msync (buffer, buffer_size, 0) != 0 || munmap (buffer, buffer_size) != 0) + rv = errno; +#else + if (write (file, buffer, buffer_size) < 0) + rv = errno; + free (buffer); +#endif + } + + close (file); + + FREE (output); + + return (rv); +} + +/* Append NELEMENT entries to FILENAME. The entries appended are from + the end of the list minus NELEMENTs up to the end of the list. */ +int +append_history (nelements, filename) + int nelements; + const char *filename; +{ + return (history_do_write (filename, nelements, HISTORY_APPEND)); +} + +/* Overwrite FILENAME with the current history. If FILENAME is NULL, + then write the history list to ~/.history. Values returned + are as in read_history ().*/ +int +write_history (filename) + const char *filename; +{ + return (history_do_write (filename, history_length, HISTORY_OVERWRITE)); +} diff --git a/extra/readline/histlib.h b/extra/readline/histlib.h new file mode 100644 index 00000000000..4418f537389 --- /dev/null +++ b/extra/readline/histlib.h @@ -0,0 +1,82 @@ +/* histlib.h -- internal definitions for the history library. */ +/* Copyright (C) 1989, 1992 Free Software Foundation, Inc. + + This file contains the GNU History Library (the Library), a set of + routines for managing the text of previously typed lines. + + The Library is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + The 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 + General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#if !defined (_HISTLIB_H_) +#define _HISTLIB_H_ + +#if defined (HAVE_STRING_H) +# include <string.h> +#else +# include <strings.h> +#endif /* !HAVE_STRING_H */ + +#if !defined (STREQ) +#define STREQ(a, b) (((a)[0] == (b)[0]) && (strcmp ((a), (b)) == 0)) +#define STREQN(a, b, n) (((n) == 0) ? (1) \ + : ((a)[0] == (b)[0]) && (strncmp ((a), (b), (n)) == 0)) +#endif + +#ifndef savestring +#define savestring(x) strcpy (xmalloc (1 + strlen (x)), (x)) +#endif + +#ifndef whitespace +#define whitespace(c) (((c) == ' ') || ((c) == '\t')) +#endif + +#ifndef _rl_digit_p +#define _rl_digit_p(c) ((c) >= '0' && (c) <= '9') +#endif + +#ifndef _rl_digit_value +#define _rl_digit_value(c) ((c) - '0') +#endif + +#ifndef member +# ifndef strchr +extern char *strchr (); +# endif +#define member(c, s) ((c) ? ((char *)strchr ((s), (c)) != (char *)NULL) : 0) +#endif + +#ifndef FREE +# define FREE(x) if (x) free (x) +#endif + +/* Possible history errors passed to hist_error. */ +#define EVENT_NOT_FOUND 0 +#define BAD_WORD_SPEC 1 +#define SUBST_FAILED 2 +#define BAD_MODIFIER 3 +#define NO_PREV_SUBST 4 + +/* Possible definitions for history starting point specification. */ +#define ANCHORED_SEARCH 1 +#define NON_ANCHORED_SEARCH 0 + +/* Possible definitions for what style of writing the history file we want. */ +#define HISTORY_APPEND 0 +#define HISTORY_OVERWRITE 1 + +/* Some variable definitions shared across history source files. */ +extern int history_offset; + +#endif /* !_HISTLIB_H_ */ diff --git a/extra/readline/history.c b/extra/readline/history.c new file mode 100644 index 00000000000..f36bbe4bc53 --- /dev/null +++ b/extra/readline/history.c @@ -0,0 +1,518 @@ +/* history.c -- standalone history library */ + +/* Copyright (C) 1989-2005 Free Software Foundation, Inc. + + This file contains the GNU History Library (the Library), a set of + routines for managing the text of previously typed lines. + + The Library is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + The 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 + General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* The goal is to make the implementation transparent, so that you + don't have to know what data types are used, just what functions + you can call. I think I have done that. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <stdio.h> + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include <sys/types.h> +# endif +# include <unistd.h> +#endif + +#include "history.h" +#include "histlib.h" + +#include "xmalloc.h" + +/* The number of slots to increase the_history by. */ +#define DEFAULT_HISTORY_GROW_SIZE 50 + +static char *hist_inittime PARAMS((void)); + +/* **************************************************************** */ +/* */ +/* History Functions */ +/* */ +/* **************************************************************** */ + +/* An array of HIST_ENTRY. This is where we store the history. */ +static HIST_ENTRY **the_history = (HIST_ENTRY **)NULL; + +/* Non-zero means that we have enforced a limit on the amount of + history that we save. */ +static int history_stifled; + +/* The current number of slots allocated to the input_history. */ +static int history_size; + +/* If HISTORY_STIFLED is non-zero, then this is the maximum number of + entries to remember. */ +int history_max_entries; +int max_input_history; /* backwards compatibility */ + +/* The current location of the interactive history pointer. Just makes + life easier for outside callers. */ +int history_offset; + +/* The number of strings currently stored in the history list. */ +int history_length; + +/* The logical `base' of the history array. It defaults to 1. */ +int history_base = 1; + +/* Return the current HISTORY_STATE of the history. */ +HISTORY_STATE * +history_get_history_state () +{ + HISTORY_STATE *state; + + state = (HISTORY_STATE *)xmalloc (sizeof (HISTORY_STATE)); + state->entries = the_history; + state->offset = history_offset; + state->length = history_length; + state->size = history_size; + state->flags = 0; + if (history_stifled) + state->flags |= HS_STIFLED; + + return (state); +} + +/* Set the state of the current history array to STATE. */ +void +history_set_history_state (state) + HISTORY_STATE *state; +{ + the_history = state->entries; + history_offset = state->offset; + history_length = state->length; + history_size = state->size; + if (state->flags & HS_STIFLED) + history_stifled = 1; +} + +/* Begin a session in which the history functions might be used. This + initializes interactive variables. */ +void +using_history () +{ + history_offset = history_length; +} + +/* Return the number of bytes that the primary history entries are using. + This just adds up the lengths of the_history->lines and the associated + timestamps. */ +int +history_total_bytes () +{ + register int i, result; + + for (i = result = 0; the_history && the_history[i]; i++) + result += HISTENT_BYTES (the_history[i]); + + return (result); +} + +/* Returns the magic number which says what history element we are + looking at now. In this implementation, it returns history_offset. */ +int +where_history () +{ + return (history_offset); +} + +/* Make the current history item be the one at POS, an absolute index. + Returns zero if POS is out of range, else non-zero. */ +int +history_set_pos (pos) + int pos; +{ + if (pos > history_length || pos < 0 || !the_history) + return (0); + history_offset = pos; + return (1); +} + +/* Return the current history array. The caller has to be carefull, since this + is the actual array of data, and could be bashed or made corrupt easily. + The array is terminated with a NULL pointer. */ +HIST_ENTRY ** +history_list () +{ + return (the_history); +} + +/* Return the history entry at the current position, as determined by + history_offset. If there is no entry there, return a NULL pointer. */ +HIST_ENTRY * +current_history () +{ + return ((history_offset == history_length) || the_history == 0) + ? (HIST_ENTRY *)NULL + : the_history[history_offset]; +} + +/* Back up history_offset to the previous history entry, and return + a pointer to that entry. If there is no previous entry then return + a NULL pointer. */ +HIST_ENTRY * +previous_history () +{ + return history_offset ? the_history[--history_offset] : (HIST_ENTRY *)NULL; +} + +/* Move history_offset forward to the next history entry, and return + a pointer to that entry. If there is no next entry then return a + NULL pointer. */ +HIST_ENTRY * +next_history () +{ + return (history_offset == history_length) ? (HIST_ENTRY *)NULL : the_history[++history_offset]; +} + +/* Return the history entry which is logically at OFFSET in the history array. + OFFSET is relative to history_base. */ +HIST_ENTRY * +history_get (offset) + int offset; +{ + int local_index; + + local_index = offset - history_base; + return (local_index >= history_length || local_index < 0 || the_history == 0) + ? (HIST_ENTRY *)NULL + : the_history[local_index]; +} + +HIST_ENTRY * +alloc_history_entry (string, ts) + const char *string; + char *ts; +{ + HIST_ENTRY *temp; + + temp = (HIST_ENTRY *)xmalloc (sizeof (HIST_ENTRY)); + + temp->line = string ? savestring ((char*) string) : (char*) string; + temp->data = (char *)NULL; + temp->timestamp = ts; + + return temp; +} + +time_t +history_get_time (hist) + HIST_ENTRY *hist; +{ + char *ts; + time_t t; + + if (hist == 0 || hist->timestamp == 0) + return 0; + ts = hist->timestamp; + if (ts[0] != history_comment_char) + return 0; + t = (time_t) atol (ts + 1); /* XXX - should use strtol() here */ + return t; +} + +static char * +hist_inittime () +{ + time_t t; + char ts[64], *ret; + + t = (time_t) time ((time_t *)0); +#if defined (HAVE_VSNPRINTF) /* assume snprintf if vsnprintf exists */ + snprintf (ts, sizeof (ts) - 1, "X%lu", (unsigned long) t); +#else + sprintf (ts, "X%lu", (unsigned long) t); +#endif + ret = savestring (ts); + ret[0] = history_comment_char; + + return ret; +} + +/* Place STRING at the end of the history list. The data field + is set to NULL. */ +void +add_history (string) + const char *string; +{ + HIST_ENTRY *temp; + + if (history_stifled && (history_length == history_max_entries)) + { + register int i; + + /* If the history is stifled, and history_length is zero, + and it equals history_max_entries, we don't save items. */ + if (history_length == 0) + return; + + /* If there is something in the slot, then remove it. */ + if (the_history[0]) + (void) free_history_entry (the_history[0]); + + /* Copy the rest of the entries, moving down one slot. */ + for (i = 0; i < history_length; i++) + the_history[i] = the_history[i + 1]; + + history_base++; + } + else + { + if (history_size == 0) + { + history_size = DEFAULT_HISTORY_GROW_SIZE; + the_history = (HIST_ENTRY **)xmalloc (history_size * sizeof (HIST_ENTRY *)); + history_length = 1; + } + else + { + if (history_length == (history_size - 1)) + { + history_size += DEFAULT_HISTORY_GROW_SIZE; + the_history = (HIST_ENTRY **) + xrealloc (the_history, history_size * sizeof (HIST_ENTRY *)); + } + history_length++; + } + } + + temp = alloc_history_entry ((char*) string, hist_inittime ()); + + the_history[history_length] = (HIST_ENTRY *)NULL; + the_history[history_length - 1] = temp; +} + +/* Change the time stamp of the most recent history entry to STRING. */ +void +add_history_time (string) + const char *string; +{ + HIST_ENTRY *hs; + + hs = the_history[history_length - 1]; + FREE (hs->timestamp); + hs->timestamp = savestring (string); +} + +/* Free HIST and return the data so the calling application can free it + if necessary and desired. */ +histdata_t +free_history_entry (hist) + HIST_ENTRY *hist; +{ + histdata_t x; + + if (hist == 0) + return ((histdata_t) 0); + FREE (hist->line); + FREE (hist->timestamp); + x = hist->data; + free (hist); + return (x); +} + +HIST_ENTRY * +copy_history_entry (hist) + HIST_ENTRY *hist; +{ + HIST_ENTRY *ret; + char *ts; + + if (hist == 0) + return hist; + + ret = alloc_history_entry (hist->line, (char *)NULL); + + ts = hist->timestamp ? savestring (hist->timestamp) : hist->timestamp; + ret->timestamp = ts; + + ret->data = hist->data; + + return ret; +} + +/* Make the history entry at WHICH have LINE and DATA. This returns + the old entry so you can dispose of the data. In the case of an + invalid WHICH, a NULL pointer is returned. */ +HIST_ENTRY * +replace_history_entry (which, line, data) + int which; + const char *line; + histdata_t data; +{ + HIST_ENTRY *temp, *old_value; + + if (which < 0 || which >= history_length) + return ((HIST_ENTRY *)NULL); + + temp = (HIST_ENTRY *)xmalloc (sizeof (HIST_ENTRY)); + old_value = the_history[which]; + + temp->line = savestring (line); + temp->data = data; + temp->timestamp = savestring (old_value->timestamp); + the_history[which] = temp; + + return (old_value); +} + +/* Replace the DATA in the specified history entries, replacing OLD with + NEW. WHICH says which one(s) to replace: WHICH == -1 means to replace + all of the history entries where entry->data == OLD; WHICH == -2 means + to replace the `newest' history entry where entry->data == OLD; and + WHICH >= 0 means to replace that particular history entry's data, as + long as it matches OLD. */ +void +replace_history_data (which,old, new) + int which; + histdata_t *old, *new; +{ + HIST_ENTRY *entry; + register int i, last; + + if (which < -2 || which >= history_length || history_length == 0 || the_history == 0) + return; + + if (which >= 0) + { + entry = the_history[which]; + if (entry && entry->data == old) + entry->data = new; + return; + } + + last = -1; + for (i = 0; i < history_length; i++) + { + entry = the_history[i]; + if (entry == 0) + continue; + if (entry->data == old) + { + last = i; + if (which == -1) + entry->data = new; + } + } + if (which == -2 && last >= 0) + { + entry = the_history[last]; + entry->data = new; /* XXX - we don't check entry->old */ + } +} + +/* Remove history element WHICH from the history. The removed + element is returned to you so you can free the line, data, + and containing structure. */ +HIST_ENTRY * +remove_history (which) + int which; +{ + HIST_ENTRY *return_value; + register int i; + + if (which < 0 || which >= history_length || history_length == 0 || the_history == 0) + return ((HIST_ENTRY *)NULL); + + return_value = the_history[which]; + + for (i = which; i < history_length; i++) + the_history[i] = the_history[i + 1]; + + history_length--; + + return (return_value); +} + +/* Stifle the history list, remembering only MAX number of lines. */ +void +stifle_history (max) + int max; +{ + register int i, j; + + if (max < 0) + max = 0; + + if (history_length > max) + { + /* This loses because we cannot free the data. */ + for (i = 0, j = history_length - max; i < j; i++) + free_history_entry (the_history[i]); + + history_base = i; + for (j = 0, i = history_length - max; j < max; i++, j++) + the_history[j] = the_history[i]; + the_history[j] = (HIST_ENTRY *)NULL; + history_length = j; + } + + history_stifled = 1; + max_input_history = history_max_entries = max; +} + +/* Stop stifling the history. This returns the previous maximum + number of history entries. The value is positive if the history + was stifled, negative if it wasn't. */ +int +unstifle_history () +{ + if (history_stifled) + { + history_stifled = 0; + return (history_max_entries); + } + else + return (-history_max_entries); +} + +int +history_is_stifled () +{ + return (history_stifled); +} + +void +clear_history () +{ + register int i; + + /* This loses because we cannot free the data. */ + for (i = 0; i < history_length; i++) + { + free_history_entry (the_history[i]); + the_history[i] = (HIST_ENTRY *)NULL; + } + + history_offset = history_length = 0; +} diff --git a/extra/readline/history.h b/extra/readline/history.h new file mode 100644 index 00000000000..c196b0361e3 --- /dev/null +++ b/extra/readline/history.h @@ -0,0 +1,266 @@ +/* history.h -- the names of functions that you can call in history. */ +/* Copyright (C) 1989-2003 Free Software Foundation, Inc. + + This file contains the GNU History Library (the Library), a set of + routines for managing the text of previously typed lines. + + The Library is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + The 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 + General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#ifndef _HISTORY_H_ +#define _HISTORY_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <time.h> /* XXX - for history timestamp code */ + +#if defined READLINE_LIBRARY +# include "rlstdc.h" +# include "rltypedefs.h" +#else +# include <rlstdc.h> +# include <rltypedefs.h> +#endif + +#ifdef __STDC__ +typedef void *histdata_t; +#else +typedef char *histdata_t; +#endif + +/* The structure used to store a history entry. */ +typedef struct _hist_entry { + char *line; + char *timestamp; /* char * rather than time_t for read/write */ + histdata_t data; +} HIST_ENTRY; + +/* Size of the history-library-managed space in history entry HS. */ +#define HISTENT_BYTES(hs) (strlen ((hs)->line) + strlen ((hs)->timestamp)) + +/* A structure used to pass the current state of the history stuff around. */ +typedef struct _hist_state { + HIST_ENTRY **entries; /* Pointer to the entries themselves. */ + int offset; /* The location pointer within this array. */ + int length; /* Number of elements within this array. */ + int size; /* Number of slots allocated to this array. */ + int flags; +} HISTORY_STATE; + +/* Flag values for the `flags' member of HISTORY_STATE. */ +#define HS_STIFLED 0x01 + +/* Initialization and state management. */ + +/* Begin a session in which the history functions might be used. This + just initializes the interactive variables. */ +extern void using_history PARAMS((void)); + +/* Return the current HISTORY_STATE of the history. */ +extern HISTORY_STATE *history_get_history_state PARAMS((void)); + +/* Set the state of the current history array to STATE. */ +extern void history_set_history_state PARAMS((HISTORY_STATE *)); + +/* Manage the history list. */ + +/* Place STRING at the end of the history list. + The associated data field (if any) is set to NULL. */ +extern void add_history PARAMS((const char *)); + +/* Change the timestamp associated with the most recent history entry to + STRING. */ +extern void add_history_time PARAMS((const char *)); + +/* A reasonably useless function, only here for completeness. WHICH + is the magic number that tells us which element to delete. The + elements are numbered from 0. */ +extern HIST_ENTRY *remove_history PARAMS((int)); + +/* Free the history entry H and return any application-specific data + associated with it. */ +extern histdata_t free_history_entry PARAMS((HIST_ENTRY *)); + +/* Make the history entry at WHICH have LINE and DATA. This returns + the old entry so you can dispose of the data. In the case of an + invalid WHICH, a NULL pointer is returned. */ +extern HIST_ENTRY *replace_history_entry PARAMS((int, const char *, histdata_t)); + +/* Clear the history list and start over. */ +extern void clear_history PARAMS((void)); + +/* Stifle the history list, remembering only MAX number of entries. */ +extern void stifle_history PARAMS((int)); + +/* Stop stifling the history. This returns the previous amount the + history was stifled by. The value is positive if the history was + stifled, negative if it wasn't. */ +extern int unstifle_history PARAMS((void)); + +/* Return 1 if the history is stifled, 0 if it is not. */ +extern int history_is_stifled PARAMS((void)); + +/* Information about the history list. */ + +/* Return a NULL terminated array of HIST_ENTRY which is the current input + history. Element 0 of this list is the beginning of time. If there + is no history, return NULL. */ +extern HIST_ENTRY **history_list PARAMS((void)); + +/* Returns the number which says what history element we are now + looking at. */ +extern int where_history PARAMS((void)); + +/* Return the history entry at the current position, as determined by + history_offset. If there is no entry there, return a NULL pointer. */ +extern HIST_ENTRY *current_history PARAMS((void)); + +/* Return the history entry which is logically at OFFSET in the history + array. OFFSET is relative to history_base. */ +extern HIST_ENTRY *history_get PARAMS((int)); + +/* Return the timestamp associated with the HIST_ENTRY * passed as an + argument */ +extern time_t history_get_time PARAMS((HIST_ENTRY *)); + +/* Return the number of bytes that the primary history entries are using. + This just adds up the lengths of the_history->lines. */ +extern int history_total_bytes PARAMS((void)); + +/* Moving around the history list. */ + +/* Set the position in the history list to POS. */ +extern int history_set_pos PARAMS((int)); + +/* Back up history_offset to the previous history entry, and return + a pointer to that entry. If there is no previous entry, return + a NULL pointer. */ +extern HIST_ENTRY *previous_history PARAMS((void)); + +/* Move history_offset forward to the next item in the input_history, + and return the a pointer to that entry. If there is no next entry, + return a NULL pointer. */ +extern HIST_ENTRY *next_history PARAMS((void)); + +/* Searching the history list. */ + +/* Search the history for STRING, starting at history_offset. + If DIRECTION < 0, then the search is through previous entries, + else through subsequent. If the string is found, then + current_history () is the history entry, and the value of this function + is the offset in the line of that history entry that the string was + found in. Otherwise, nothing is changed, and a -1 is returned. */ +extern int history_search PARAMS((const char *, int)); + +/* Search the history for STRING, starting at history_offset. + The search is anchored: matching lines must begin with string. + DIRECTION is as in history_search(). */ +extern int history_search_prefix PARAMS((const char *, int)); + +/* Search for STRING in the history list, starting at POS, an + absolute index into the list. DIR, if negative, says to search + backwards from POS, else forwards. + Returns the absolute index of the history element where STRING + was found, or -1 otherwise. */ +extern int history_search_pos PARAMS((const char *, int, int)); + +/* Managing the history file. */ + +/* Add the contents of FILENAME to the history list, a line at a time. + If FILENAME is NULL, then read from ~/.history. Returns 0 if + successful, or errno if not. */ +extern int read_history PARAMS((const char *)); + +/* Read a range of lines from FILENAME, adding them to the history list. + Start reading at the FROM'th line and end at the TO'th. If FROM + is zero, start at the beginning. If TO is less than FROM, read + until the end of the file. If FILENAME is NULL, then read from + ~/.history. Returns 0 if successful, or errno if not. */ +extern int read_history_range PARAMS((const char *, int, int)); + +/* Write the current history to FILENAME. If FILENAME is NULL, + then write the history list to ~/.history. Values returned + are as in read_history (). */ +extern int write_history PARAMS((const char *)); + +/* Append NELEMENT entries to FILENAME. The entries appended are from + the end of the list minus NELEMENTs up to the end of the list. */ +extern int append_history PARAMS((int, const char *)); + +/* Truncate the history file, leaving only the last NLINES lines. */ +extern int history_truncate_file PARAMS((const char *, int)); + +/* History expansion. */ + +/* Expand the string STRING, placing the result into OUTPUT, a pointer + to a string. Returns: + + 0) If no expansions took place (or, if the only change in + the text was the de-slashifying of the history expansion + character) + 1) If expansions did take place + -1) If there was an error in expansion. + 2) If the returned line should just be printed. + + If an error ocurred in expansion, then OUTPUT contains a descriptive + error message. */ +extern int history_expand PARAMS((char *, char **)); + +/* Extract a string segment consisting of the FIRST through LAST + arguments present in STRING. Arguments are broken up as in + the shell. */ +extern char *history_arg_extract PARAMS((int, int, const char *)); + +/* Return the text of the history event beginning at the current + offset into STRING. Pass STRING with *INDEX equal to the + history_expansion_char that begins this specification. + DELIMITING_QUOTE is a character that is allowed to end the string + specification for what to search for in addition to the normal + characters `:', ` ', `\t', `\n', and sometimes `?'. */ +extern char *get_history_event PARAMS((const char *, int *, int)); + +/* Return an array of tokens, much as the shell might. The tokens are + parsed out of STRING. */ +extern char **history_tokenize PARAMS((const char *)); + +/* Exported history variables. */ +extern int history_base; +extern int history_length; +extern int history_max_entries; +extern char history_expansion_char; +extern char history_subst_char; +extern const char *history_word_delimiters; +extern char history_comment_char; +extern const char *history_no_expand_chars; +extern char *history_search_delimiter_chars; +extern int history_quotes_inhibit_expansion; + +extern int history_write_timestamps; + +/* Backwards compatibility */ +extern int max_input_history; + +/* If set, this function is called to decide whether or not a particular + history expansion should be treated as a special case for the calling + application and not expanded. */ +extern rl_linebuf_func_t *history_inhibit_expansion_function; + +#ifdef __cplusplus +} +#endif + +#endif /* !_HISTORY_H_ */ diff --git a/extra/readline/histsearch.c b/extra/readline/histsearch.c new file mode 100644 index 00000000000..dcf6b420b1b --- /dev/null +++ b/extra/readline/histsearch.c @@ -0,0 +1,195 @@ +/* histsearch.c -- searching the history list. */ + +/* Copyright (C) 1989, 1992 Free Software Foundation, Inc. + + This file contains the GNU History Library (the Library), a set of + routines for managing the text of previously typed lines. + + The Library is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + The 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 + General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <stdio.h> +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include <sys/types.h> +# endif +# include <unistd.h> +#endif + +#include "history.h" +#include "histlib.h" + +/* The list of alternate characters that can delimit a history search + string. */ +char *history_search_delimiter_chars = (char *)NULL; + +static int history_search_internal PARAMS((const char *, int, int)); + +/* Search the history for STRING, starting at history_offset. + If DIRECTION < 0, then the search is through previous entries, else + through subsequent. If ANCHORED is non-zero, the string must + appear at the beginning of a history line, otherwise, the string + may appear anywhere in the line. If the string is found, then + current_history () is the history entry, and the value of this + function is the offset in the line of that history entry that the + string was found in. Otherwise, nothing is changed, and a -1 is + returned. */ + +static int +history_search_internal (string, direction, anchored) + const char *string; + int direction, anchored; +{ + register int i, reverse; + register char *line; + register int line_index; + int string_len; + HIST_ENTRY **the_history; /* local */ + + i = history_offset; + reverse = (direction < 0); + + /* Take care of trivial cases first. */ + if (string == 0 || *string == '\0') + return (-1); + + if (!history_length || ((i >= history_length) && !reverse)) + return (-1); + + if (reverse && (i >= history_length)) + i = history_length - 1; + +#define NEXT_LINE() do { if (reverse) i--; else i++; } while (0) + + the_history = history_list (); + string_len = strlen (string); + while (1) + { + /* Search each line in the history list for STRING. */ + + /* At limit for direction? */ + if ((reverse && i < 0) || (!reverse && i == history_length)) + return (-1); + + line = the_history[i]->line; + line_index = strlen (line); + + /* If STRING is longer than line, no match. */ + if (string_len > line_index) + { + NEXT_LINE (); + continue; + } + + /* Handle anchored searches first. */ + if (anchored == ANCHORED_SEARCH) + { + if (STREQN (string, line, string_len)) + { + history_offset = i; + return (0); + } + + NEXT_LINE (); + continue; + } + + /* Do substring search. */ + if (reverse) + { + line_index -= string_len; + + while (line_index >= 0) + { + if (STREQN (string, line + line_index, string_len)) + { + history_offset = i; + return (line_index); + } + line_index--; + } + } + else + { + register int limit; + + limit = line_index - string_len + 1; + line_index = 0; + + while (line_index < limit) + { + if (STREQN (string, line + line_index, string_len)) + { + history_offset = i; + return (line_index); + } + line_index++; + } + } + NEXT_LINE (); + } +} + +/* Do a non-anchored search for STRING through the history in DIRECTION. */ +int +history_search (string, direction) + const char *string; + int direction; +{ + return (history_search_internal (string, direction, NON_ANCHORED_SEARCH)); +} + +/* Do an anchored search for string through the history in DIRECTION. */ +int +history_search_prefix (string, direction) + const char *string; + int direction; +{ + return (history_search_internal (string, direction, ANCHORED_SEARCH)); +} + +/* Search for STRING in the history list. DIR is < 0 for searching + backwards. POS is an absolute index into the history list at + which point to begin searching. */ +int +history_search_pos (string, dir, pos) + const char *string; + int dir, pos; +{ + int ret, old; + + old = where_history (); + history_set_pos (pos); + if (history_search (string, dir) == -1) + { + history_set_pos (old); + return (-1); + } + ret = where_history (); + history_set_pos (old); + return ret; +} diff --git a/extra/readline/input.c b/extra/readline/input.c new file mode 100644 index 00000000000..3f8eb65c07d --- /dev/null +++ b/extra/readline/input.c @@ -0,0 +1,585 @@ +/* input.c -- character input functions for readline. */ + +/* Copyright (C) 1994-2005 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (__TANDEM) +# include <floss.h> +#endif + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <sys/types.h> +#include <fcntl.h> +#if defined (HAVE_SYS_FILE_H) +# include <sys/file.h> +#endif /* HAVE_SYS_FILE_H */ + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#if defined (HAVE_SELECT) +# if !defined (HAVE_SYS_SELECT_H) || !defined (M_UNIX) +# include <sys/time.h> +# endif +#endif /* HAVE_SELECT */ +#if defined (HAVE_SYS_SELECT_H) +# include <sys/select.h> +#endif + +#if defined (FIONREAD_IN_SYS_IOCTL) +# include <sys/ioctl.h> +#endif + +#include <stdio.h> +#include <errno.h> + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +/* System-specific feature definitions and include files. */ +#include "rldefs.h" +#include "rlmbutil.h" + +/* Some standard library routines. */ +#include "readline.h" + +#include "rlprivate.h" +#include "rlshell.h" +#include "xmalloc.h" + +/* What kind of non-blocking I/O do we have? */ +#if !defined (O_NDELAY) && defined (O_NONBLOCK) +# define O_NDELAY O_NONBLOCK /* Posix style */ +#endif + +/* Non-null means it is a pointer to a function to run while waiting for + character input. */ +rl_hook_func_t *rl_event_hook = (rl_hook_func_t *)NULL; + +rl_getc_func_t *rl_getc_function = rl_getc; + +static int _keyboard_input_timeout = 100000; /* 0.1 seconds; it's in usec */ + +static int ibuffer_space PARAMS((void)); +static int rl_get_char PARAMS((int *)); +static int rl_gather_tyi PARAMS((void)); + +/* **************************************************************** */ +/* */ +/* Character Input Buffering */ +/* */ +/* **************************************************************** */ + +static int pop_index, push_index; +static unsigned char ibuffer[512]; +static int ibuffer_len = sizeof (ibuffer) - 1; + +#define any_typein (push_index != pop_index) + +int +_rl_any_typein () +{ + return any_typein; +} + +/* Return the amount of space available in the buffer for stuffing + characters. */ +static int +ibuffer_space () +{ + if (pop_index > push_index) + return (pop_index - push_index - 1); + else + return (ibuffer_len - (push_index - pop_index)); +} + +/* Get a key from the buffer of characters to be read. + Return the key in KEY. + Result is KEY if there was a key, or 0 if there wasn't. */ +static int +rl_get_char (key) + int *key; +{ + if (push_index == pop_index) + return (0); + + *key = ibuffer[pop_index++]; + + if (pop_index >= ibuffer_len) + pop_index = 0; + + return (1); +} + +/* Stuff KEY into the *front* of the input buffer. + Returns non-zero if successful, zero if there is + no space left in the buffer. */ +int +_rl_unget_char (key) + int key; +{ + if (ibuffer_space ()) + { + pop_index--; + if (pop_index < 0) + pop_index = ibuffer_len - 1; + ibuffer[pop_index] = key; + return (1); + } + return (0); +} + +int +_rl_pushed_input_available () +{ + return (push_index != pop_index); +} + +/* If a character is available to be read, then read it and stuff it into + IBUFFER. Otherwise, just return. Returns number of characters read + (0 if none available) and -1 on error (EIO). */ +static int +rl_gather_tyi () +{ + int tty; + register int tem, result; + int chars_avail, k; + char input; +#if defined(HAVE_SELECT) + fd_set readfds, exceptfds; + struct timeval timeout; +#endif + + chars_avail = 0; + tty = fileno (rl_instream); + +#if defined (HAVE_SELECT) + FD_ZERO (&readfds); + FD_ZERO (&exceptfds); + FD_SET (tty, &readfds); + FD_SET (tty, &exceptfds); + timeout.tv_sec = 0; + timeout.tv_usec = _keyboard_input_timeout; + result = select (tty + 1, &readfds, (fd_set *)NULL, &exceptfds, &timeout); + if (result <= 0) + return 0; /* Nothing to read. */ +#endif + + result = -1; +#if defined (FIONREAD) + errno = 0; + result = ioctl (tty, FIONREAD, &chars_avail); + if (result == -1 && errno == EIO) + return -1; +#endif + +#if defined (O_NDELAY) + if (result == -1) + { + tem = fcntl (tty, F_GETFL, 0); + + fcntl (tty, F_SETFL, (tem | O_NDELAY)); + chars_avail = read (tty, &input, 1); + + fcntl (tty, F_SETFL, tem); + if (chars_avail == -1 && errno == EAGAIN) + return 0; + if (chars_avail == 0) /* EOF */ + { + rl_stuff_char (EOF); + return (0); + } + } +#endif /* O_NDELAY */ + +#if defined (__MINGW32__) + /* Use getch/_kbhit to check for available console input, in the same way + that we read it normally. */ + chars_avail = isatty (tty) ? _kbhit () : 0; + result = 0; +#endif + + /* If there's nothing available, don't waste time trying to read + something. */ + if (chars_avail <= 0) + return 0; + + tem = ibuffer_space (); + + if (chars_avail > tem) + chars_avail = tem; + + /* One cannot read all of the available input. I can only read a single + character at a time, or else programs which require input can be + thwarted. If the buffer is larger than one character, I lose. + Damn! */ + if (tem < ibuffer_len) + chars_avail = 0; + + if (result != -1) + { + while (chars_avail--) + { + k = (*rl_getc_function) (rl_instream); + rl_stuff_char (k); + if (k == NEWLINE || k == RETURN) + break; + } + } + else + { + if (chars_avail) + rl_stuff_char (input); + } + + return 1; +} + +int +rl_set_keyboard_input_timeout (u) + int u; +{ + int o; + + o = _keyboard_input_timeout; + if (u >= 0) + _keyboard_input_timeout = u; + return (o); +} + +/* Is there input available to be read on the readline input file + descriptor? Only works if the system has select(2) or FIONREAD. + Uses the value of _keyboard_input_timeout as the timeout; if another + readline function wants to specify a timeout and not leave it up to + the user, it should use _rl_input_queued(timeout_value_in_microseconds) + instead. */ +int +_rl_input_available () +{ +#if defined(HAVE_SELECT) + fd_set readfds, exceptfds; + struct timeval timeout; +#endif +#if !defined (HAVE_SELECT) && defined(FIONREAD) + int chars_avail; +#endif + int tty; + + tty = fileno (rl_instream); + +#if defined (HAVE_SELECT) + FD_ZERO (&readfds); + FD_ZERO (&exceptfds); + FD_SET (tty, &readfds); + FD_SET (tty, &exceptfds); + timeout.tv_sec = 0; + timeout.tv_usec = _keyboard_input_timeout; + return (select (tty + 1, &readfds, (fd_set *)NULL, &exceptfds, &timeout) > 0); +#else + +#if defined (FIONREAD) + if (ioctl (tty, FIONREAD, &chars_avail) == 0) + return (chars_avail); +#endif + +#endif + +#if defined (__MINGW32__) + if (isatty (tty)) + return (_kbhit ()); +#endif + +#if !defined (HAVE_SELECT) + return 0; +#endif +} + +int +_rl_input_queued (t) + int t; +{ + int old_timeout, r; + + old_timeout = rl_set_keyboard_input_timeout (t); + r = _rl_input_available (); + rl_set_keyboard_input_timeout (old_timeout); + return r; +} + +void +_rl_insert_typein (c) + int c; +{ + int key, t, i; + char *string; + + i = key = 0; + string = (char *)xmalloc (ibuffer_len + 1); + string[i++] = (char) c; + + while ((t = rl_get_char (&key)) && + _rl_keymap[key].type == ISFUNC && + _rl_keymap[key].function == rl_insert) + string[i++] = key; + + if (t) + _rl_unget_char (key); + + string[i] = '\0'; + rl_insert_text (string); + free (string); +} + +/* Add KEY to the buffer of characters to be read. Returns 1 if the + character was stuffed correctly; 0 otherwise. */ +int +rl_stuff_char (key) + int key; +{ + if (ibuffer_space () == 0) + return 0; + + if (key == EOF) + { + key = NEWLINE; + rl_pending_input = EOF; + RL_SETSTATE (RL_STATE_INPUTPENDING); + } + ibuffer[push_index++] = key; + if (push_index >= ibuffer_len) + push_index = 0; + + return 1; +} + +/* Make C be the next command to be executed. */ +int +rl_execute_next (c) + int c; +{ + rl_pending_input = c; + RL_SETSTATE (RL_STATE_INPUTPENDING); + return 0; +} + +/* Clear any pending input pushed with rl_execute_next() */ +int +rl_clear_pending_input () +{ + rl_pending_input = 0; + RL_UNSETSTATE (RL_STATE_INPUTPENDING); + return 0; +} + +/* **************************************************************** */ +/* */ +/* Character Input */ +/* */ +/* **************************************************************** */ + +/* Read a key, including pending input. */ +int +rl_read_key () +{ + int c; + + rl_key_sequence_length++; + + if (rl_pending_input) + { + c = rl_pending_input; + rl_clear_pending_input (); + } + else + { + /* If input is coming from a macro, then use that. */ + if ((c = _rl_next_macro_key ())) + return (c); + + /* If the user has an event function, then call it periodically. */ + if (rl_event_hook) + { + while (rl_event_hook && rl_get_char (&c) == 0) + { + (*rl_event_hook) (); + if (rl_done) /* XXX - experimental */ + return ('\n'); + if (rl_gather_tyi () < 0) /* XXX - EIO */ + { + rl_done = 1; + return ('\n'); + } + } + } + else + { + if (rl_get_char (&c) == 0) + c = (*rl_getc_function) (rl_instream); + } + } + + return (c); +} + +int +rl_getc (stream) + FILE *stream; +{ + int result; + unsigned char c; + + while (1) + { +#if defined (__MINGW32__) + if (isatty (fileno (stream))) + return (getch ()); +#endif + result = read (fileno (stream), &c, sizeof (unsigned char)); + + if (result == sizeof (unsigned char)) + return (c); + + /* If zero characters are returned, then the file that we are + reading from is empty! Return EOF in that case. */ + if (result == 0) + return (EOF); + +#if defined (__BEOS__) + if (errno == EINTR) + continue; +#endif + +#if defined (EWOULDBLOCK) +# define X_EWOULDBLOCK EWOULDBLOCK +#else +# define X_EWOULDBLOCK -99 +#endif + +#if defined (EAGAIN) +# define X_EAGAIN EAGAIN +#else +# define X_EAGAIN -99 +#endif + + if (errno == X_EWOULDBLOCK || errno == X_EAGAIN) + { + if (sh_unset_nodelay_mode (fileno (stream)) < 0) + return (EOF); + continue; + } + +#undef X_EWOULDBLOCK +#undef X_EAGAIN + + /* If the error that we received was SIGINT, then try again, + this is simply an interrupted system call to read (). + Otherwise, some error ocurred, also signifying EOF. */ + if (errno != EINTR) + return (RL_ISSTATE (RL_STATE_READCMD) ? READERR : EOF); + } +} + +#if defined (HANDLE_MULTIBYTE) +/* read multibyte char */ +int +_rl_read_mbchar (mbchar, size) + char *mbchar; + int size; +{ + int mb_len = 0; + size_t mbchar_bytes_length; + wchar_t wc; + mbstate_t ps, ps_back; + + memset(&ps, 0, sizeof (mbstate_t)); + memset(&ps_back, 0, sizeof (mbstate_t)); + + while (mb_len < size) + { + RL_SETSTATE(RL_STATE_MOREINPUT); + mbchar[mb_len++] = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); + + mbchar_bytes_length = mbrtowc (&wc, mbchar, mb_len, &ps); + if (mbchar_bytes_length == (size_t)(-1)) + break; /* invalid byte sequence for the current locale */ + else if (mbchar_bytes_length == (size_t)(-2)) + { + /* shorted bytes */ + ps = ps_back; + continue; + } + else if (mbchar_bytes_length == 0) + { + mbchar[0] = '\0'; /* null wide character */ + mb_len = 1; + break; + } + else if (mbchar_bytes_length > (size_t)(0)) + break; + } + + return mb_len; +} + +/* Read a multibyte-character string whose first character is FIRST into + the buffer MB of length MLEN. Returns the last character read, which + may be FIRST. Used by the search functions, among others. Very similar + to _rl_read_mbchar. */ +int +_rl_read_mbstring (first, mb, mlen) + int first; + char *mb; + int mlen; +{ + int i, c; + mbstate_t ps; + + c = first; + memset (mb, 0, mlen); + for (i = 0; i < mlen; i++) + { + mb[i] = (char)c; + memset (&ps, 0, sizeof (mbstate_t)); + if (_rl_get_char_len (mb, &ps) == -2) + { + /* Read more for multibyte character */ + RL_SETSTATE (RL_STATE_MOREINPUT); + c = rl_read_key (); + RL_UNSETSTATE (RL_STATE_MOREINPUT); + } + else + break; + } + return c; +} +#endif /* HANDLE_MULTIBYTE */ diff --git a/extra/readline/isearch.c b/extra/readline/isearch.c new file mode 100644 index 00000000000..83829f4861c --- /dev/null +++ b/extra/readline/isearch.c @@ -0,0 +1,666 @@ +/* **************************************************************** */ +/* */ +/* I-Search and Searching */ +/* */ +/* **************************************************************** */ + +/* Copyright (C) 1987-2005 Free Software Foundation, Inc. + + This file contains the Readline Library (the Library), a set of + routines for providing Emacs style line input to programs that ask + for it. + + The Library is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + The 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 + General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <sys/types.h> + +#include <stdio.h> + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif + +#include "rldefs.h" +#include "rlmbutil.h" + +#include "readline.h" +#include "history.h" + +#include "rlprivate.h" +#include "xmalloc.h" + +/* Variables exported to other files in the readline library. */ +char *_rl_isearch_terminators = (char *)NULL; + +_rl_search_cxt *_rl_iscxt = 0; + +/* Variables imported from other files in the readline library. */ +extern HIST_ENTRY *_rl_saved_line_for_history; + +static int rl_search_history PARAMS((int, int)); + +static _rl_search_cxt *_rl_isearch_init PARAMS((int)); +static void _rl_isearch_fini PARAMS((_rl_search_cxt *)); +static int _rl_isearch_cleanup PARAMS((_rl_search_cxt *, int)); + +/* Last line found by the current incremental search, so we don't `find' + identical lines many times in a row. Now part of isearch context. */ +/* static char *prev_line_found; */ + +/* Last search string and its length. */ +static char *last_isearch_string; +static int last_isearch_string_len; + +static const char *default_isearch_terminators = "\033\012"; + +_rl_search_cxt * +_rl_scxt_alloc (type, flags) + int type, flags; +{ + _rl_search_cxt *cxt; + + cxt = (_rl_search_cxt *)xmalloc (sizeof (_rl_search_cxt)); + + cxt->type = type; + cxt->sflags = flags; + + cxt->search_string = 0; + cxt->search_string_size = cxt->search_string_index = 0; + + cxt->lines = 0; + cxt->allocated_line = 0; + cxt->hlen = cxt->hindex = 0; + + cxt->save_point = rl_point; + cxt->save_mark = rl_mark; + cxt->save_line = where_history (); + cxt->last_found_line = cxt->save_line; + cxt->prev_line_found = 0; + + cxt->save_undo_list = 0; + + cxt->history_pos = 0; + cxt->direction = 0; + + cxt->lastc = 0; + + cxt->sline = 0; + cxt->sline_len = cxt->sline_index = 0; + + cxt->search_terminators = 0; + + return cxt; +} + +void +_rl_scxt_dispose (cxt, flags) + _rl_search_cxt *cxt; + int flags __attribute__((unused)); +{ + FREE (cxt->search_string); + FREE (cxt->allocated_line); + FREE (cxt->lines); + + free (cxt); +} + +/* Search backwards through the history looking for a string which is typed + interactively. Start with the current line. */ +int +rl_reverse_search_history (sign, key) + int sign, key; +{ + return (rl_search_history (-sign, key)); +} + +/* Search forwards through the history looking for a string which is typed + interactively. Start with the current line. */ +int +rl_forward_search_history (sign, key) + int sign, key; +{ + return (rl_search_history (sign, key)); +} + +/* Display the current state of the search in the echo-area. + SEARCH_STRING contains the string that is being searched for, + DIRECTION is zero for forward, or non-zero for reverse, + WHERE is the history list number of the current line. If it is + -1, then this line is the starting one. */ +static void +rl_display_search (search_string, reverse_p, where) + char *search_string; + int reverse_p, where __attribute__((unused)); +{ + char *message; + int msglen, searchlen; + + searchlen = (search_string && *search_string) ? strlen (search_string) : 0; + + message = (char *)xmalloc (searchlen + 33); + msglen = 0; + +#if defined (NOTDEF) + if (where != -1) + { + sprintf (message, "[%d]", where + history_base); + msglen = strlen (message); + } +#endif /* NOTDEF */ + + message[msglen++] = '('; + + if (reverse_p) + { + strcpy (message + msglen, "reverse-"); + msglen += 8; + } + + strcpy (message + msglen, "i-search)`"); + msglen += 10; + + if (search_string) + { + strcpy (message + msglen, search_string); + msglen += searchlen; + } + + strcpy (message + msglen, "': "); + + rl_message ("%s", message); + free (message); + (*rl_redisplay_function) (); +} + +static _rl_search_cxt * +_rl_isearch_init (direction) + int direction; +{ + _rl_search_cxt *cxt; + register int i; + HIST_ENTRY **hlist; + + cxt = _rl_scxt_alloc (RL_SEARCH_ISEARCH, 0); + if (direction < 0) + cxt->sflags |= SF_REVERSE; + + cxt->search_terminators = _rl_isearch_terminators ? _rl_isearch_terminators + : default_isearch_terminators; + + /* Create an arrary of pointers to the lines that we want to search. */ + hlist = history_list (); + rl_maybe_replace_line (); + i = 0; + if (hlist) + for (i = 0; hlist[i]; i++); + + /* Allocate space for this many lines, +1 for the current input line, + and remember those lines. */ + cxt->lines = (char **)xmalloc ((1 + (cxt->hlen = i)) * sizeof (char *)); + for (i = 0; i < cxt->hlen; i++) + cxt->lines[i] = hlist[i]->line; + + if (_rl_saved_line_for_history) + cxt->lines[i] = _rl_saved_line_for_history->line; + else + { + /* Keep track of this so we can free it. */ + cxt->allocated_line = (char *)xmalloc (1 + strlen (rl_line_buffer)); + strcpy (cxt->allocated_line, &rl_line_buffer[0]); + cxt->lines[i] = cxt->allocated_line; + } + + cxt->hlen++; + + /* The line where we start the search. */ + cxt->history_pos = cxt->save_line; + + rl_save_prompt (); + + /* Initialize search parameters. */ + cxt->search_string = (char *)xmalloc (cxt->search_string_size = 128); + cxt->search_string[cxt->search_string_index = 0] = '\0'; + + /* Normalize DIRECTION into 1 or -1. */ + cxt->direction = (direction >= 0) ? 1 : -1; + + cxt->sline = rl_line_buffer; + cxt->sline_len = strlen (cxt->sline); + cxt->sline_index = rl_point; + + _rl_iscxt = cxt; /* save globally */ + + return cxt; +} + +static void +_rl_isearch_fini (cxt) + _rl_search_cxt *cxt; +{ + /* First put back the original state. */ + strcpy (rl_line_buffer, cxt->lines[cxt->save_line]); + + rl_restore_prompt (); + + /* Save the search string for possible later use. */ + FREE (last_isearch_string); + last_isearch_string = cxt->search_string; + last_isearch_string_len = cxt->search_string_index; + cxt->search_string = 0; + + if (cxt->last_found_line < cxt->save_line) + rl_get_previous_history (cxt->save_line - cxt->last_found_line, 0); + else + rl_get_next_history (cxt->last_found_line - cxt->save_line, 0); + + /* If the string was not found, put point at the end of the last matching + line. If last_found_line == orig_line, we didn't find any matching + history lines at all, so put point back in its original position. */ + if (cxt->sline_index < 0) + { + if (cxt->last_found_line == cxt->save_line) + cxt->sline_index = cxt->save_point; + else + cxt->sline_index = strlen (rl_line_buffer); + rl_mark = cxt->save_mark; + } + + rl_point = cxt->sline_index; + /* Don't worry about where to put the mark here; rl_get_previous_history + and rl_get_next_history take care of it. */ + + rl_clear_message (); +} + +int +_rl_search_getchar (cxt) + _rl_search_cxt *cxt; +{ + int c; + + /* Read a key and decide how to proceed. */ + RL_SETSTATE(RL_STATE_MOREINPUT); + c = cxt->lastc = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); + +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + c = cxt->lastc = _rl_read_mbstring (cxt->lastc, cxt->mb, MB_LEN_MAX); +#endif + + return c; +} + +/* Process just-read character C according to isearch context CXT. Return + -1 if the caller should just free the context and return, 0 if we should + break out of the loop, and 1 if we should continue to read characters. */ +int +_rl_isearch_dispatch (cxt, c) + _rl_search_cxt *cxt; + int c; +{ + int n, wstart, wlen, limit, cval; + rl_command_func_t *f; + + f = (rl_command_func_t *)NULL; + + /* Translate the keys we do something with to opcodes. */ + if (c >= 0 && _rl_keymap[c].type == ISFUNC) + { + f = _rl_keymap[c].function; + + if (f == rl_reverse_search_history) + cxt->lastc = (cxt->sflags & SF_REVERSE) ? -1 : -2; + else if (f == rl_forward_search_history) + cxt->lastc = (cxt->sflags & SF_REVERSE) ? -2 : -1; + else if (f == rl_rubout) + cxt->lastc = -3; + else if (c == CTRL ('G')) + cxt->lastc = -4; + else if (c == CTRL ('W')) /* XXX */ + cxt->lastc = -5; + else if (c == CTRL ('Y')) /* XXX */ + cxt->lastc = -6; + } + + /* The characters in isearch_terminators (set from the user-settable + variable isearch-terminators) are used to terminate the search but + not subsequently execute the character as a command. The default + value is "\033\012" (ESC and C-J). */ + if (strchr (cxt->search_terminators, cxt->lastc)) + { + /* ESC still terminates the search, but if there is pending + input or if input arrives within 0.1 seconds (on systems + with select(2)) it is used as a prefix character + with rl_execute_next. WATCH OUT FOR THIS! This is intended + to allow the arrow keys to be used like ^F and ^B are used + to terminate the search and execute the movement command. + XXX - since _rl_input_available depends on the application- + settable keyboard timeout value, this could alternatively + use _rl_input_queued(100000) */ + if (cxt->lastc == ESC && _rl_input_available ()) + rl_execute_next (ESC); + return (0); + } + +#define ENDSRCH_CHAR(c) \ + ((CTRL_CHAR (c) || META_CHAR (c) || (c) == RUBOUT) && ((c) != CTRL ('G'))) + +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + if (cxt->lastc >= 0 && (cxt->mb[0] && cxt->mb[1] == '\0') && ENDSRCH_CHAR (cxt->lastc)) + { + /* This sets rl_pending_input to c; it will be picked up the next + time rl_read_key is called. */ + rl_execute_next (cxt->lastc); + return (0); + } + } + else +#endif + if (cxt->lastc >= 0 && ENDSRCH_CHAR (cxt->lastc)) + { + /* This sets rl_pending_input to LASTC; it will be picked up the next + time rl_read_key is called. */ + rl_execute_next (cxt->lastc); + return (0); + } + + /* Now dispatch on the character. `Opcodes' affect the search string or + state. Other characters are added to the string. */ + switch (cxt->lastc) + { + /* search again */ + case -1: + if (cxt->search_string_index == 0) + { + if (last_isearch_string) + { + cxt->search_string_size = 64 + last_isearch_string_len; + cxt->search_string = (char *)xrealloc (cxt->search_string, cxt->search_string_size); + strcpy (cxt->search_string, last_isearch_string); + cxt->search_string_index = last_isearch_string_len; + rl_display_search (cxt->search_string, (cxt->sflags & SF_REVERSE), -1); + break; + } + return (1); + } + else if (cxt->sflags & SF_REVERSE) + cxt->sline_index--; + else if (cxt->sline_index != cxt->sline_len) + cxt->sline_index++; + else + rl_ding (); + break; + + /* switch directions */ + case -2: + cxt->direction = -cxt->direction; + if (cxt->direction < 0) + cxt->sflags |= SF_REVERSE; + else + cxt->sflags &= ~SF_REVERSE; + break; + + /* delete character from search string. */ + case -3: /* C-H, DEL */ + /* This is tricky. To do this right, we need to keep a + stack of search positions for the current search, with + sentinels marking the beginning and end. But this will + do until we have a real isearch-undo. */ + if (cxt->search_string_index == 0) + rl_ding (); + else + cxt->search_string[--cxt->search_string_index] = '\0'; + break; + + case -4: /* C-G, abort */ + rl_replace_line (cxt->lines[cxt->save_line], 0); + rl_point = cxt->save_point; + rl_mark = cxt->save_mark; + rl_restore_prompt(); + rl_clear_message (); + + return -1; + + case -5: /* C-W */ + /* skip over portion of line we already matched and yank word */ + wstart = rl_point + cxt->search_string_index; + if (wstart >= rl_end) + { + rl_ding (); + break; + } + + /* if not in a word, move to one. */ + cval = _rl_char_value (rl_line_buffer, wstart); + if (_rl_walphabetic (cval) == 0) + { + rl_ding (); + break; + } + n = MB_NEXTCHAR (rl_line_buffer, wstart, 1, MB_FIND_NONZERO);; + while (n < rl_end) + { + cval = _rl_char_value (rl_line_buffer, n); + if (_rl_walphabetic (cval) == 0) + break; + n = MB_NEXTCHAR (rl_line_buffer, n, 1, MB_FIND_NONZERO);; + } + wlen = n - wstart + 1; + if (cxt->search_string_index + wlen + 1 >= cxt->search_string_size) + { + cxt->search_string_size += wlen + 1; + cxt->search_string = (char *)xrealloc (cxt->search_string, cxt->search_string_size); + } + for (; wstart < n; wstart++) + cxt->search_string[cxt->search_string_index++] = rl_line_buffer[wstart]; + cxt->search_string[cxt->search_string_index] = '\0'; + break; + + case -6: /* C-Y */ + /* skip over portion of line we already matched and yank rest */ + wstart = rl_point + cxt->search_string_index; + if (wstart >= rl_end) + { + rl_ding (); + break; + } + n = rl_end - wstart + 1; + if (cxt->search_string_index + n + 1 >= cxt->search_string_size) + { + cxt->search_string_size += n + 1; + cxt->search_string = (char *)xrealloc (cxt->search_string, cxt->search_string_size); + } + for (n = wstart; n < rl_end; n++) + cxt->search_string[cxt->search_string_index++] = rl_line_buffer[n]; + cxt->search_string[cxt->search_string_index] = '\0'; + break; + + /* Add character to search string and continue search. */ + default: + if (cxt->search_string_index + 2 >= cxt->search_string_size) + { + cxt->search_string_size += 128; + cxt->search_string = (char *)xrealloc (cxt->search_string, cxt->search_string_size); + } +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + int j, l; + for (j = 0, l = strlen (cxt->mb); j < l; ) + cxt->search_string[cxt->search_string_index++] = cxt->mb[j++]; + } + else +#endif + cxt->search_string[cxt->search_string_index++] = c; + cxt->search_string[cxt->search_string_index] = '\0'; + break; + } + + for (cxt->sflags &= ~(SF_FOUND|SF_FAILED);; ) + { + limit = cxt->sline_len - cxt->search_string_index + 1; + + /* Search the current line. */ + while ((cxt->sflags & SF_REVERSE) ? (cxt->sline_index >= 0) : (cxt->sline_index < limit)) + { + if (STREQN (cxt->search_string, cxt->sline + cxt->sline_index, cxt->search_string_index)) + { + cxt->sflags |= SF_FOUND; + break; + } + else + cxt->sline_index += cxt->direction; + } + if (cxt->sflags & SF_FOUND) + break; + + /* Move to the next line, but skip new copies of the line + we just found and lines shorter than the string we're + searching for. */ + do + { + /* Move to the next line. */ + cxt->history_pos += cxt->direction; + + /* At limit for direction? */ + if ((cxt->sflags & SF_REVERSE) ? (cxt->history_pos < 0) : (cxt->history_pos == cxt->hlen)) + { + cxt->sflags |= SF_FAILED; + break; + } + + /* We will need these later. */ + cxt->sline = cxt->lines[cxt->history_pos]; + cxt->sline_len = strlen (cxt->sline); + } + while ((cxt->prev_line_found && STREQ (cxt->prev_line_found, cxt->lines[cxt->history_pos])) || + (cxt->search_string_index > cxt->sline_len)); + + if (cxt->sflags & SF_FAILED) + break; + + /* Now set up the line for searching... */ + cxt->sline_index = (cxt->sflags & SF_REVERSE) ? cxt->sline_len - cxt->search_string_index : 0; + } + + if (cxt->sflags & SF_FAILED) + { + /* We cannot find the search string. Ding the bell. */ + rl_ding (); + cxt->history_pos = cxt->last_found_line; + return 1; + } + + /* We have found the search string. Just display it. But don't + actually move there in the history list until the user accepts + the location. */ + if (cxt->sflags & SF_FOUND) + { + cxt->prev_line_found = cxt->lines[cxt->history_pos]; + rl_replace_line (cxt->lines[cxt->history_pos], 0); + rl_point = cxt->sline_index; + cxt->last_found_line = cxt->history_pos; + rl_display_search (cxt->search_string, (cxt->sflags & SF_REVERSE), (cxt->history_pos == cxt->save_line) ? -1 : cxt->history_pos); + } + + return 1; +} + +static int +_rl_isearch_cleanup (cxt, r) + _rl_search_cxt *cxt; + int r; +{ + if (r >= 0) + _rl_isearch_fini (cxt); + _rl_scxt_dispose (cxt, 0); + _rl_iscxt = 0; + + RL_UNSETSTATE(RL_STATE_ISEARCH); + + return (r != 0); +} + +/* Search through the history looking for an interactively typed string. + This is analogous to i-search. We start the search in the current line. + DIRECTION is which direction to search; >= 0 means forward, < 0 means + backwards. */ +static int +rl_search_history (direction, invoking_key) + int direction, invoking_key __attribute__((unused)); +{ + _rl_search_cxt *cxt; /* local for now, but saved globally */ + int r; + + RL_SETSTATE(RL_STATE_ISEARCH); + cxt = _rl_isearch_init (direction); + + rl_display_search (cxt->search_string, (cxt->sflags & SF_REVERSE), -1); + + /* If we are using the callback interface, all we do is set up here and + return. The key is that we leave RL_STATE_ISEARCH set. */ + if (RL_ISSTATE (RL_STATE_CALLBACK)) + return (0); + + r = -1; + for (;;) + { + _rl_search_getchar (cxt); + /* We might want to handle EOF here (c == 0) */ + r = _rl_isearch_dispatch (cxt, cxt->lastc); + if (r <= 0) + break; + } + + /* The searching is over. The user may have found the string that she + was looking for, or else she may have exited a failing search. If + LINE_INDEX is -1, then that shows that the string searched for was + not found. We use this to determine where to place rl_point. */ + return (_rl_isearch_cleanup (cxt, r)); +} + +#if defined (READLINE_CALLBACKS) +/* Called from the callback functions when we are ready to read a key. The + callback functions know to call this because RL_ISSTATE(RL_STATE_ISEARCH). + If _rl_isearch_dispatch finishes searching, this function is responsible + for turning off RL_STATE_ISEARCH, which it does using _rl_isearch_cleanup. */ +int +_rl_isearch_callback (cxt) + _rl_search_cxt *cxt; +{ + int r; + + _rl_search_getchar (cxt); + /* We might want to handle EOF here */ + r = _rl_isearch_dispatch (cxt, cxt->lastc); + + return (r <= 0) ? _rl_isearch_cleanup (cxt, r) : 0; +} +#endif diff --git a/extra/readline/keymaps.c b/extra/readline/keymaps.c new file mode 100644 index 00000000000..17436cf20bf --- /dev/null +++ b/extra/readline/keymaps.c @@ -0,0 +1,149 @@ +/* keymaps.c -- Functions and keymaps for the GNU Readline library. */ + +/* Copyright (C) 1988,1989 Free Software Foundation, Inc. + + This file is part of GNU Readline, a library for reading lines + of text with interactive input and history editing. + + Readline is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any + later version. + + Readline 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Readline; see the file COPYING. If not, write to the Free + Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#include <stdio.h> /* for FILE * definition for readline.h */ + +#include "readline.h" +#include "rlconf.h" + +#include "emacs_keymap.c" + +#if defined (VI_MODE) +#include "vi_keymap.c" +#endif + +#include "xmalloc.h" + +/* **************************************************************** */ +/* */ +/* Functions for manipulating Keymaps. */ +/* */ +/* **************************************************************** */ + + +/* Return a new, empty keymap. + Free it with free() when you are done. */ +Keymap +rl_make_bare_keymap () +{ + register int i; + Keymap keymap = (Keymap)xmalloc (KEYMAP_SIZE * sizeof (KEYMAP_ENTRY)); + + for (i = 0; i < KEYMAP_SIZE; i++) + { + keymap[i].type = ISFUNC; + keymap[i].function = (rl_command_func_t *)NULL; + } + +#if 0 + for (i = 'A'; i < ('Z' + 1); i++) + { + keymap[i].type = ISFUNC; + keymap[i].function = rl_do_lowercase_version; + } +#endif + + return (keymap); +} + +/* Return a new keymap which is a copy of MAP. */ +Keymap +rl_copy_keymap (map) + Keymap map; +{ + register int i; + Keymap temp; + + temp = rl_make_bare_keymap (); + for (i = 0; i < KEYMAP_SIZE; i++) + { + temp[i].type = map[i].type; + temp[i].function = map[i].function; + } + return (temp); +} + +/* Return a new keymap with the printing characters bound to rl_insert, + the uppercase Meta characters bound to run their lowercase equivalents, + and the Meta digits bound to produce numeric arguments. */ +Keymap +rl_make_keymap () +{ + register int i; + Keymap newmap; + + newmap = rl_make_bare_keymap (); + + /* All ASCII printing characters are self-inserting. */ + for (i = ' '; i < 127; i++) + newmap[i].function = rl_insert; + + newmap[TAB].function = rl_insert; + newmap[RUBOUT].function = rl_rubout; /* RUBOUT == 127 */ + newmap[CTRL('H')].function = rl_rubout; + +#if KEYMAP_SIZE > 128 + /* Printing characters in ISO Latin-1 and some 8-bit character sets. */ + for (i = 128; i < 256; i++) + newmap[i].function = rl_insert; +#endif /* KEYMAP_SIZE > 128 */ + + return (newmap); +} + +/* Free the storage associated with MAP. */ +void +rl_discard_keymap (map) + Keymap map; +{ + int i; + + if (!map) + return; + + for (i = 0; i < KEYMAP_SIZE; i++) + { + switch (map[i].type) + { + case ISFUNC: + break; + + case ISKMAP: + rl_discard_keymap ((Keymap)map[i].function); + break; + + case ISMACR: + free ((char *)map[i].function); + break; + } + } +} diff --git a/extra/readline/keymaps.h b/extra/readline/keymaps.h new file mode 100644 index 00000000000..1de567ddc1e --- /dev/null +++ b/extra/readline/keymaps.h @@ -0,0 +1,103 @@ +/* keymaps.h -- Manipulation of readline keymaps. */ + +/* Copyright (C) 1987, 1989, 1992 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#ifndef _KEYMAPS_H_ +#define _KEYMAPS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined (READLINE_LIBRARY) +# include "rlstdc.h" +# include "chardefs.h" +# include "rltypedefs.h" +#else +# include <rlstdc.h> +# include <chardefs.h> +# include <rltypedefs.h> +#endif + +/* A keymap contains one entry for each key in the ASCII set. + Each entry consists of a type and a pointer. + FUNCTION is the address of a function to run, or the + address of a keymap to indirect through. + TYPE says which kind of thing FUNCTION is. */ +typedef struct _keymap_entry { + char type; + rl_command_func_t *function; +} KEYMAP_ENTRY; + +/* This must be large enough to hold bindings for all of the characters + in a desired character set (e.g, 128 for ASCII, 256 for ISO Latin-x, + and so on) plus one for subsequence matching. */ +#define KEYMAP_SIZE 257 +#define ANYOTHERKEY KEYMAP_SIZE-1 + +/* I wanted to make the above structure contain a union of: + union { rl_command_func_t *function; struct _keymap_entry *keymap; } value; + but this made it impossible for me to create a static array. + Maybe I need C lessons. */ + +typedef KEYMAP_ENTRY KEYMAP_ENTRY_ARRAY[KEYMAP_SIZE]; +typedef KEYMAP_ENTRY *Keymap; + +/* The values that TYPE can have in a keymap entry. */ +#define ISFUNC 0 +#define ISKMAP 1 +#define ISMACR 2 + +extern KEYMAP_ENTRY_ARRAY emacs_standard_keymap, emacs_meta_keymap, emacs_ctlx_keymap; +extern KEYMAP_ENTRY_ARRAY vi_insertion_keymap, vi_movement_keymap; + +/* Return a new, empty keymap. + Free it with free() when you are done. */ +extern Keymap rl_make_bare_keymap PARAMS((void)); + +/* Return a new keymap which is a copy of MAP. */ +extern Keymap rl_copy_keymap PARAMS((Keymap)); + +/* Return a new keymap with the printing characters bound to rl_insert, + the lowercase Meta characters bound to run their equivalents, and + the Meta digits bound to produce numeric arguments. */ +extern Keymap rl_make_keymap PARAMS((void)); + +/* Free the storage associated with a keymap. */ +extern void rl_discard_keymap PARAMS((Keymap)); + +/* These functions actually appear in bind.c */ + +/* Return the keymap corresponding to a given name. Names look like + `emacs' or `emacs-meta' or `vi-insert'. */ +extern Keymap rl_get_keymap_by_name PARAMS((const char *)); + +/* Return the current keymap. */ +extern Keymap rl_get_keymap PARAMS((void)); + +/* Set the current keymap to MAP. */ +extern void rl_set_keymap PARAMS((Keymap)); + +#ifdef __cplusplus +} +#endif + +#endif /* _KEYMAPS_H_ */ diff --git a/extra/readline/kill.c b/extra/readline/kill.c new file mode 100644 index 00000000000..bfe6afe77fe --- /dev/null +++ b/extra/readline/kill.c @@ -0,0 +1,694 @@ +/* kill.c -- kill ring management. */ + +/* Copyright (C) 1994 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <sys/types.h> + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> /* for _POSIX_VERSION */ +#endif /* HAVE_UNISTD_H */ + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#include <stdio.h> + +/* System-specific feature definitions and include files. */ +#include "rldefs.h" + +/* Some standard library routines. */ +#include "readline.h" +#include "history.h" + +#include "rlprivate.h" +#include "xmalloc.h" + +/* **************************************************************** */ +/* */ +/* Killing Mechanism */ +/* */ +/* **************************************************************** */ + +/* What we assume for a max number of kills. */ +#define DEFAULT_MAX_KILLS 10 + +/* The real variable to look at to find out when to flush kills. */ +static int rl_max_kills = DEFAULT_MAX_KILLS; + +/* Where to store killed text. */ +static char **rl_kill_ring = (char **)NULL; + +/* Where we are in the kill ring. */ +static int rl_kill_index; + +/* How many slots we have in the kill ring. */ +static int rl_kill_ring_length; + +static int _rl_copy_to_kill_ring PARAMS((char *, int)); +static int region_kill_internal PARAMS((int)); +static int _rl_copy_word_as_kill PARAMS((int, int)); +static int rl_yank_nth_arg_internal PARAMS((int, int, int)); + +/* How to say that you only want to save a certain amount + of kill material. */ +int +rl_set_retained_kills (num) + int num __attribute__((unused)); +{ + return 0; +} + +/* Add TEXT to the kill ring, allocating a new kill ring slot as necessary. + This uses TEXT directly, so the caller must not free it. If APPEND is + non-zero, and the last command was a kill, the text is appended to the + current kill ring slot, otherwise prepended. */ +static int +_rl_copy_to_kill_ring (text, append) + char *text; + int append; +{ + char *old, *new; + int slot; + + /* First, find the slot to work with. */ + if (_rl_last_command_was_kill == 0) + { + /* Get a new slot. */ + if (rl_kill_ring == 0) + { + /* If we don't have any defined, then make one. */ + rl_kill_ring = (char **) + xmalloc (((rl_kill_ring_length = 1) + 1) * sizeof (char *)); + rl_kill_ring[slot = 0] = (char *)NULL; + } + else + { + /* We have to add a new slot on the end, unless we have + exceeded the max limit for remembering kills. */ + slot = rl_kill_ring_length; + if (slot == rl_max_kills) + { + register int i; + free (rl_kill_ring[0]); + for (i = 0; i < slot; i++) + rl_kill_ring[i] = rl_kill_ring[i + 1]; + } + else + { + slot = rl_kill_ring_length += 1; + rl_kill_ring = (char **)xrealloc (rl_kill_ring, slot * sizeof (char *)); + } + rl_kill_ring[--slot] = (char *)NULL; + } + } + else + slot = rl_kill_ring_length - 1; + + /* If the last command was a kill, prepend or append. */ + if (_rl_last_command_was_kill && rl_editing_mode != vi_mode) + { + old = rl_kill_ring[slot]; + new = (char *)xmalloc (1 + strlen (old) + strlen (text)); + + if (append) + { + strcpy (new, old); + strcat (new, text); + } + else + { + strcpy (new, text); + strcat (new, old); + } + free (old); + free (text); + rl_kill_ring[slot] = new; + } + else + rl_kill_ring[slot] = text; + + rl_kill_index = slot; + return 0; +} + +/* The way to kill something. This appends or prepends to the last + kill, if the last command was a kill command. if FROM is less + than TO, then the text is appended, otherwise prepended. If the + last command was not a kill command, then a new slot is made for + this kill. */ +int +rl_kill_text (from, to) + int from, to; +{ + char *text; + + /* Is there anything to kill? */ + if (from == to) + { + _rl_last_command_was_kill++; + return 0; + } + + text = rl_copy_text (from, to); + + /* Delete the copied text from the line. */ + rl_delete_text (from, to); + + _rl_copy_to_kill_ring (text, from < to); + + _rl_last_command_was_kill++; + return 0; +} + +/* Now REMEMBER! In order to do prepending or appending correctly, kill + commands always make rl_point's original position be the FROM argument, + and rl_point's extent be the TO argument. */ + +/* **************************************************************** */ +/* */ +/* Killing Commands */ +/* */ +/* **************************************************************** */ + +/* Delete the word at point, saving the text in the kill ring. */ +int +rl_kill_word (count, key) + int count, key; +{ + int orig_point; + + if (count < 0) + return (rl_backward_kill_word (-count, key)); + else + { + orig_point = rl_point; + rl_forward_word (count, key); + + if (rl_point != orig_point) + rl_kill_text (orig_point, rl_point); + + rl_point = orig_point; + if (rl_editing_mode == emacs_mode) + rl_mark = rl_point; + } + return 0; +} + +/* Rubout the word before point, placing it on the kill ring. */ +int +rl_backward_kill_word (count, ignore) + int count, ignore; +{ + int orig_point; + + if (count < 0) + return (rl_kill_word (-count, ignore)); + else + { + orig_point = rl_point; + rl_backward_word (count, ignore); + + if (rl_point != orig_point) + rl_kill_text (orig_point, rl_point); + + if (rl_editing_mode == emacs_mode) + rl_mark = rl_point; + } + return 0; +} + +/* Kill from here to the end of the line. If DIRECTION is negative, kill + back to the line start instead. */ +int +rl_kill_line (direction, ignore) + int direction, ignore; +{ + int orig_point; + + if (direction < 0) + return (rl_backward_kill_line (1, ignore)); + else + { + orig_point = rl_point; + rl_end_of_line (1, ignore); + if (orig_point != rl_point) + rl_kill_text (orig_point, rl_point); + rl_point = orig_point; + if (rl_editing_mode == emacs_mode) + rl_mark = rl_point; + } + return 0; +} + +/* Kill backwards to the start of the line. If DIRECTION is negative, kill + forwards to the line end instead. */ +int +rl_backward_kill_line (direction, ignore) + int direction, ignore; +{ + int orig_point; + + if (direction < 0) + return (rl_kill_line (1, ignore)); + else + { + if (!rl_point) + rl_ding (); + else + { + orig_point = rl_point; + rl_beg_of_line (1, ignore); + if (rl_point != orig_point) + rl_kill_text (orig_point, rl_point); + if (rl_editing_mode == emacs_mode) + rl_mark = rl_point; + } + } + return 0; +} + +/* Kill the whole line, no matter where point is. */ +int +rl_kill_full_line (count, ignore) + int count __attribute__((unused)), ignore __attribute__((unused)); +{ + rl_begin_undo_group (); + rl_point = 0; + rl_kill_text (rl_point, rl_end); + rl_mark = 0; + rl_end_undo_group (); + return 0; +} + +/* The next two functions mimic unix line editing behaviour, except they + save the deleted text on the kill ring. This is safer than not saving + it, and since we have a ring, nobody should get screwed. */ + +/* This does what C-w does in Unix. We can't prevent people from + using behaviour that they expect. */ +int +rl_unix_word_rubout (count, key) + int count, key __attribute__((unused)); +{ + int orig_point; + + if (rl_point == 0) + rl_ding (); + else + { + orig_point = rl_point; + if (count <= 0) + count = 1; + + while (count--) + { + while (rl_point && whitespace (rl_line_buffer[rl_point - 1])) + rl_point--; + + while (rl_point && (whitespace (rl_line_buffer[rl_point - 1]) == 0)) + rl_point--; + } + + rl_kill_text (orig_point, rl_point); + if (rl_editing_mode == emacs_mode) + rl_mark = rl_point; + } + + return 0; +} + +/* This deletes one filename component in a Unix pathname. That is, it + deletes backward to directory separator (`/') or whitespace. */ +int +rl_unix_filename_rubout (count, key) + int count, key __attribute__((unused)); +{ + int orig_point, c; + + if (rl_point == 0) + rl_ding (); + else + { + orig_point = rl_point; + if (count <= 0) + count = 1; + + while (count--) + { + c = rl_line_buffer[rl_point - 1]; + while (rl_point && (whitespace (c) || c == '/')) + { + rl_point--; + c = rl_line_buffer[rl_point - 1]; + } + + while (rl_point && (whitespace (c) == 0) && c != '/') + { + rl_point--; + c = rl_line_buffer[rl_point - 1]; + } + } + + rl_kill_text (orig_point, rl_point); + if (rl_editing_mode == emacs_mode) + rl_mark = rl_point; + } + + return 0; +} + +/* Here is C-u doing what Unix does. You don't *have* to use these + key-bindings. We have a choice of killing the entire line, or + killing from where we are to the start of the line. We choose the + latter, because if you are a Unix weenie, then you haven't backspaced + into the line at all, and if you aren't, then you know what you are + doing. */ +int +rl_unix_line_discard (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ + if (rl_point == 0) + rl_ding (); + else + { + rl_kill_text (rl_point, 0); + rl_point = 0; + if (rl_editing_mode == emacs_mode) + rl_mark = rl_point; + } + return 0; +} + +/* Copy the text in the `region' to the kill ring. If DELETE is non-zero, + delete the text from the line as well. */ +static int +region_kill_internal (delete) + int delete; +{ + char *text; + + if (rl_mark != rl_point) + { + text = rl_copy_text (rl_point, rl_mark); + if (delete) + rl_delete_text (rl_point, rl_mark); + _rl_copy_to_kill_ring (text, rl_point < rl_mark); + } + + _rl_last_command_was_kill++; + return 0; +} + +/* Copy the text in the region to the kill ring. */ +int +rl_copy_region_to_kill (count, ignore) + int count __attribute__((unused)), ignore __attribute__((unused)); +{ + return (region_kill_internal (0)); +} + +/* Kill the text between the point and mark. */ +int +rl_kill_region (count, ignore) + int count __attribute__((unused)), ignore __attribute__((unused)); +{ + int r, npoint; + + npoint = (rl_point < rl_mark) ? rl_point : rl_mark; + r = region_kill_internal (1); + _rl_fix_point (1); + rl_point = npoint; + return r; +} + +/* Copy COUNT words to the kill ring. DIR says which direction we look + to find the words. */ +static int +_rl_copy_word_as_kill (count, dir) + int count, dir; +{ + int om, op, r; + + om = rl_mark; + op = rl_point; + + if (dir > 0) + rl_forward_word (count, 0); + else + rl_backward_word (count, 0); + + rl_mark = rl_point; + + if (dir > 0) + rl_backward_word (count, 0); + else + rl_forward_word (count, 0); + + r = region_kill_internal (0); + + rl_mark = om; + rl_point = op; + + return r; +} + +int +rl_copy_forward_word (count, key) + int count, key; +{ + if (count < 0) + return (rl_copy_backward_word (-count, key)); + + return (_rl_copy_word_as_kill (count, 1)); +} + +int +rl_copy_backward_word (count, key) + int count, key; +{ + if (count < 0) + return (rl_copy_forward_word (-count, key)); + + return (_rl_copy_word_as_kill (count, -1)); +} + +/* Yank back the last killed text. This ignores arguments. */ +int +rl_yank (count, ignore) + int count __attribute__((unused)), ignore __attribute__((unused)); +{ + if (rl_kill_ring == 0) + { + _rl_abort_internal (); + return -1; + } + + _rl_set_mark_at_pos (rl_point); + rl_insert_text (rl_kill_ring[rl_kill_index]); + return 0; +} + +/* If the last command was yank, or yank_pop, and the text just + before point is identical to the current kill item, then + delete that text from the line, rotate the index down, and + yank back some other text. */ +int +rl_yank_pop (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ + int l, n; + + if (((rl_last_func != rl_yank_pop) && (rl_last_func != rl_yank)) || + !rl_kill_ring) + { + _rl_abort_internal (); + return -1; + } + + l = strlen (rl_kill_ring[rl_kill_index]); + n = rl_point - l; + if (n >= 0 && STREQN (rl_line_buffer + n, rl_kill_ring[rl_kill_index], l)) + { + rl_delete_text (n, rl_point); + rl_point = n; + rl_kill_index--; + if (rl_kill_index < 0) + rl_kill_index = rl_kill_ring_length - 1; + rl_yank (1, 0); + return 0; + } + else + { + _rl_abort_internal (); + return -1; + } +} + +/* Yank the COUNTh argument from the previous history line, skipping + HISTORY_SKIP lines before looking for the `previous line'. */ +static int +rl_yank_nth_arg_internal (count, ignore, history_skip) + int count, ignore, history_skip; +{ + register HIST_ENTRY *entry; + char *arg; + int i, pos; + + pos = where_history (); + + if (history_skip) + { + for (i = 0; i < history_skip; i++) + entry = previous_history (); + } + + entry = previous_history (); + + history_set_pos (pos); + + if (entry == 0) + { + rl_ding (); + return -1; + } + + arg = history_arg_extract (count, count, entry->line); + if (!arg || !*arg) + { + rl_ding (); + FREE (arg); + return -1; + } + + rl_begin_undo_group (); + + _rl_set_mark_at_pos (rl_point); + +#if defined (VI_MODE) + /* Vi mode always inserts a space before yanking the argument, and it + inserts it right *after* rl_point. */ + if (rl_editing_mode == vi_mode) + { + rl_vi_append_mode (1, ignore); + rl_insert_text (" "); + } +#endif /* VI_MODE */ + + rl_insert_text (arg); + free (arg); + + rl_end_undo_group (); + return 0; +} + +/* Yank the COUNTth argument from the previous history line. */ +int +rl_yank_nth_arg (count, ignore) + int count, ignore; +{ + return (rl_yank_nth_arg_internal (count, ignore, 0)); +} + +/* Yank the last argument from the previous history line. This `knows' + how rl_yank_nth_arg treats a count of `$'. With an argument, this + behaves the same as rl_yank_nth_arg. */ +int +rl_yank_last_arg (count, key) + int count, key; +{ + static int history_skip = 0; + static int explicit_arg_p = 0; + static int count_passed = 1; + static int direction = 1; + static int undo_needed = 0; + int retval; + + if (rl_last_func != rl_yank_last_arg) + { + history_skip = 0; + explicit_arg_p = rl_explicit_arg; + count_passed = count; + direction = 1; + } + else + { + if (undo_needed) + rl_do_undo (); + if (count < 1) + direction = -direction; + history_skip += direction; + if (history_skip < 0) + history_skip = 0; + } + + if (explicit_arg_p) + retval = rl_yank_nth_arg_internal (count_passed, key, history_skip); + else + retval = rl_yank_nth_arg_internal ('$', key, history_skip); + + undo_needed = retval == 0; + return retval; +} + +/* A special paste command for users of Cygnus's cygwin32. */ +#if defined (__CYGWIN__) +#include <windows.h> + +int +rl_paste_from_clipboard (count, key) + int count, key; +{ + char *data, *ptr; + int len; + + if (OpenClipboard (NULL) == 0) + return (0); + + data = (char *)GetClipboardData (CF_TEXT); + if (data) + { + ptr = strchr (data, '\r'); + if (ptr) + { + len = ptr - data; + ptr = (char *)xmalloc (len + 1); + ptr[len] = '\0'; + strncpy (ptr, data, len); + } + else + ptr = data; + _rl_set_mark_at_pos (rl_point); + rl_insert_text (ptr); + if (ptr != data) + free (ptr); + CloseClipboard (); + } + return (0); +} +#endif /* __CYGWIN__ */ diff --git a/extra/readline/macro.c b/extra/readline/macro.c new file mode 100644 index 00000000000..7a26fc40c97 --- /dev/null +++ b/extra/readline/macro.c @@ -0,0 +1,271 @@ +/* macro.c -- keyboard macros for readline. */ + +/* Copyright (C) 1994 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <sys/types.h> + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> /* for _POSIX_VERSION */ +#endif /* HAVE_UNISTD_H */ + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#include <stdio.h> + +/* System-specific feature definitions and include files. */ +#include "rldefs.h" + +/* Some standard library routines. */ +#include "readline.h" +#include "history.h" + +#include "rlprivate.h" +#include "xmalloc.h" + +/* **************************************************************** */ +/* */ +/* Hacking Keyboard Macros */ +/* */ +/* **************************************************************** */ + +/* The currently executing macro string. If this is non-zero, + then it is a malloc ()'ed string where input is coming from. */ +char *rl_executing_macro = (char *)NULL; + +/* The offset in the above string to the next character to be read. */ +static int executing_macro_index; + +/* The current macro string being built. Characters get stuffed + in here by add_macro_char (). */ +static char *current_macro = (char *)NULL; + +/* The size of the buffer allocated to current_macro. */ +static int current_macro_size; + +/* The index at which characters are being added to current_macro. */ +static int current_macro_index; + +/* A structure used to save nested macro strings. + It is a linked list of string/index for each saved macro. */ +struct saved_macro { + struct saved_macro *next; + char *string; + int sindex; +}; + +/* The list of saved macros. */ +static struct saved_macro *macro_list = (struct saved_macro *)NULL; + +/* Set up to read subsequent input from STRING. + STRING is free ()'ed when we are done with it. */ +void +_rl_with_macro_input (string) + char *string; +{ + _rl_push_executing_macro (); + rl_executing_macro = string; + executing_macro_index = 0; + RL_SETSTATE(RL_STATE_MACROINPUT); +} + +/* Return the next character available from a macro, or 0 if + there are no macro characters. */ +int +_rl_next_macro_key () +{ + int c; + + if (rl_executing_macro == 0) + return (0); + + if (rl_executing_macro[executing_macro_index] == 0) + { + _rl_pop_executing_macro (); + return (_rl_next_macro_key ()); + } + +#if defined (READLINE_CALLBACKS) + c = rl_executing_macro[executing_macro_index++]; + if (RL_ISSTATE (RL_STATE_CALLBACK) && RL_ISSTATE (RL_STATE_READCMD|RL_STATE_MOREINPUT) && rl_executing_macro[executing_macro_index] == 0) + _rl_pop_executing_macro (); + return c; +#else + return (rl_executing_macro[executing_macro_index++]); +#endif +} + +/* Save the currently executing macro on a stack of saved macros. */ +void +_rl_push_executing_macro () +{ + struct saved_macro *saver; + + saver = (struct saved_macro *)xmalloc (sizeof (struct saved_macro)); + saver->next = macro_list; + saver->sindex = executing_macro_index; + saver->string = rl_executing_macro; + + macro_list = saver; +} + +/* Discard the current macro, replacing it with the one + on the top of the stack of saved macros. */ +void +_rl_pop_executing_macro () +{ + struct saved_macro *macro; + + FREE (rl_executing_macro); + rl_executing_macro = (char *)NULL; + executing_macro_index = 0; + + if (macro_list) + { + macro = macro_list; + rl_executing_macro = macro_list->string; + executing_macro_index = macro_list->sindex; + macro_list = macro_list->next; + free (macro); + } + + if (rl_executing_macro == 0) + RL_UNSETSTATE(RL_STATE_MACROINPUT); +} + +/* Add a character to the macro being built. */ +void +_rl_add_macro_char (c) + int c; +{ + if (current_macro_index + 1 >= current_macro_size) + { + if (current_macro == 0) + current_macro = (char *)xmalloc (current_macro_size = 25); + else + current_macro = (char *)xrealloc (current_macro, current_macro_size += 25); + } + + current_macro[current_macro_index++] = c; + current_macro[current_macro_index] = '\0'; +} + +void +_rl_kill_kbd_macro () +{ + if (current_macro) + { + free (current_macro); + current_macro = (char *) NULL; + } + current_macro_size = current_macro_index = 0; + + FREE (rl_executing_macro); + rl_executing_macro = (char *) NULL; + executing_macro_index = 0; + + RL_UNSETSTATE(RL_STATE_MACRODEF); +} + +/* Begin defining a keyboard macro. + Keystrokes are recorded as they are executed. + End the definition with rl_end_kbd_macro (). + If a numeric argument was explicitly typed, then append this + definition to the end of the existing macro, and start by + re-executing the existing macro. */ +int +rl_start_kbd_macro (ignore1, ignore2) + int ignore1 __attribute__((unused)), ignore2 __attribute__((unused)); +{ + if (RL_ISSTATE (RL_STATE_MACRODEF)) + { + _rl_abort_internal (); + return -1; + } + + if (rl_explicit_arg) + { + if (current_macro) + _rl_with_macro_input (savestring (current_macro)); + } + else + current_macro_index = 0; + + RL_SETSTATE(RL_STATE_MACRODEF); + return 0; +} + +/* Stop defining a keyboard macro. + A numeric argument says to execute the macro right now, + that many times, counting the definition as the first time. */ +int +rl_end_kbd_macro (count, ignore) + int count, ignore __attribute__((unused)); +{ + if (RL_ISSTATE (RL_STATE_MACRODEF) == 0) + { + _rl_abort_internal (); + return -1; + } + + current_macro_index -= rl_key_sequence_length - 1; + current_macro[current_macro_index] = '\0'; + + RL_UNSETSTATE(RL_STATE_MACRODEF); + + return (rl_call_last_kbd_macro (--count, 0)); +} + +/* Execute the most recently defined keyboard macro. + COUNT says how many times to execute it. */ +int +rl_call_last_kbd_macro (count, ignore) + int count, ignore __attribute__((unused)); +{ + if (current_macro == 0) + _rl_abort_internal (); + + if (RL_ISSTATE (RL_STATE_MACRODEF)) + { + rl_ding (); /* no recursive macros */ + current_macro[--current_macro_index] = '\0'; /* erase this char */ + return 0; + } + + while (count--) + _rl_with_macro_input (savestring (current_macro)); + return 0; +} + +void +rl_push_macro_input (macro) + char *macro; +{ + _rl_with_macro_input (macro); +} diff --git a/extra/readline/mbutil.c b/extra/readline/mbutil.c new file mode 100644 index 00000000000..b3d5c1b0ea4 --- /dev/null +++ b/extra/readline/mbutil.c @@ -0,0 +1,373 @@ +/* mbutil.c -- readline multibyte character utility functions */ + +/* Copyright (C) 2001-2005 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <sys/types.h> +#include <fcntl.h> +#include "posixjmp.h" + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> /* for _POSIX_VERSION */ +#endif /* HAVE_UNISTD_H */ + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#include <stdio.h> +#include <ctype.h> + +/* System-specific feature definitions and include files. */ +#include "rldefs.h" +#include "rlmbutil.h" + +#if defined (TIOCSTAT_IN_SYS_IOCTL) +# include <sys/ioctl.h> +#endif /* TIOCSTAT_IN_SYS_IOCTL */ + +/* Some standard library routines. */ +#include "readline.h" + +#include "rlprivate.h" +#include "xmalloc.h" + +/* Declared here so it can be shared between the readline and history + libraries. */ +#if defined (HANDLE_MULTIBYTE) +int rl_byte_oriented = 0; +#else +int rl_byte_oriented = 1; +#endif + +/* **************************************************************** */ +/* */ +/* Multibyte Character Utility Functions */ +/* */ +/* **************************************************************** */ + +#if defined(HANDLE_MULTIBYTE) + +static int +_rl_find_next_mbchar_internal (string, seed, count, find_non_zero) + char *string; + int seed, count, find_non_zero; +{ + size_t tmp; + mbstate_t ps; + int point; + wchar_t wc; + + tmp = 0; + + memset(&ps, 0, sizeof (mbstate_t)); + if (seed < 0) + seed = 0; + if (count <= 0) + return seed; + + point = seed + _rl_adjust_point (string, seed, &ps); + /* if this is true, means that seed was not pointed character + started byte. So correct the point and consume count */ + if (seed < point) + count--; + + while (count > 0) + { + tmp = mbrtowc (&wc, string+point, strlen(string + point), &ps); + if (MB_INVALIDCH ((size_t)tmp)) + { + /* invalid bytes. asume a byte represents a character */ + point++; + count--; + /* reset states. */ + memset(&ps, 0, sizeof(mbstate_t)); + } + else if (MB_NULLWCH (tmp)) + break; /* found wide '\0' */ + else + { + /* valid bytes */ + point += tmp; + if (find_non_zero) + { + if (wcwidth (wc) == 0) + continue; + else + count--; + } + else + count--; + } + } + + if (find_non_zero) + { + tmp = mbrtowc (&wc, string + point, strlen (string + point), &ps); + while (tmp > 0 && wcwidth (wc) == 0) + { + point += tmp; + tmp = mbrtowc (&wc, string + point, strlen (string + point), &ps); + if (MB_NULLWCH (tmp) || MB_INVALIDCH (tmp)) + break; + } + } + + return point; +} + +static int +_rl_find_prev_mbchar_internal (string, seed, find_non_zero) + char *string; + int seed, find_non_zero; +{ + mbstate_t ps; + int prev, non_zero_prev, point, length; + size_t tmp; + wchar_t wc; + + memset(&ps, 0, sizeof(mbstate_t)); + length = strlen(string); + + if (seed < 0) + return 0; + else if (length < seed) + return length; + + prev = non_zero_prev = point = 0; + while (point < seed) + { + tmp = mbrtowc (&wc, string + point, length - point, &ps); + if (MB_INVALIDCH ((size_t)tmp)) + { + /* in this case, bytes are invalid or shorted to compose + multibyte char, so assume that the first byte represents + a single character anyway. */ + tmp = 1; + /* clear the state of the byte sequence, because + in this case effect of mbstate is undefined */ + memset(&ps, 0, sizeof (mbstate_t)); + + /* Since we're assuming that this byte represents a single + non-zero-width character, don't forget about it. */ + prev = point; + } + else if (MB_NULLWCH (tmp)) + break; /* Found '\0' char. Can this happen? */ + else + { + if (find_non_zero) + { + if (wcwidth (wc) != 0) + prev = point; + } + else + prev = point; + } + + point += tmp; + } + + return prev; +} + +/* return the number of bytes parsed from the multibyte sequence starting + at src, if a non-L'\0' wide character was recognized. It returns 0, + if a L'\0' wide character was recognized. It returns (size_t)(-1), + if an invalid multibyte sequence was encountered. It returns (size_t)(-2) + if it couldn't parse a complete multibyte character. */ +int +_rl_get_char_len (src, ps) + char *src; + mbstate_t *ps; +{ + size_t tmp; + + tmp = mbrlen((const char *)src, (size_t)strlen (src), ps); + if (tmp == (size_t)(-2)) + { + /* shorted to compose multibyte char */ + if (ps) + memset (ps, 0, sizeof(mbstate_t)); + return -2; + } + else if (tmp == (size_t)(-1)) + { + /* invalid to compose multibyte char */ + /* initialize the conversion state */ + if (ps) + memset (ps, 0, sizeof(mbstate_t)); + return -1; + } + else if (tmp == (size_t)0) + return 0; + else + return (int)tmp; +} + +/* compare the specified two characters. If the characters matched, + return 1. Otherwise return 0. */ +int +_rl_compare_chars (buf1, pos1, ps1, buf2, pos2, ps2) + char *buf1; + int pos1; + mbstate_t *ps1; + char *buf2; + int pos2; + mbstate_t *ps2; +{ + int i, w1, w2; + + if ((w1 = _rl_get_char_len (&buf1[pos1], ps1)) <= 0 || + (w2 = _rl_get_char_len (&buf2[pos2], ps2)) <= 0 || + (w1 != w2) || + (buf1[pos1] != buf2[pos2])) + return 0; + + for (i = 1; i < w1; i++) + if (buf1[pos1+i] != buf2[pos2+i]) + return 0; + + return 1; +} + +/* adjust pointed byte and find mbstate of the point of string. + adjusted point will be point <= adjusted_point, and returns + differences of the byte(adjusted_point - point). + if point is invalied (point < 0 || more than string length), + it returns -1 */ +int +_rl_adjust_point(string, point, ps) + char *string; + int point; + mbstate_t *ps; +{ + size_t tmp = 0; + int length; + int pos = 0; + + length = strlen(string); + if (point < 0) + return -1; + if (length < point) + return -1; + + while (pos < point) + { + tmp = mbrlen (string + pos, length - pos, ps); + if (MB_INVALIDCH ((size_t)tmp)) + { + /* in this case, bytes are invalid or shorted to compose + multibyte char, so assume that the first byte represents + a single character anyway. */ + pos++; + /* clear the state of the byte sequence, because + in this case effect of mbstate is undefined */ + if (ps) + memset (ps, 0, sizeof (mbstate_t)); + } + else if (MB_NULLWCH (tmp)) + pos++; + else + pos += tmp; + } + + return (pos - point); +} + +int +_rl_is_mbchar_matched (string, seed, end, mbchar, length) + char *string; + int seed, end; + char *mbchar; + int length; +{ + int i; + + if ((end - seed) < length) + return 0; + + for (i = 0; i < length; i++) + if (string[seed + i] != mbchar[i]) + return 0; + return 1; +} + +wchar_t +_rl_char_value (buf, ind) + char *buf; + int ind; +{ + size_t tmp; + wchar_t wc; + mbstate_t ps; + int l; + + if (MB_LEN_MAX == 1 || rl_byte_oriented) + return ((wchar_t) buf[ind]); + l = strlen (buf); + if (ind >= l - 1) + return ((wchar_t) buf[ind]); + memset (&ps, 0, sizeof (mbstate_t)); + tmp = mbrtowc (&wc, buf + ind, l - ind, &ps); + if (MB_INVALIDCH (tmp) || MB_NULLWCH (tmp)) + return ((wchar_t) buf[ind]); + return wc; +} +#endif /* HANDLE_MULTIBYTE */ + +/* Find next `count' characters started byte point of the specified seed. + If flags is MB_FIND_NONZERO, we look for non-zero-width multibyte + characters. */ +#undef _rl_find_next_mbchar +int +_rl_find_next_mbchar (string, seed, count, flags) + char *string __attribute__((unused)); + int seed, count, flags __attribute__((unused)); +{ +#if defined (HANDLE_MULTIBYTE) + return _rl_find_next_mbchar_internal (string, seed, count, flags); +#else + return (seed + count); +#endif +} + +/* Find previous character started byte point of the specified seed. + Returned point will be point <= seed. If flags is MB_FIND_NONZERO, + we look for non-zero-width multibyte characters. */ +#undef _rl_find_prev_mbchar +int +_rl_find_prev_mbchar (string, seed, flags) + char *string __attribute__((unused)); + int seed, flags __attribute__((unused)); +{ +#if defined (HANDLE_MULTIBYTE) + return _rl_find_prev_mbchar_internal (string, seed, flags); +#else + return ((seed == 0) ? seed : seed - 1); +#endif +} diff --git a/extra/readline/misc.c b/extra/readline/misc.c new file mode 100644 index 00000000000..033a0dd9482 --- /dev/null +++ b/extra/readline/misc.c @@ -0,0 +1,603 @@ +/* misc.c -- miscellaneous bindable readline functions. */ + +/* Copyright (C) 1987-2005 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#if defined (HAVE_LOCALE_H) +# include <locale.h> +#endif + +#include <stdio.h> + +/* System-specific feature definitions and include files. */ +#include "rldefs.h" +#include "rlmbutil.h" + +/* Some standard library routines. */ +#include "readline.h" +#include "history.h" + +#include "rlprivate.h" +#include "rlshell.h" +#include "xmalloc.h" + +static int rl_digit_loop PARAMS((void)); +static void _rl_history_set_point PARAMS((void)); + +/* Forward declarations used in this file */ +void _rl_free_history_entry PARAMS((HIST_ENTRY *)); + +/* If non-zero, rl_get_previous_history and rl_get_next_history attempt + to preserve the value of rl_point from line to line. */ +int _rl_history_preserve_point = 0; + +_rl_arg_cxt _rl_argcxt; + +/* Saved target point for when _rl_history_preserve_point is set. Special + value of -1 means that point is at the end of the line. */ +int _rl_history_saved_point = -1; + +/* **************************************************************** */ +/* */ +/* Numeric Arguments */ +/* */ +/* **************************************************************** */ + +int +_rl_arg_overflow () +{ + if (rl_numeric_arg > 1000000) + { + _rl_argcxt = 0; + rl_explicit_arg = rl_numeric_arg = 0; + rl_ding (); + rl_restore_prompt (); + rl_clear_message (); + RL_UNSETSTATE(RL_STATE_NUMERICARG); + return 1; + } + return 0; +} + +void +_rl_arg_init () +{ + rl_save_prompt (); + _rl_argcxt = 0; + RL_SETSTATE(RL_STATE_NUMERICARG); +} + +int +_rl_arg_getchar () +{ + int c; + + rl_message ("(arg: %d) ", rl_arg_sign * rl_numeric_arg); + RL_SETSTATE(RL_STATE_MOREINPUT); + c = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); + + return c; +} + +/* Process C as part of the current numeric argument. Return -1 if the + argument should be aborted, 0 if we should not read any more chars, and + 1 if we should continue to read chars. */ +int +_rl_arg_dispatch (cxt, c) + _rl_arg_cxt cxt; + int c; +{ + int key, r; + + key = c; + + /* If we see a key bound to `universal-argument' after seeing digits, + it ends the argument but is otherwise ignored. */ + if (_rl_keymap[c].type == ISFUNC && _rl_keymap[c].function == rl_universal_argument) + { + if ((cxt & NUM_SAWDIGITS) == 0) + { + rl_numeric_arg *= 4; + return 1; + } + else if (RL_ISSTATE (RL_STATE_CALLBACK)) + { + _rl_argcxt |= NUM_READONE; + return 0; /* XXX */ + } + else + { + RL_SETSTATE(RL_STATE_MOREINPUT); + key = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); + rl_restore_prompt (); + rl_clear_message (); + RL_UNSETSTATE(RL_STATE_NUMERICARG); + return (_rl_dispatch (key, _rl_keymap)); + } + } + + c = UNMETA (c); + + if (_rl_digit_p (c)) + { + r = _rl_digit_value (c); + rl_numeric_arg = rl_explicit_arg ? (rl_numeric_arg * 10) + r : r; + rl_explicit_arg = 1; + _rl_argcxt |= NUM_SAWDIGITS; + } + else if (c == '-' && rl_explicit_arg == 0) + { + rl_numeric_arg = 1; + _rl_argcxt |= NUM_SAWMINUS; + rl_arg_sign = -1; + } + else + { + /* Make M-- command equivalent to M--1 command. */ + if ((_rl_argcxt & NUM_SAWMINUS) && rl_numeric_arg == 1 && rl_explicit_arg == 0) + rl_explicit_arg = 1; + rl_restore_prompt (); + rl_clear_message (); + RL_UNSETSTATE(RL_STATE_NUMERICARG); + + r = _rl_dispatch (key, _rl_keymap); + if (RL_ISSTATE (RL_STATE_CALLBACK)) + { + /* At worst, this will cause an extra redisplay. Otherwise, + we have to wait until the next character comes in. */ + if (rl_done == 0) + (*rl_redisplay_function) (); + r = 0; + } + return r; + } + + return 1; +} + +/* Handle C-u style numeric args, as well as M--, and M-digits. */ +static int +rl_digit_loop () +{ + int c, r; + + while (1) + { + if (_rl_arg_overflow ()) + return 1; + + c = _rl_arg_getchar (); + + if (c < 0) + { + _rl_abort_internal (); + return -1; + } + + r = _rl_arg_dispatch (_rl_argcxt, c); + if (r <= 0 || (RL_ISSTATE (RL_STATE_NUMERICARG) == 0)) + break; + } + + return r; +} + +/* Create a default argument. */ +void +_rl_reset_argument () +{ + rl_numeric_arg = rl_arg_sign = 1; + rl_explicit_arg = 0; + _rl_argcxt = 0; +} + +/* Start a numeric argument with initial value KEY */ +int +rl_digit_argument (ignore, key) + int ignore __attribute__((unused)), key; +{ + _rl_arg_init (); + if (RL_ISSTATE (RL_STATE_CALLBACK)) + { + _rl_arg_dispatch (_rl_argcxt, key); + rl_message ("(arg: %d) ", rl_arg_sign * rl_numeric_arg); + return 0; + } + else + { + rl_execute_next (key); + return (rl_digit_loop ()); + } +} + +/* C-u, universal argument. Multiply the current argument by 4. + Read a key. If the key has nothing to do with arguments, then + dispatch on it. If the key is the abort character then abort. */ +int +rl_universal_argument (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ + _rl_arg_init (); + rl_numeric_arg *= 4; + + return (RL_ISSTATE (RL_STATE_CALLBACK) ? 0 : rl_digit_loop ()); +} + +int +_rl_arg_callback (cxt) + _rl_arg_cxt cxt; +{ + int c, r; + + c = _rl_arg_getchar (); + + if (_rl_argcxt & NUM_READONE) + { + _rl_argcxt &= ~NUM_READONE; + rl_restore_prompt (); + rl_clear_message (); + RL_UNSETSTATE(RL_STATE_NUMERICARG); + rl_execute_next (c); + return 0; + } + + r = _rl_arg_dispatch (cxt, c); + return (r != 1); +} + +/* What to do when you abort reading an argument. */ +int +rl_discard_argument () +{ + rl_ding (); + rl_clear_message (); + _rl_reset_argument (); + + return 0; +} + +/* **************************************************************** */ +/* */ +/* History Utilities */ +/* */ +/* **************************************************************** */ + +/* We already have a history library, and that is what we use to control + the history features of readline. This is our local interface to + the history mechanism. */ + +/* While we are editing the history, this is the saved + version of the original line. */ +HIST_ENTRY *_rl_saved_line_for_history = (HIST_ENTRY *)NULL; + +/* Set the history pointer back to the last entry in the history. */ +void +_rl_start_using_history () +{ + using_history (); + if (_rl_saved_line_for_history) + _rl_free_history_entry (_rl_saved_line_for_history); + + _rl_saved_line_for_history = (HIST_ENTRY *)NULL; +} + +/* Free the contents (and containing structure) of a HIST_ENTRY. */ +void +_rl_free_history_entry (entry) + HIST_ENTRY *entry; +{ + if (entry == 0) + return; + + FREE (entry->line); + FREE (entry->timestamp); + + free (entry); +} + +/* Perhaps put back the current line if it has changed. */ +int +rl_maybe_replace_line () +{ + HIST_ENTRY *temp; + + temp = current_history (); + /* If the current line has changed, save the changes. */ + if (temp && ((UNDO_LIST *)(temp->data) != rl_undo_list)) + { + temp = replace_history_entry (where_history (), rl_line_buffer, (histdata_t)rl_undo_list); + free (temp->line); + FREE (temp->timestamp); + free (temp); + } + return 0; +} + +/* Restore the _rl_saved_line_for_history if there is one. */ +int +rl_maybe_unsave_line () +{ + if (_rl_saved_line_for_history) + { + /* Can't call with `1' because rl_undo_list might point to an undo + list from a history entry, as in rl_replace_from_history() below. */ + rl_replace_line (_rl_saved_line_for_history->line, 0); + rl_undo_list = (UNDO_LIST *)_rl_saved_line_for_history->data; + _rl_free_history_entry (_rl_saved_line_for_history); + _rl_saved_line_for_history = (HIST_ENTRY *)NULL; + rl_point = rl_end; /* rl_replace_line sets rl_end */ + } + else + rl_ding (); + return 0; +} + +/* Save the current line in _rl_saved_line_for_history. */ +int +rl_maybe_save_line () +{ + if (_rl_saved_line_for_history == 0) + { + _rl_saved_line_for_history = (HIST_ENTRY *)xmalloc (sizeof (HIST_ENTRY)); + _rl_saved_line_for_history->line = savestring (rl_line_buffer); + _rl_saved_line_for_history->timestamp = (char *)NULL; + _rl_saved_line_for_history->data = (char *)rl_undo_list; + } + + return 0; +} + +int +_rl_free_saved_history_line () +{ + if (_rl_saved_line_for_history) + { + _rl_free_history_entry (_rl_saved_line_for_history); + _rl_saved_line_for_history = (HIST_ENTRY *)NULL; + } + return 0; +} + +static void +_rl_history_set_point () +{ + rl_point = (_rl_history_preserve_point && _rl_history_saved_point != -1) + ? _rl_history_saved_point + : rl_end; + if (rl_point > rl_end) + rl_point = rl_end; + +#if defined (VI_MODE) + if (rl_editing_mode == vi_mode && _rl_keymap != vi_insertion_keymap) + rl_point = 0; +#endif /* VI_MODE */ + + if (rl_editing_mode == emacs_mode) + rl_mark = (rl_point == rl_end ? 0 : rl_end); +} + +void +rl_replace_from_history (entry, flags) + HIST_ENTRY *entry; + int flags __attribute__((unused)); /* currently unused */ +{ + /* Can't call with `1' because rl_undo_list might point to an undo list + from a history entry, just like we're setting up here. */ + rl_replace_line (entry->line, 0); + rl_undo_list = (UNDO_LIST *)entry->data; + rl_point = rl_end; + rl_mark = 0; + +#if defined (VI_MODE) + if (rl_editing_mode == vi_mode) + { + rl_point = 0; + rl_mark = rl_end; + } +#endif +} + +/* **************************************************************** */ +/* */ +/* History Commands */ +/* */ +/* **************************************************************** */ + +/* Meta-< goes to the start of the history. */ +int +rl_beginning_of_history (count, key) + int count __attribute__((unused)), key; +{ + return (rl_get_previous_history (1 + where_history (), key)); +} + +/* Meta-> goes to the end of the history. (The current line). */ +int +rl_end_of_history (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ + rl_maybe_replace_line (); + using_history (); + rl_maybe_unsave_line (); + return 0; +} + +/* Move down to the next history line. */ +int +rl_get_next_history (count, key) + int count, key; +{ + HIST_ENTRY *temp; + + if (count < 0) + return (rl_get_previous_history (-count, key)); + + if (count == 0) + return 0; + + rl_maybe_replace_line (); + + /* either not saved by rl_newline or at end of line, so set appropriately. */ + if (_rl_history_saved_point == -1 && (rl_point || rl_end)) + _rl_history_saved_point = (rl_point == rl_end) ? -1 : rl_point; + + temp = (HIST_ENTRY *)NULL; + while (count) + { + temp = next_history (); + if (!temp) + break; + --count; + } + + if (temp == 0) + rl_maybe_unsave_line (); + else + { + rl_replace_from_history (temp, 0); + _rl_history_set_point (); + } + return 0; +} + +/* Get the previous item out of our interactive history, making it the current + line. If there is no previous history, just ding. */ +int +rl_get_previous_history (count, key) + int count, key; +{ + HIST_ENTRY *old_temp, *temp; + + if (count < 0) + return (rl_get_next_history (-count, key)); + + if (count == 0) + return 0; + + /* either not saved by rl_newline or at end of line, so set appropriately. */ + if (_rl_history_saved_point == -1 && (rl_point || rl_end)) + _rl_history_saved_point = (rl_point == rl_end) ? -1 : rl_point; + + /* If we don't have a line saved, then save this one. */ + rl_maybe_save_line (); + + /* If the current line has changed, save the changes. */ + rl_maybe_replace_line (); + + temp = old_temp = (HIST_ENTRY *)NULL; + while (count) + { + temp = previous_history (); + if (temp == 0) + break; + + old_temp = temp; + --count; + } + + /* If there was a large argument, and we moved back to the start of the + history, that is not an error. So use the last value found. */ + if (!temp && old_temp) + temp = old_temp; + + if (temp == 0) + rl_ding (); + else + { + rl_replace_from_history (temp, 0); + _rl_history_set_point (); + } + + return 0; +} + +/* **************************************************************** */ +/* */ +/* Editing Modes */ +/* */ +/* **************************************************************** */ +/* How to toggle back and forth between editing modes. */ +int +rl_vi_editing_mode (count, key) + int count __attribute__((unused)), key; +{ +#if defined (VI_MODE) + _rl_set_insert_mode (RL_IM_INSERT, 1); /* vi mode ignores insert mode */ + rl_editing_mode = vi_mode; + rl_vi_insertion_mode (1, key); +#endif /* VI_MODE */ + + return 0; +} + +int +rl_emacs_editing_mode (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ + rl_editing_mode = emacs_mode; + _rl_set_insert_mode (RL_IM_INSERT, 1); /* emacs mode default is insert mode */ + _rl_keymap = emacs_standard_keymap; + return 0; +} + +/* Function for the rest of the library to use to set insert/overwrite mode. */ +void +_rl_set_insert_mode (im, force) + int im, force __attribute__((unused)); +{ +#ifdef CURSOR_MODE + _rl_set_cursor (im, force); +#endif + + rl_insert_mode = im; +} + +/* Toggle overwrite mode. A positive explicit argument selects overwrite + mode. A negative or zero explicit argument selects insert mode. */ +int +rl_overwrite_mode (count, key) + int count, key __attribute__((unused)); +{ + if (rl_explicit_arg == 0) + _rl_set_insert_mode (rl_insert_mode ^ 1, 0); + else if (count > 0) + _rl_set_insert_mode (RL_IM_OVERWRITE, 0); + else + _rl_set_insert_mode (RL_IM_INSERT, 0); + + return 0; +} diff --git a/extra/readline/nls.c b/extra/readline/nls.c new file mode 100644 index 00000000000..ddfca55d62d --- /dev/null +++ b/extra/readline/nls.c @@ -0,0 +1,254 @@ +/* nls.c -- skeletal internationalization code. */ + +/* Copyright (C) 1996 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <sys/types.h> + +#include <stdio.h> + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#if defined (HAVE_LOCALE_H) +# include <locale.h> +#endif + +#include <ctype.h> + +#include "rldefs.h" +#include "readline.h" +#include "rlshell.h" +#include "rlprivate.h" + +#if !defined (HAVE_SETLOCALE) +/* A list of legal values for the LANG or LC_CTYPE environment variables. + If a locale name in this list is the value for the LC_ALL, LC_CTYPE, + or LANG environment variable (using the first of those with a value), + readline eight-bit mode is enabled. */ +static char *legal_lang_values[] = +{ + "iso88591", + "iso88592", + "iso88593", + "iso88594", + "iso88595", + "iso88596", + "iso88597", + "iso88598", + "iso88599", + "iso885910", + "koi8r", + 0 +}; + +static char *normalize_codeset PARAMS((char *)); +static char *find_codeset PARAMS((char *, size_t *)); +#endif /* !HAVE_SETLOCALE */ + +static char *_rl_get_locale_var PARAMS((const char *)); + +static char * +_rl_get_locale_var (v) + const char *v; +{ + char *lspec; + + lspec = sh_get_env_value ("LC_ALL"); + if (lspec == 0 || *lspec == 0) + lspec = sh_get_env_value (v); + if (lspec == 0 || *lspec == 0) + lspec = sh_get_env_value ("LANG"); + + return lspec; +} + +/* Check for LC_ALL, LC_CTYPE, and LANG and use the first with a value + to decide the defaults for 8-bit character input and output. Returns + 1 if we set eight-bit mode. */ +int +_rl_init_eightbit () +{ +/* If we have setlocale(3), just check the current LC_CTYPE category + value, and go into eight-bit mode if it's not C or POSIX. */ +#if defined (HAVE_SETLOCALE) + const char *lspec; + char *t; + + /* Set the LC_CTYPE locale category from environment variables. */ + lspec = _rl_get_locale_var ("LC_CTYPE"); + /* Since _rl_get_locale_var queries the right environment variables, + we query the current locale settings with setlocale(), and, if + that doesn't return anything, we set lspec to the empty string to + force the subsequent call to setlocale() to define the `native' + environment. */ + if (lspec == 0 || *lspec == 0) + lspec = setlocale (LC_CTYPE, (char *)NULL); + if (lspec == 0) + lspec = ""; + t = setlocale (LC_CTYPE, lspec); + + if (t && *t && (t[0] != 'C' || t[1]) && (STREQ (t, "POSIX") == 0)) + { + _rl_meta_flag = 1; + _rl_convert_meta_chars_to_ascii = 0; + _rl_output_meta_chars = 1; + return (1); + } + else + return (0); + +#else /* !HAVE_SETLOCALE */ + const char *lspec; + char *t; + int i; + + /* We don't have setlocale. Finesse it. Check the environment for the + appropriate variables and set eight-bit mode if they have the right + values. */ + lspec = _rl_get_locale_var ("LC_CTYPE"); + + if (lspec == 0 || (t = normalize_codeset (lspec)) == 0) + return (0); + for (i = 0; t && legal_lang_values[i]; i++) + if (STREQ (t, legal_lang_values[i])) + { + _rl_meta_flag = 1; + _rl_convert_meta_chars_to_ascii = 0; + _rl_output_meta_chars = 1; + break; + } + free (t); + return (legal_lang_values[i] ? 1 : 0); + +#endif /* !HAVE_SETLOCALE */ +} + +#if !defined (HAVE_SETLOCALE) +static char * +normalize_codeset (codeset) + char *codeset; +{ + size_t namelen, i; + int len, all_digits; + char *wp, *retval; + + codeset = find_codeset (codeset, &namelen); + + if (codeset == 0) + return (codeset); + + all_digits = 1; + for (len = 0, i = 0; i < namelen; i++) + { + if (ISALNUM ((unsigned char)codeset[i])) + { + len++; + all_digits &= _rl_digit_p (codeset[i]); + } + } + + retval = (char *)malloc ((all_digits ? 3 : 0) + len + 1); + if (retval == 0) + return ((char *)0); + + wp = retval; + /* Add `iso' to beginning of an all-digit codeset */ + if (all_digits) + { + *wp++ = 'i'; + *wp++ = 's'; + *wp++ = 'o'; + } + + for (i = 0; i < namelen; i++) + if (ISALPHA ((unsigned char)codeset[i])) + *wp++ = _rl_to_lower (codeset[i]); + else if (_rl_digit_p (codeset[i])) + *wp++ = codeset[i]; + *wp = '\0'; + + return retval; +} + +/* Isolate codeset portion of locale specification. */ +static char * +find_codeset (name, lenp) + char *name; + size_t *lenp; +{ + char *cp, *language, *result; + + cp = language = name; + result = (char *)0; + + while (*cp && *cp != '_' && *cp != '@' && *cp != '+' && *cp != ',') + cp++; + + /* This does not make sense: language has to be specified. As + an exception we allow the variable to contain only the codeset + name. Perhaps there are funny codeset names. */ + if (language == cp) + { + *lenp = strlen (language); + result = language; + } + else + { + /* Next is the territory. */ + if (*cp == '_') + do + ++cp; + while (*cp && *cp != '.' && *cp != '@' && *cp != '+' && *cp != ',' && *cp != '_'); + + /* Now, finally, is the codeset. */ + result = cp; + if (*cp == '.') + do + ++cp; + while (*cp && *cp != '@'); + + if (cp - result > 2) + { + result++; + *lenp = cp - result; + } + else + { + *lenp = strlen (language); + result = language; + } + } + + return result; +} +#endif /* !HAVE_SETLOCALE */ diff --git a/extra/readline/parens.c b/extra/readline/parens.c new file mode 100644 index 00000000000..6b2a4d8d263 --- /dev/null +++ b/extra/readline/parens.c @@ -0,0 +1,183 @@ +/* parens.c -- Implementation of matching parentheses feature. */ + +/* Copyright (C) 1987, 1989, 1992 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (__TANDEM) +# include <floss.h> +#endif + +#include "rlconf.h" + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <stdio.h> +#include <sys/types.h> + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif + +#if defined (FD_SET) && !defined (HAVE_SELECT) +# define HAVE_SELECT +#endif + +#if defined (HAVE_SELECT) +# include <sys/time.h> +#endif /* HAVE_SELECT */ +#if defined (HAVE_SYS_SELECT_H) +# include <sys/select.h> +#endif + +#if defined (HAVE_STRING_H) +# include <string.h> +#else /* !HAVE_STRING_H */ +# include <strings.h> +#endif /* !HAVE_STRING_H */ + +#if !defined (strchr) && !defined (__STDC__) +extern char *strchr (), *strrchr (); +#endif /* !strchr && !__STDC__ */ + +#include "readline.h" +#include "rlprivate.h" + +static int find_matching_open PARAMS((char *, int, int)); + +/* Non-zero means try to blink the matching open parenthesis when the + close parenthesis is inserted. */ +#if defined (HAVE_SELECT) +int rl_blink_matching_paren = 1; +#else /* !HAVE_SELECT */ +int rl_blink_matching_paren = 0; +#endif /* !HAVE_SELECT */ + +static int _paren_blink_usec = 500000; + +/* Change emacs_standard_keymap to have bindings for paren matching when + ON_OR_OFF is 1, change them back to self_insert when ON_OR_OFF == 0. */ +void +_rl_enable_paren_matching (on_or_off) + int on_or_off; +{ + if (on_or_off) + { /* ([{ */ + rl_bind_key_in_map (')', rl_insert_close, emacs_standard_keymap); + rl_bind_key_in_map (']', rl_insert_close, emacs_standard_keymap); + rl_bind_key_in_map ('}', rl_insert_close, emacs_standard_keymap); + } + else + { /* ([{ */ + rl_bind_key_in_map (')', rl_insert, emacs_standard_keymap); + rl_bind_key_in_map (']', rl_insert, emacs_standard_keymap); + rl_bind_key_in_map ('}', rl_insert, emacs_standard_keymap); + } +} + +int +rl_set_paren_blink_timeout (u) + int u; +{ + int o; + + o = _paren_blink_usec; + if (u > 0) + _paren_blink_usec = u; + return (o); +} + +int +rl_insert_close (count, invoking_key) + int count, invoking_key; +{ + if (rl_explicit_arg || !rl_blink_matching_paren) + _rl_insert_char (count, invoking_key); + else + { +#if defined (HAVE_SELECT) + int orig_point, match_point; + struct timeval timer; + fd_set readfds; + + _rl_insert_char (1, invoking_key); + (*rl_redisplay_function) (); + match_point = + find_matching_open (rl_line_buffer, rl_point - 2, invoking_key); + + /* Emacs might message or ring the bell here, but I don't. */ + if (match_point < 0) + return -1; + + FD_ZERO (&readfds); + FD_SET (fileno (rl_instream), &readfds); + timer.tv_sec = 0; + timer.tv_usec = _paren_blink_usec; + + orig_point = rl_point; + rl_point = match_point; + (*rl_redisplay_function) (); + select (1, &readfds, (fd_set *)NULL, (fd_set *)NULL, &timer); + rl_point = orig_point; +#else /* !HAVE_SELECT */ + _rl_insert_char (count, invoking_key); +#endif /* !HAVE_SELECT */ + } + return 0; +} + +static int +find_matching_open (string, from, closer) + char *string; + int from, closer; +{ + register int i; + int opener, level, delimiter; + + switch (closer) + { + case ']': opener = '['; break; + case '}': opener = '{'; break; + case ')': opener = '('; break; + default: + return (-1); + } + + level = 1; /* The closer passed in counts as 1. */ + delimiter = 0; /* Delimited state unknown. */ + + for (i = from; i > -1; i--) + { + if (delimiter && (string[i] == delimiter)) + delimiter = 0; + else if (rl_basic_quote_characters && strchr (rl_basic_quote_characters, string[i])) + delimiter = string[i]; + else if (!delimiter && (string[i] == closer)) + level++; + else if (!delimiter && (string[i] == opener)) + level--; + + if (!level) + break; + } + return (i); +} diff --git a/extra/readline/posixdir.h b/extra/readline/posixdir.h new file mode 100644 index 00000000000..fe4cb231173 --- /dev/null +++ b/extra/readline/posixdir.h @@ -0,0 +1,61 @@ +/* posixdir.h -- Posix directory reading includes and defines. */ + +/* Copyright (C) 1987,1991 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + Bash 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 General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with Bash; see the file COPYING. If not, write to the Free + Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* This file should be included instead of <dirent.h> or <sys/dir.h>. */ + +#if !defined (_POSIXDIR_H_) +#define _POSIXDIR_H_ + +#if defined (HAVE_DIRENT_H) +# include <dirent.h> +# if defined (HAVE_STRUCT_DIRENT_D_NAMLEN) +# define D_NAMLEN(d) ((d)->d_namlen) +# else +# define D_NAMLEN(d) (strlen ((d)->d_name)) +# endif /* !HAVE_STRUCT_DIRENT_D_NAMLEN */ +#else +# if defined (HAVE_SYS_NDIR_H) +# include <sys/ndir.h> +# endif +# if defined (HAVE_SYS_DIR_H) +# include <sys/dir.h> +# endif +# if defined (HAVE_NDIR_H) +# include <ndir.h> +# endif +# if !defined (dirent) +# define dirent direct +# endif /* !dirent */ +# define D_NAMLEN(d) ((d)->d_namlen) +#endif /* !HAVE_DIRENT_H */ + +#if defined (HAVE_STRUCT_DIRENT_D_INO) && !defined (HAVE_STRUCT_DIRENT_D_FILENO) +# define d_fileno d_ino +#endif + +#if defined (_POSIX_SOURCE) && (!defined (HAVE_STRUCT_DIRENT_D_INO) || defined (BROKEN_DIRENT_D_INO)) +/* Posix does not require that the d_ino field be present, and some + systems do not provide it. */ +# define REAL_DIR_ENTRY(dp) 1 +#else +# define REAL_DIR_ENTRY(dp) (dp->d_ino != 0) +#endif /* _POSIX_SOURCE */ + +#endif /* !_POSIXDIR_H_ */ diff --git a/extra/readline/posixjmp.h b/extra/readline/posixjmp.h new file mode 100644 index 00000000000..a2c89b83d4e --- /dev/null +++ b/extra/readline/posixjmp.h @@ -0,0 +1,40 @@ +/* posixjmp.h -- wrapper for setjmp.h with changes for POSIX systems. */ + +/* Copyright (C) 1987,1991 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + Bash 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 General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with Bash; see the file COPYING. If not, write to the Free + Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#ifndef _POSIXJMP_H_ +#define _POSIXJMP_H_ + +#include <setjmp.h> + +/* This *must* be included *after* config.h */ + +#if defined (HAVE_POSIX_SIGSETJMP) +# define procenv_t sigjmp_buf +# if !defined (__OPENNT) +# undef setjmp +# define setjmp(x) sigsetjmp((x), 1) +# undef longjmp +# define longjmp(x, n) siglongjmp((x), (n)) +# endif /* !__OPENNT */ +#else +# define procenv_t jmp_buf +#endif + +#endif /* _POSIXJMP_H_ */ diff --git a/extra/readline/posixstat.h b/extra/readline/posixstat.h new file mode 100644 index 00000000000..d3eca09b634 --- /dev/null +++ b/extra/readline/posixstat.h @@ -0,0 +1,142 @@ +/* posixstat.h -- Posix stat(2) definitions for systems that + don't have them. */ + +/* Copyright (C) 1987,1991 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + Bash 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 General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with Bash; see the file COPYING. If not, write to the Free + Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* This file should be included instead of <sys/stat.h>. + It relies on the local sys/stat.h to work though. */ +#if !defined (_POSIXSTAT_H_) +#define _POSIXSTAT_H_ + +#include <sys/stat.h> + +#if defined (STAT_MACROS_BROKEN) +# undef S_ISBLK +# undef S_ISCHR +# undef S_ISDIR +# undef S_ISFIFO +# undef S_ISREG +# undef S_ISLNK +#endif /* STAT_MACROS_BROKEN */ + +/* These are guaranteed to work only on isc386 */ +#if !defined (S_IFDIR) && !defined (S_ISDIR) +# define S_IFDIR 0040000 +#endif /* !S_IFDIR && !S_ISDIR */ +#if !defined (S_IFMT) +# define S_IFMT 0170000 +#endif /* !S_IFMT */ + +/* Posix 1003.1 5.6.1.1 <sys/stat.h> file types */ + +/* Some Posix-wannabe systems define _S_IF* macros instead of S_IF*, but + do not provide the S_IS* macros that Posix requires. */ + +#if defined (_S_IFMT) && !defined (S_IFMT) +#define S_IFMT _S_IFMT +#endif +#if defined (_S_IFIFO) && !defined (S_IFIFO) +#define S_IFIFO _S_IFIFO +#endif +#if defined (_S_IFCHR) && !defined (S_IFCHR) +#define S_IFCHR _S_IFCHR +#endif +#if defined (_S_IFDIR) && !defined (S_IFDIR) +#define S_IFDIR _S_IFDIR +#endif +#if defined (_S_IFBLK) && !defined (S_IFBLK) +#define S_IFBLK _S_IFBLK +#endif +#if defined (_S_IFREG) && !defined (S_IFREG) +#define S_IFREG _S_IFREG +#endif +#if defined (_S_IFLNK) && !defined (S_IFLNK) +#define S_IFLNK _S_IFLNK +#endif +#if defined (_S_IFSOCK) && !defined (S_IFSOCK) +#define S_IFSOCK _S_IFSOCK +#endif + +/* Test for each symbol individually and define the ones necessary (some + systems claiming Posix compatibility define some but not all). */ + +#if defined (S_IFBLK) && !defined (S_ISBLK) +#define S_ISBLK(m) (((m)&S_IFMT) == S_IFBLK) /* block device */ +#endif + +#if defined (S_IFCHR) && !defined (S_ISCHR) +#define S_ISCHR(m) (((m)&S_IFMT) == S_IFCHR) /* character device */ +#endif + +#if defined (S_IFDIR) && !defined (S_ISDIR) +#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR) /* directory */ +#endif + +#if defined (S_IFREG) && !defined (S_ISREG) +#define S_ISREG(m) (((m)&S_IFMT) == S_IFREG) /* file */ +#endif + +#if defined (S_IFIFO) && !defined (S_ISFIFO) +#define S_ISFIFO(m) (((m)&S_IFMT) == S_IFIFO) /* fifo - named pipe */ +#endif + +#if defined (S_IFLNK) && !defined (S_ISLNK) +#define S_ISLNK(m) (((m)&S_IFMT) == S_IFLNK) /* symbolic link */ +#endif + +#if defined (S_IFSOCK) && !defined (S_ISSOCK) +#define S_ISSOCK(m) (((m)&S_IFMT) == S_IFSOCK) /* socket */ +#endif + +/* + * POSIX 1003.1 5.6.1.2 <sys/stat.h> File Modes + */ + +#if !defined (S_IRWXU) +# if !defined (S_IREAD) +# define S_IREAD 00400 +# define S_IWRITE 00200 +# define S_IEXEC 00100 +# endif /* S_IREAD */ + +# if !defined (S_IRUSR) +# define S_IRUSR S_IREAD /* read, owner */ +# define S_IWUSR S_IWRITE /* write, owner */ +# define S_IXUSR S_IEXEC /* execute, owner */ + +# define S_IRGRP (S_IREAD >> 3) /* read, group */ +# define S_IWGRP (S_IWRITE >> 3) /* write, group */ +# define S_IXGRP (S_IEXEC >> 3) /* execute, group */ + +# define S_IROTH (S_IREAD >> 6) /* read, other */ +# define S_IWOTH (S_IWRITE >> 6) /* write, other */ +# define S_IXOTH (S_IEXEC >> 6) /* execute, other */ +# endif /* !S_IRUSR */ + +# define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR) +# define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP) +# define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH) +#endif /* !S_IRWXU */ + +/* These are non-standard, but are used in builtins.c$symbolic_umask() */ +#define S_IRUGO (S_IRUSR | S_IRGRP | S_IROTH) +#define S_IWUGO (S_IWUSR | S_IWGRP | S_IWOTH) +#define S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH) + +#endif /* _POSIXSTAT_H_ */ diff --git a/extra/readline/readline.c b/extra/readline/readline.c new file mode 100644 index 00000000000..d2e710875dc --- /dev/null +++ b/extra/readline/readline.c @@ -0,0 +1,1200 @@ +/* readline.c -- a general facility for reading lines of input + with emacs style editing and completion. */ + +/* Copyright (C) 1987-2005 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <sys/types.h> +#include "posixstat.h" +#include <fcntl.h> +#if defined (HAVE_SYS_FILE_H) +# include <sys/file.h> +#endif /* HAVE_SYS_FILE_H */ + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#if defined (HAVE_LOCALE_H) +# include <locale.h> +#endif + +#include <stdio.h> +#include "posixjmp.h" +#include <errno.h> + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +/* System-specific feature definitions and include files. */ +#include "rldefs.h" +#include "rlmbutil.h" + +#if defined (__EMX__) +# define INCL_DOSPROCESS +# include <os2.h> +#endif /* __EMX__ */ + +/* Some standard library routines. */ +#include "readline.h" +#include "history.h" + +#include "rlprivate.h" +#include "rlshell.h" +#include "xmalloc.h" + +#ifndef RL_LIBRARY_VERSION +# define RL_LIBRARY_VERSION "5.1" +#endif + +#ifndef RL_READLINE_VERSION +# define RL_READLINE_VERSION 0x0501 +#endif + +extern void _rl_free_history_entry PARAMS((HIST_ENTRY *)); + +/* Forward declarations used in this file. */ +static char *readline_internal PARAMS((void)); +static void readline_initialize_everything PARAMS((void)); + +static void bind_arrow_keys_internal PARAMS((Keymap)); +static void bind_arrow_keys PARAMS((void)); + +static void readline_default_bindings PARAMS((void)); + +static int _rl_subseq_result PARAMS((int, Keymap, int, int)); +static int _rl_subseq_getchar PARAMS((int)); + +/* **************************************************************** */ +/* */ +/* Line editing input utility */ +/* */ +/* **************************************************************** */ + +const char *rl_library_version = RL_LIBRARY_VERSION; + +int rl_readline_version = RL_READLINE_VERSION; + +/* True if this is `real' readline as opposed to some stub substitute. */ +int rl_gnu_readline_p = 1; + +/* A pointer to the keymap that is currently in use. + By default, it is the standard emacs keymap. */ +Keymap _rl_keymap = emacs_standard_keymap; + + +/* The current style of editing. */ +int rl_editing_mode = emacs_mode; + +/* The current insert mode: input (the default) or overwrite */ +int rl_insert_mode = RL_IM_DEFAULT; + +/* Non-zero if we called this function from _rl_dispatch(). It's present + so functions can find out whether they were called from a key binding + or directly from an application. */ +int rl_dispatching; + +/* Non-zero if the previous command was a kill command. */ +int _rl_last_command_was_kill = 0; + +/* The current value of the numeric argument specified by the user. */ +int rl_numeric_arg = 1; + +/* Non-zero if an argument was typed. */ +int rl_explicit_arg = 0; + +/* Temporary value used while generating the argument. */ +int rl_arg_sign = 1; + +/* Non-zero means we have been called at least once before. */ +static int rl_initialized; + +#if 0 +/* If non-zero, this program is running in an EMACS buffer. */ +static int running_in_emacs; +#endif + +/* Flags word encapsulating the current readline state. */ +int rl_readline_state = RL_STATE_NONE; + +/* The current offset in the current input line. */ +int rl_point; + +/* Mark in the current input line. */ +int rl_mark; + +/* Length of the current input line. */ +int rl_end; + +/* Make this non-zero to return the current input_line. */ +int rl_done; + +/* The last function executed by readline. */ +rl_command_func_t *rl_last_func = (rl_command_func_t *)NULL; + +/* Top level environment for readline_internal (). */ +procenv_t readline_top_level; + +/* The streams we interact with. */ +FILE *_rl_in_stream, *_rl_out_stream; + +/* The names of the streams that we do input and output to. */ +FILE *rl_instream = (FILE *)NULL; +FILE *rl_outstream = (FILE *)NULL; + +/* Non-zero means echo characters as they are read. Defaults to no echo; + set to 1 if there is a controlling terminal, we can get its attributes, + and the attributes include `echo'. Look at rltty.c:prepare_terminal_settings + for the code that sets it. */ +int readline_echoing_p = 0; + +/* Current prompt. */ +char *rl_prompt = (char *)NULL; +int rl_visible_prompt_length = 0; + +/* Set to non-zero by calling application if it has already printed rl_prompt + and does not want readline to do it the first time. */ +int rl_already_prompted = 0; + +/* The number of characters read in order to type this complete command. */ +int rl_key_sequence_length = 0; + +/* If non-zero, then this is the address of a function to call just + before readline_internal_setup () prints the first prompt. */ +rl_hook_func_t *rl_startup_hook = (rl_hook_func_t *)NULL; + +/* If non-zero, this is the address of a function to call just before + readline_internal_setup () returns and readline_internal starts + reading input characters. */ +rl_hook_func_t *rl_pre_input_hook = (rl_hook_func_t *)NULL; + +/* What we use internally. You should always refer to RL_LINE_BUFFER. */ +static char *the_line; + +/* The character that can generate an EOF. Really read from + the terminal driver... just defaulted here. */ +int _rl_eof_char = CTRL ('D'); + +/* Non-zero makes this the next keystroke to read. */ +int rl_pending_input = 0; + +/* Pointer to a useful terminal name. */ +const char *rl_terminal_name = (const char *)NULL; + +/* Non-zero means to always use horizontal scrolling in line display. */ +int _rl_horizontal_scroll_mode = 0; + +/* Non-zero means to display an asterisk at the starts of history lines + which have been modified. */ +int _rl_mark_modified_lines = 0; + +/* The style of `bell' notification preferred. This can be set to NO_BELL, + AUDIBLE_BELL, or VISIBLE_BELL. */ +int _rl_bell_preference = AUDIBLE_BELL; + +/* String inserted into the line by rl_insert_comment (). */ +char *_rl_comment_begin; + +/* Keymap holding the function currently being executed. */ +Keymap rl_executing_keymap; + +/* Keymap we're currently using to dispatch. */ +Keymap _rl_dispatching_keymap; + +/* Non-zero means to erase entire line, including prompt, on empty input lines. */ +int rl_erase_empty_line = 0; + +/* Non-zero means to read only this many characters rather than up to a + character bound to accept-line. */ +int rl_num_chars_to_read; + +/* Line buffer and maintenence. */ +char *rl_line_buffer = (char *)NULL; +int rl_line_buffer_len = 0; + +/* Key sequence `contexts' */ +_rl_keyseq_cxt *_rl_kscxt = 0; + +/* Forward declarations used by the display, termcap, and history code. */ + +/* **************************************************************** */ +/* */ +/* `Forward' declarations */ +/* */ +/* **************************************************************** */ + +/* Non-zero means do not parse any lines other than comments and + parser directives. */ +unsigned char _rl_parsing_conditionalized_out = 0; + +/* Non-zero means to convert characters with the meta bit set to + escape-prefixed characters so we can indirect through + emacs_meta_keymap or vi_escape_keymap. */ +int _rl_convert_meta_chars_to_ascii = 1; + +/* Non-zero means to output characters with the meta bit set directly + rather than as a meta-prefixed escape sequence. */ +int _rl_output_meta_chars = 0; + +/* Non-zero means to look at the termios special characters and bind + them to equivalent readline functions at startup. */ +int _rl_bind_stty_chars = 1; + +/* **************************************************************** */ +/* */ +/* Top Level Functions */ +/* */ +/* **************************************************************** */ + +/* Non-zero means treat 0200 bit in terminal input as Meta bit. */ +int _rl_meta_flag = 0; /* Forward declaration */ + +/* Set up the prompt and expand it. Called from readline() and + rl_callback_handler_install (). */ +int +rl_set_prompt (prompt) + const char *prompt; +{ + FREE (rl_prompt); + rl_prompt = prompt ? savestring (prompt) : (char *)NULL; + rl_display_prompt = rl_prompt ? rl_prompt : (char*) ""; + + rl_visible_prompt_length = rl_expand_prompt (rl_prompt); + return 0; +} + +/* Read a line of input. Prompt with PROMPT. An empty PROMPT means + none. A return value of NULL means that EOF was encountered. */ +char * +readline (prompt) + const char *prompt; +{ + char *value; + + /* If we are at EOF return a NULL string. */ + if (rl_pending_input == EOF) + { + rl_clear_pending_input (); + return ((char *)NULL); + } + + rl_set_prompt (prompt); + + rl_initialize (); + if (rl_prep_term_function) + (*rl_prep_term_function) (_rl_meta_flag); + +#if defined (HANDLE_SIGNALS) + rl_set_signals (); +#endif + + value = readline_internal (); + if (rl_deprep_term_function) + (*rl_deprep_term_function) (); + +#if defined (HANDLE_SIGNALS) + rl_clear_signals (); +#endif + + return (value); +} + +#if defined (READLINE_CALLBACKS) +# define STATIC_CALLBACK +#else +# define STATIC_CALLBACK static +#endif + +STATIC_CALLBACK void +readline_internal_setup () +{ + char *nprompt; + + _rl_in_stream = rl_instream; + _rl_out_stream = rl_outstream; + + if (rl_startup_hook) + (*rl_startup_hook) (); + + /* If we're not echoing, we still want to at least print a prompt, because + rl_redisplay will not do it for us. If the calling application has a + custom redisplay function, though, let that function handle it. */ + if (readline_echoing_p == 0 && rl_redisplay_function == rl_redisplay) + { + if (rl_prompt && rl_already_prompted == 0) + { + nprompt = _rl_strip_prompt (rl_prompt); + fprintf (_rl_out_stream, "%s", nprompt); + fflush (_rl_out_stream); + free (nprompt); + } + } + else + { + if (rl_prompt && rl_already_prompted) + rl_on_new_line_with_prompt (); + else + rl_on_new_line (); + (*rl_redisplay_function) (); + } + +#if defined (VI_MODE) + if (rl_editing_mode == vi_mode) + rl_vi_insertion_mode (1, 'i'); +#endif /* VI_MODE */ + + if (rl_pre_input_hook) + (*rl_pre_input_hook) (); +} + +STATIC_CALLBACK char * +readline_internal_teardown (eof) + int eof; +{ + char *temp; + HIST_ENTRY *entry; + + /* Restore the original of this history line, iff the line that we + are editing was originally in the history, AND the line has changed. */ + entry = current_history (); + + if (entry && rl_undo_list) + { + temp = savestring (the_line); + rl_revert_line (1, 0); + entry = replace_history_entry (where_history (), the_line, (histdata_t)NULL); + _rl_free_history_entry (entry); + + strcpy (the_line, temp); + free (temp); + } + + /* At any rate, it is highly likely that this line has an undo list. Get + rid of it now. */ + if (rl_undo_list) + rl_free_undo_list (); + + /* Restore normal cursor, if available. */ + _rl_set_insert_mode (RL_IM_INSERT, 0); + + return (eof ? (char *)NULL : savestring (the_line)); +} + +void +_rl_internal_char_cleanup () +{ +#if defined (VI_MODE) + /* In vi mode, when you exit insert mode, the cursor moves back + over the previous character. We explicitly check for that here. */ + if (rl_editing_mode == vi_mode && _rl_keymap == vi_movement_keymap) + rl_vi_check (); +#endif /* VI_MODE */ + + if (rl_num_chars_to_read && rl_end >= rl_num_chars_to_read) + { + (*rl_redisplay_function) (); + _rl_want_redisplay = 0; + rl_newline (1, '\n'); + } + + if (rl_done == 0) + { + (*rl_redisplay_function) (); + _rl_want_redisplay = 0; + } + + /* If the application writer has told us to erase the entire line if + the only character typed was something bound to rl_newline, do so. */ + if (rl_erase_empty_line && rl_done && rl_last_func == rl_newline && + rl_point == 0 && rl_end == 0) + _rl_erase_entire_line (); +} + +STATIC_CALLBACK int +#if defined (READLINE_CALLBACKS) +readline_internal_char () +#else +readline_internal_charloop () +#endif +{ + static int lastc; + int c, code, lk; + + lastc = -1; + +#if !defined (READLINE_CALLBACKS) + while (rl_done == 0) + { +#endif + lk = _rl_last_command_was_kill; + + code = setjmp (readline_top_level); + + if (code) + { + (*rl_redisplay_function) (); + _rl_want_redisplay = 0; + /* If we get here, we're not being called from something dispatched + from _rl_callback_read_char(), which sets up its own value of + readline_top_level (saving and restoring the old, of course), so + we can just return here. */ + if (RL_ISSTATE (RL_STATE_CALLBACK)) + return (0); + } + + if (rl_pending_input == 0) + { + /* Then initialize the argument and number of keys read. */ + _rl_reset_argument (); + rl_key_sequence_length = 0; + } + + RL_SETSTATE(RL_STATE_READCMD); + c = rl_read_key (); + RL_UNSETSTATE(RL_STATE_READCMD); + + /* look at input.c:rl_getc() for the circumstances under which this will + be returned; punt immediately on read error without converting it to + a newline. */ + if (c == READERR) + { +#if defined (READLINE_CALLBACKS) + RL_SETSTATE(RL_STATE_DONE); + return (rl_done = 1); +#else + eof_found = 1; + break; +#endif + } + + /* EOF typed to a non-blank line is a <NL>. */ + if (c == EOF && rl_end) + c = NEWLINE; + + /* The character _rl_eof_char typed to blank line, and not as the + previous character is interpreted as EOF. */ + if (((c == _rl_eof_char && lastc != c) || c == EOF) && !rl_end) + { +#if defined (READLINE_CALLBACKS) + RL_SETSTATE(RL_STATE_DONE); + return (rl_done = 1); +#else + eof_found = 1; + break; +#endif + } + + lastc = c; + _rl_dispatch ((unsigned char)c, _rl_keymap); + + /* If there was no change in _rl_last_command_was_kill, then no kill + has taken place. Note that if input is pending we are reading + a prefix command, so nothing has changed yet. */ + if (rl_pending_input == 0 && lk == _rl_last_command_was_kill) + _rl_last_command_was_kill = 0; + + _rl_internal_char_cleanup (); + +#if defined (READLINE_CALLBACKS) + return 0; +#else + } + + return (eof_found); +#endif +} + +#if defined (READLINE_CALLBACKS) +static int +readline_internal_charloop () +{ + int eof = 1; + + while (rl_done == 0) + eof = readline_internal_char (); + return (eof); +} +#endif /* READLINE_CALLBACKS */ + +/* Read a line of input from the global rl_instream, doing output on + the global rl_outstream. + If rl_prompt is non-null, then that is our prompt. */ +static char * +readline_internal () +{ + int eof; + + readline_internal_setup (); + eof = readline_internal_charloop (); + return (readline_internal_teardown (eof)); +} + +void +_rl_init_line_state () +{ + rl_point = rl_end = rl_mark = 0; + the_line = rl_line_buffer; + the_line[0] = 0; +} + +void +_rl_set_the_line () +{ + the_line = rl_line_buffer; +} + +#if defined (READLINE_CALLBACKS) +_rl_keyseq_cxt * +_rl_keyseq_cxt_alloc () +{ + _rl_keyseq_cxt *cxt; + + cxt = (_rl_keyseq_cxt *)xmalloc (sizeof (_rl_keyseq_cxt)); + + cxt->flags = cxt->subseq_arg = cxt->subseq_retval = 0; + + cxt->okey = 0; + cxt->ocxt = _rl_kscxt; + cxt->childval = 42; /* sentinel value */ + + return cxt; +} + +void +_rl_keyseq_cxt_dispose (cxt) + _rl_keyseq_cxt *cxt; +{ + free (cxt); +} + +void +_rl_keyseq_chain_dispose () +{ + _rl_keyseq_cxt *cxt; + + while (_rl_kscxt) + { + cxt = _rl_kscxt; + _rl_kscxt = _rl_kscxt->ocxt; + _rl_keyseq_cxt_dispose (cxt); + } +} +#endif + +static int +_rl_subseq_getchar (key) + int key; +{ + int k; + + if (key == ESC) + RL_SETSTATE(RL_STATE_METANEXT); + RL_SETSTATE(RL_STATE_MOREINPUT); + k = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); + if (key == ESC) + RL_UNSETSTATE(RL_STATE_METANEXT); + + return k; +} + +#if defined (READLINE_CALLBACKS) +int +_rl_dispatch_callback (cxt) + _rl_keyseq_cxt *cxt; +{ + int nkey, r; + + /* For now */ +#if 1 + /* The first time this context is used, we want to read input and dispatch + on it. When traversing the chain of contexts back `up', we want to use + the value from the next context down. We're simulating recursion using + a chain of contexts. */ + if ((cxt->flags & KSEQ_DISPATCHED) == 0) + { + nkey = _rl_subseq_getchar (cxt->okey); + r = _rl_dispatch_subseq (nkey, cxt->dmap, cxt->subseq_arg); + cxt->flags |= KSEQ_DISPATCHED; + } + else + r = cxt->childval; +#else + r = _rl_dispatch_subseq (nkey, cxt->dmap, cxt->subseq_arg); +#endif + + /* For now */ + r = _rl_subseq_result (r, cxt->oldmap, cxt->okey, (cxt->flags & KSEQ_SUBSEQ)); + + if (r == 0) /* success! */ + { + _rl_keyseq_chain_dispose (); + RL_UNSETSTATE (RL_STATE_MULTIKEY); + return r; + } + + if (r != -3) /* magic value that says we added to the chain */ + _rl_kscxt = cxt->ocxt; + if (_rl_kscxt) + _rl_kscxt->childval = r; + if (r != -3) + _rl_keyseq_cxt_dispose (cxt); + + return r; +} +#endif /* READLINE_CALLBACKS */ + +/* Do the command associated with KEY in MAP. + If the associated command is really a keymap, then read + another key, and dispatch into that map. */ +int +_rl_dispatch (key, map) + register int key; + Keymap map; +{ + _rl_dispatching_keymap = map; + return _rl_dispatch_subseq (key, map, 0); +} + +int +_rl_dispatch_subseq (key, map, got_subseq) + register int key; + Keymap map; + int got_subseq; +{ + int r, newkey; + char *macro; + rl_command_func_t *func; +#if defined (READLINE_CALLBACKS) + _rl_keyseq_cxt *cxt; +#endif + + if (META_CHAR (key) && _rl_convert_meta_chars_to_ascii) + { + if (map[ESC].type == ISKMAP) + { + if (RL_ISSTATE (RL_STATE_MACRODEF)) + _rl_add_macro_char (ESC); + map = FUNCTION_TO_KEYMAP (map, ESC); + key = UNMETA (key); + rl_key_sequence_length += 2; + return (_rl_dispatch (key, map)); + } + else + rl_ding (); + return 0; + } + + if (RL_ISSTATE (RL_STATE_MACRODEF)) + _rl_add_macro_char (key); + + r = 0; + switch (map[key].type) + { + case ISFUNC: + func = map[key].function; + if (func) + { + /* Special case rl_do_lowercase_version (). */ + if (func == rl_do_lowercase_version) + return (_rl_dispatch (_rl_to_lower (key), map)); + + rl_executing_keymap = map; + + rl_dispatching = 1; + RL_SETSTATE(RL_STATE_DISPATCHING); + (*map[key].function)(rl_numeric_arg * rl_arg_sign, key); + RL_UNSETSTATE(RL_STATE_DISPATCHING); + rl_dispatching = 0; + + /* If we have input pending, then the last command was a prefix + command. Don't change the state of rl_last_func. Otherwise, + remember the last command executed in this variable. */ + if (rl_pending_input == 0 && map[key].function != rl_digit_argument) + rl_last_func = map[key].function; + } + else if (map[ANYOTHERKEY].function) + { + /* OK, there's no function bound in this map, but there is a + shadow function that was overridden when the current keymap + was created. Return -2 to note that. */ + _rl_unget_char (key); + return -2; + } + else if (got_subseq) + { + /* Return -1 to note that we're in a subsequence, but we don't + have a matching key, nor was one overridden. This means + we need to back up the recursion chain and find the last + subsequence that is bound to a function. */ + _rl_unget_char (key); + return -1; + } + else + { +#if defined (READLINE_CALLBACKS) + RL_UNSETSTATE (RL_STATE_MULTIKEY); + _rl_keyseq_chain_dispose (); +#endif + _rl_abort_internal (); + return -1; + } + break; + + case ISKMAP: + if (map[key].function != 0) + { +#if defined (VI_MODE) + /* The only way this test will be true is if a subsequence has been + bound starting with ESC, generally the arrow keys. What we do is + check whether there's input in the queue, which there generally + will be if an arrow key has been pressed, and, if there's not, + just dispatch to (what we assume is) rl_vi_movement_mode right + away. This is essentially an input test with a zero timeout. */ + if (rl_editing_mode == vi_mode && key == ESC && map == vi_insertion_keymap + && _rl_input_queued (0) == 0) + return (_rl_dispatch (ANYOTHERKEY, FUNCTION_TO_KEYMAP (map, key))); +#endif + + rl_key_sequence_length++; + _rl_dispatching_keymap = FUNCTION_TO_KEYMAP (map, key); + + /* Allocate new context here. Use linked contexts (linked through + cxt->ocxt) to simulate recursion */ +#if defined (READLINE_CALLBACKS) + if (RL_ISSTATE (RL_STATE_CALLBACK)) + { + /* Return 0 only the first time, to indicate success to + _rl_callback_read_char. The rest of the time, we're called + from _rl_dispatch_callback, so we return 3 to indicate + special handling is necessary. */ + r = RL_ISSTATE (RL_STATE_MULTIKEY) ? -3 : 0; + cxt = _rl_keyseq_cxt_alloc (); + + if (got_subseq) + cxt->flags |= KSEQ_SUBSEQ; + cxt->okey = key; + cxt->oldmap = map; + cxt->dmap = _rl_dispatching_keymap; + cxt->subseq_arg = got_subseq || cxt->dmap[ANYOTHERKEY].function; + + RL_SETSTATE (RL_STATE_MULTIKEY); + _rl_kscxt = cxt; + + return r; /* don't indicate immediate success */ + } +#endif + + newkey = _rl_subseq_getchar (key); + if (newkey < 0) + { + _rl_abort_internal (); + return -1; + } + + r = _rl_dispatch_subseq (newkey, _rl_dispatching_keymap, got_subseq || map[ANYOTHERKEY].function); + return _rl_subseq_result (r, map, key, got_subseq); + } + else + { + _rl_abort_internal (); + return -1; + } + break; + + case ISMACR: + if (map[key].function != 0) + { + macro = savestring ((char *)map[key].function); + _rl_with_macro_input (macro); + return 0; + } + break; + } +#if defined (VI_MODE) + if (rl_editing_mode == vi_mode && _rl_keymap == vi_movement_keymap && + key != ANYOTHERKEY && + _rl_vi_textmod_command (key)) + _rl_vi_set_last (key, rl_numeric_arg, rl_arg_sign); +#endif + + return (r); +} + +static int +_rl_subseq_result (r, map, key, got_subseq) + int r; + Keymap map; + int key, got_subseq; +{ + Keymap m; + int type, nt; + rl_command_func_t *func, *nf; + + if (r == -2) + /* We didn't match anything, and the keymap we're indexed into + shadowed a function previously bound to that prefix. Call + the function. The recursive call to _rl_dispatch_subseq has + already taken care of pushing any necessary input back onto + the input queue with _rl_unget_char. */ + { + m = _rl_dispatching_keymap; + type = m[ANYOTHERKEY].type; + func = m[ANYOTHERKEY].function; + if (type == ISFUNC && func == rl_do_lowercase_version) + r = _rl_dispatch (_rl_to_lower (key), map); + else if (type == ISFUNC && func == rl_insert) + { + /* If the function that was shadowed was self-insert, we + somehow need a keymap with map[key].func == self-insert. + Let's use this one. */ + nt = m[key].type; + nf = m[key].function; + + m[key].type = type; + m[key].function = func; + r = _rl_dispatch (key, m); + m[key].type = nt; + m[key].function = nf; + } + else + r = _rl_dispatch (ANYOTHERKEY, m); + } + else if (r && map[ANYOTHERKEY].function) + { + /* We didn't match (r is probably -1), so return something to + tell the caller that it should try ANYOTHERKEY for an + overridden function. */ + _rl_unget_char (key); + _rl_dispatching_keymap = map; + return -2; + } + else if (r && got_subseq) + { + /* OK, back up the chain. */ + _rl_unget_char (key); + _rl_dispatching_keymap = map; + return -1; + } + + return r; +} + +/* **************************************************************** */ +/* */ +/* Initializations */ +/* */ +/* **************************************************************** */ + +/* Initialize readline (and terminal if not already). */ +int +rl_initialize () +{ + /* If we have never been called before, initialize the + terminal and data structures. */ + if (!rl_initialized) + { + RL_SETSTATE(RL_STATE_INITIALIZING); + readline_initialize_everything (); + RL_UNSETSTATE(RL_STATE_INITIALIZING); + rl_initialized++; + RL_SETSTATE(RL_STATE_INITIALIZED); + } + + /* Initalize the current line information. */ + _rl_init_line_state (); + + /* We aren't done yet. We haven't even gotten started yet! */ + rl_done = 0; + RL_UNSETSTATE(RL_STATE_DONE); + + /* Tell the history routines what is going on. */ + _rl_start_using_history (); + + /* Make the display buffer match the state of the line. */ + rl_reset_line_state (); + + /* No such function typed yet. */ + rl_last_func = (rl_command_func_t *)NULL; + + /* Parsing of key-bindings begins in an enabled state. */ + _rl_parsing_conditionalized_out = 0; + +#if defined (VI_MODE) + if (rl_editing_mode == vi_mode) + _rl_vi_initialize_line (); +#endif + + /* Each line starts in insert mode (the default). */ + _rl_set_insert_mode (RL_IM_DEFAULT, 1); + + return 0; +} + +#if 0 +#if defined (__EMX__) +static void +_emx_build_environ () +{ + TIB *tibp; + PIB *pibp; + char *t, **tp; + int c; + + DosGetInfoBlocks (&tibp, &pibp); + t = pibp->pib_pchenv; + for (c = 1; *t; c++) + t += strlen (t) + 1; + tp = environ = (char **)xmalloc ((c + 1) * sizeof (char *)); + t = pibp->pib_pchenv; + while (*t) + { + *tp++ = t; + t += strlen (t) + 1; + } + *tp = 0; +} +#endif /* __EMX__ */ +#endif + +/* Initialize the entire state of the world. */ +static void +readline_initialize_everything () +{ +#if 0 +#if defined (__EMX__) + if (environ == 0) + _emx_build_environ (); +#endif +#endif + +#if 0 + /* Find out if we are running in Emacs -- UNUSED. */ + running_in_emacs = sh_get_env_value ("EMACS") != (char *)0; +#endif + + /* Set up input and output if they are not already set up. */ + if (!rl_instream) + rl_instream = stdin; + + if (!rl_outstream) + rl_outstream = stdout; + + /* Bind _rl_in_stream and _rl_out_stream immediately. These values + may change, but they may also be used before readline_internal () + is called. */ + _rl_in_stream = rl_instream; + _rl_out_stream = rl_outstream; + + /* Allocate data structures. */ + if (rl_line_buffer == 0) + rl_line_buffer = (char *)xmalloc (rl_line_buffer_len = DEFAULT_BUFFER_SIZE); + + /* Initialize the terminal interface. */ + if (rl_terminal_name == 0) + rl_terminal_name = sh_get_env_value ("TERM"); + _rl_init_terminal_io (rl_terminal_name); + + /* Bind tty characters to readline functions. */ + readline_default_bindings (); + + /* Initialize the function names. */ + rl_initialize_funmap (); + + /* Decide whether we should automatically go into eight-bit mode. */ + _rl_init_eightbit (); + + /* Read in the init file. */ + rl_read_init_file ((char *)NULL); + + /* XXX */ + if (_rl_horizontal_scroll_mode && _rl_term_autowrap) + { + _rl_screenwidth--; + _rl_screenchars -= _rl_screenheight; + } + + /* Override the effect of any `set keymap' assignments in the + inputrc file. */ + rl_set_keymap_from_edit_mode (); + + /* Try to bind a common arrow key prefix, if not already bound. */ + bind_arrow_keys (); + + /* Enable the meta key, if this terminal has one. */ + if (_rl_enable_meta) + _rl_enable_meta_key (); + + /* If the completion parser's default word break characters haven't + been set yet, then do so now. */ + if (rl_completer_word_break_characters == (char *)NULL) + rl_completer_word_break_characters = (char *)rl_basic_word_break_characters; +} + +/* If this system allows us to look at the values of the regular + input editing characters, then bind them to their readline + equivalents, iff the characters are not bound to keymaps. */ +static void +readline_default_bindings () +{ + if (_rl_bind_stty_chars) + rl_tty_set_default_bindings (_rl_keymap); +} + +/* Bind some common arrow key sequences in MAP. */ +static void +bind_arrow_keys_internal (map) + Keymap map; +{ + Keymap xkeymap; + + xkeymap = _rl_keymap; + _rl_keymap = map; + +#if defined (__MSDOS__) + rl_bind_keyseq_if_unbound ("\033[0A", rl_get_previous_history); + rl_bind_keyseq_if_unbound ("\033[0B", rl_backward_char); + rl_bind_keyseq_if_unbound ("\033[0C", rl_forward_char); + rl_bind_keyseq_if_unbound ("\033[0D", rl_get_next_history); +#endif + + rl_bind_keyseq_if_unbound ("\033[A", rl_get_previous_history); + rl_bind_keyseq_if_unbound ("\033[B", rl_get_next_history); + rl_bind_keyseq_if_unbound ("\033[C", rl_forward_char); + rl_bind_keyseq_if_unbound ("\033[D", rl_backward_char); + rl_bind_keyseq_if_unbound ("\033[H", rl_beg_of_line); + rl_bind_keyseq_if_unbound ("\033[F", rl_end_of_line); + + rl_bind_keyseq_if_unbound ("\033OA", rl_get_previous_history); + rl_bind_keyseq_if_unbound ("\033OB", rl_get_next_history); + rl_bind_keyseq_if_unbound ("\033OC", rl_forward_char); + rl_bind_keyseq_if_unbound ("\033OD", rl_backward_char); + rl_bind_keyseq_if_unbound ("\033OH", rl_beg_of_line); + rl_bind_keyseq_if_unbound ("\033OF", rl_end_of_line); + +#if defined (__MINGW32__) + rl_bind_keyseq_if_unbound ("\340H", rl_get_previous_history); + rl_bind_keyseq_if_unbound ("\340P", rl_get_next_history); + rl_bind_keyseq_if_unbound ("\340M", rl_forward_char); + rl_bind_keyseq_if_unbound ("\340K", rl_backward_char); +#endif + + _rl_keymap = xkeymap; +} + +/* Try and bind the common arrow key prefixes after giving termcap and + the inputrc file a chance to bind them and create `real' keymaps + for the arrow key prefix. */ +static void +bind_arrow_keys () +{ + bind_arrow_keys_internal (emacs_standard_keymap); + +#if defined (VI_MODE) + bind_arrow_keys_internal (vi_movement_keymap); + bind_arrow_keys_internal (vi_insertion_keymap); +#endif +} + +/* **************************************************************** */ +/* */ +/* Saving and Restoring Readline's state */ +/* */ +/* **************************************************************** */ + +int +rl_save_state (sp) + struct readline_state *sp; +{ + if (sp == 0) + return -1; + + sp->point = rl_point; + sp->end = rl_end; + sp->mark = rl_mark; + sp->buffer = rl_line_buffer; + sp->buflen = rl_line_buffer_len; + sp->ul = rl_undo_list; + sp->prompt = rl_prompt; + + sp->rlstate = rl_readline_state; + sp->done = rl_done; + sp->kmap = _rl_keymap; + + sp->lastfunc = rl_last_func; + sp->insmode = rl_insert_mode; + sp->edmode = rl_editing_mode; + sp->kseqlen = rl_key_sequence_length; + sp->inf = rl_instream; + sp->outf = rl_outstream; + sp->pendingin = rl_pending_input; + sp->macro = rl_executing_macro; + + sp->catchsigs = rl_catch_signals; + sp->catchsigwinch = rl_catch_sigwinch; + + return (0); +} + +int +rl_restore_state (sp) + struct readline_state *sp; +{ + if (sp == 0) + return -1; + + rl_point = sp->point; + rl_end = sp->end; + rl_mark = sp->mark; + the_line = rl_line_buffer = sp->buffer; + rl_line_buffer_len = sp->buflen; + rl_undo_list = sp->ul; + rl_prompt = sp->prompt; + + rl_readline_state = sp->rlstate; + rl_done = sp->done; + _rl_keymap = sp->kmap; + + rl_last_func = sp->lastfunc; + rl_insert_mode = sp->insmode; + rl_editing_mode = sp->edmode; + rl_key_sequence_length = sp->kseqlen; + rl_instream = sp->inf; + rl_outstream = sp->outf; + rl_pending_input = sp->pendingin; + rl_executing_macro = sp->macro; + + rl_catch_signals = sp->catchsigs; + rl_catch_sigwinch = sp->catchsigwinch; + + return (0); +} diff --git a/extra/readline/readline.h b/extra/readline/readline.h new file mode 100644 index 00000000000..867b2e71641 --- /dev/null +++ b/extra/readline/readline.h @@ -0,0 +1,853 @@ +/* Readline.h -- the names of functions callable from within readline. */ + +/* Copyright (C) 1987-2005 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#if !defined (_READLINE_H_) +#define _READLINE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined (READLINE_LIBRARY) +# include "rlstdc.h" +# include "rltypedefs.h" +# include "keymaps.h" +# include "tilde.h" +#else +# include <rlstdc.h> +# include <rltypedefs.h> +# include <keymaps.h> +# include <tilde.h> +#endif + +/* Hex-encoded Readline version number. */ +#define RL_READLINE_VERSION 0x0502 /* Readline 5.2 */ +#define RL_VERSION_MAJOR 5 +#define RL_VERSION_MINOR 2 + +/* Readline data structures. */ + +/* Maintaining the state of undo. We remember individual deletes and inserts + on a chain of things to do. */ + +/* The actions that undo knows how to undo. Notice that UNDO_DELETE means + to insert some text, and UNDO_INSERT means to delete some text. I.e., + the code tells undo what to undo, not how to undo it. */ +enum undo_code { UNDO_DELETE, UNDO_INSERT, UNDO_BEGIN, UNDO_END }; + +/* What an element of THE_UNDO_LIST looks like. */ +typedef struct undo_list { + struct undo_list *next; + int start, end; /* Where the change took place. */ + char *text; /* The text to insert, if undoing a delete. */ + enum undo_code what; /* Delete, Insert, Begin, End. */ +} UNDO_LIST; + +/* The current undo list for RL_LINE_BUFFER. */ +extern UNDO_LIST *rl_undo_list; + +/* The data structure for mapping textual names to code addresses. */ +typedef struct _funmap { + const char *name; + rl_command_func_t *function; +} FUNMAP; + +extern FUNMAP **funmap; + +/* **************************************************************** */ +/* */ +/* Functions available to bind to key sequences */ +/* */ +/* **************************************************************** */ + +/* Bindable commands for numeric arguments. */ +extern int rl_digit_argument PARAMS((int, int)); +extern int rl_universal_argument PARAMS((int, int)); + +/* Bindable commands for moving the cursor. */ +extern int rl_forward_byte PARAMS((int, int)); +extern int rl_forward_char PARAMS((int, int)); +extern int rl_forward PARAMS((int, int)); +extern int rl_backward_byte PARAMS((int, int)); +extern int rl_backward_char PARAMS((int, int)); +extern int rl_backward PARAMS((int, int)); +extern int rl_beg_of_line PARAMS((int, int)); +extern int rl_end_of_line PARAMS((int, int)); +extern int rl_forward_word PARAMS((int, int)); +extern int rl_backward_word PARAMS((int, int)); +extern int rl_refresh_line PARAMS((int, int)); +extern int rl_clear_screen PARAMS((int, int)); +extern int rl_arrow_keys PARAMS((int, int)); + +/* Bindable commands for inserting and deleting text. */ +extern int rl_insert PARAMS((int, int)); +extern int rl_quoted_insert PARAMS((int, int)); +extern int rl_tab_insert PARAMS((int, int)); +extern int rl_newline PARAMS((int, int)); +extern int rl_do_lowercase_version PARAMS((int, int)); +extern int rl_rubout PARAMS((int, int)); +extern int rl_delete PARAMS((int, int)); +extern int rl_rubout_or_delete PARAMS((int, int)); +extern int rl_delete_horizontal_space PARAMS((int, int)); +extern int rl_delete_or_show_completions PARAMS((int, int)); +extern int rl_insert_comment PARAMS((int, int)); + +/* Bindable commands for changing case. */ +extern int rl_upcase_word PARAMS((int, int)); +extern int rl_downcase_word PARAMS((int, int)); +extern int rl_capitalize_word PARAMS((int, int)); + +/* Bindable commands for transposing characters and words. */ +extern int rl_transpose_words PARAMS((int, int)); +extern int rl_transpose_chars PARAMS((int, int)); + +/* Bindable commands for searching within a line. */ +extern int rl_char_search PARAMS((int, int)); +extern int rl_backward_char_search PARAMS((int, int)); + +/* Bindable commands for readline's interface to the command history. */ +extern int rl_beginning_of_history PARAMS((int, int)); +extern int rl_end_of_history PARAMS((int, int)); +extern int rl_get_next_history PARAMS((int, int)); +extern int rl_get_previous_history PARAMS((int, int)); + +/* Bindable commands for managing the mark and region. */ +extern int rl_set_mark PARAMS((int, int)); +extern int rl_exchange_point_and_mark PARAMS((int, int)); + +/* Bindable commands to set the editing mode (emacs or vi). */ +extern int rl_vi_editing_mode PARAMS((int, int)); +extern int rl_emacs_editing_mode PARAMS((int, int)); + +/* Bindable commands to change the insert mode (insert or overwrite) */ +extern int rl_overwrite_mode PARAMS((int, int)); + +/* Bindable commands for managing key bindings. */ +extern int rl_re_read_init_file PARAMS((int, int)); +extern int rl_dump_functions PARAMS((int, int)); +extern int rl_dump_macros PARAMS((int, int)); +extern int rl_dump_variables PARAMS((int, int)); + +/* Bindable commands for word completion. */ +extern int rl_complete PARAMS((int, int)); +extern int rl_possible_completions PARAMS((int, int)); +extern int rl_insert_completions PARAMS((int, int)); +extern int rl_menu_complete PARAMS((int, int)); + +/* Bindable commands for killing and yanking text, and managing the kill ring. */ +extern int rl_kill_word PARAMS((int, int)); +extern int rl_backward_kill_word PARAMS((int, int)); +extern int rl_kill_line PARAMS((int, int)); +extern int rl_backward_kill_line PARAMS((int, int)); +extern int rl_kill_full_line PARAMS((int, int)); +extern int rl_unix_word_rubout PARAMS((int, int)); +extern int rl_unix_filename_rubout PARAMS((int, int)); +extern int rl_unix_line_discard PARAMS((int, int)); +extern int rl_copy_region_to_kill PARAMS((int, int)); +extern int rl_kill_region PARAMS((int, int)); +extern int rl_copy_forward_word PARAMS((int, int)); +extern int rl_copy_backward_word PARAMS((int, int)); +extern int rl_yank PARAMS((int, int)); +extern int rl_yank_pop PARAMS((int, int)); +extern int rl_yank_nth_arg PARAMS((int, int)); +extern int rl_yank_last_arg PARAMS((int, int)); +/* Not available unless __CYGWIN__ is defined. */ +#ifdef __CYGWIN__ +extern int rl_paste_from_clipboard PARAMS((int, int)); +#endif + +/* Bindable commands for incremental searching. */ +extern int rl_reverse_search_history PARAMS((int, int)); +extern int rl_forward_search_history PARAMS((int, int)); + +/* Bindable keyboard macro commands. */ +extern int rl_start_kbd_macro PARAMS((int, int)); +extern int rl_end_kbd_macro PARAMS((int, int)); +extern int rl_call_last_kbd_macro PARAMS((int, int)); + +/* Bindable undo commands. */ +extern int rl_revert_line PARAMS((int, int)); +extern int rl_undo_command PARAMS((int, int)); + +/* Bindable tilde expansion commands. */ +extern int rl_tilde_expand PARAMS((int, int)); + +/* Bindable terminal control commands. */ +extern int rl_restart_output PARAMS((int, int)); +extern int rl_stop_output PARAMS((int, int)); + +/* Miscellaneous bindable commands. */ +extern int rl_abort PARAMS((int, int)); +extern int rl_tty_status PARAMS((int, int)); + +/* Bindable commands for incremental and non-incremental history searching. */ +extern int rl_history_search_forward PARAMS((int, int)); +extern int rl_history_search_backward PARAMS((int, int)); +extern int rl_noninc_forward_search PARAMS((int, int)); +extern int rl_noninc_reverse_search PARAMS((int, int)); +extern int rl_noninc_forward_search_again PARAMS((int, int)); +extern int rl_noninc_reverse_search_again PARAMS((int, int)); + +/* Bindable command used when inserting a matching close character. */ +extern int rl_insert_close PARAMS((int, int)); + +/* Not available unless READLINE_CALLBACKS is defined. */ +extern void rl_callback_handler_install PARAMS((const char *, rl_vcpfunc_t *)); +extern void rl_callback_read_char PARAMS((void)); +extern void rl_callback_handler_remove PARAMS((void)); + +/* Things for vi mode. Not available unless readline is compiled -DVI_MODE. */ +/* VI-mode bindable commands. */ +extern int rl_vi_redo PARAMS((int, int)); +extern int rl_vi_undo PARAMS((int, int)); +extern int rl_vi_yank_arg PARAMS((int, int)); +extern int rl_vi_fetch_history PARAMS((int, int)); +extern int rl_vi_search_again PARAMS((int, int)); +extern int rl_vi_search PARAMS((int, int)); +extern int rl_vi_complete PARAMS((int, int)); +extern int rl_vi_tilde_expand PARAMS((int, int)); +extern int rl_vi_prev_word PARAMS((int, int)); +extern int rl_vi_next_word PARAMS((int, int)); +extern int rl_vi_end_word PARAMS((int, int)); +extern int rl_vi_insert_beg PARAMS((int, int)); +extern int rl_vi_append_mode PARAMS((int, int)); +extern int rl_vi_append_eol PARAMS((int, int)); +extern int rl_vi_eof_maybe PARAMS((int, int)); +extern int rl_vi_insertion_mode PARAMS((int, int)); +extern int rl_vi_movement_mode PARAMS((int, int)); +extern int rl_vi_arg_digit PARAMS((int, int)); +extern int rl_vi_change_case PARAMS((int, int)); +extern int rl_vi_put PARAMS((int, int)); +extern int rl_vi_column PARAMS((int, int)); +extern int rl_vi_delete_to PARAMS((int, int)); +extern int rl_vi_change_to PARAMS((int, int)); +extern int rl_vi_yank_to PARAMS((int, int)); +extern int rl_vi_rubout PARAMS((int, int)); +extern int rl_vi_delete PARAMS((int, int)); +extern int rl_vi_back_to_indent PARAMS((int, int)); +extern int rl_vi_first_print PARAMS((int, int)); +extern int rl_vi_char_search PARAMS((int, int)); +extern int rl_vi_match PARAMS((int, int)); +extern int rl_vi_change_char PARAMS((int, int)); +extern int rl_vi_subst PARAMS((int, int)); +extern int rl_vi_overstrike PARAMS((int, int)); +extern int rl_vi_overstrike_delete PARAMS((int, int)); +extern int rl_vi_replace PARAMS((int, int)); +extern int rl_vi_set_mark PARAMS((int, int)); +extern int rl_vi_goto_mark PARAMS((int, int)); + +/* VI-mode utility functions. */ +extern int rl_vi_check PARAMS((void)); +extern int rl_vi_domove PARAMS((int, int *)); +extern int rl_vi_bracktype PARAMS((int)); + +extern void rl_vi_start_inserting PARAMS((int, int, int)); + +/* VI-mode pseudo-bindable commands, used as utility functions. */ +extern int rl_vi_fWord PARAMS((int, int)); +extern int rl_vi_bWord PARAMS((int, int)); +extern int rl_vi_eWord PARAMS((int, int)); +extern int rl_vi_fword PARAMS((int, int)); +extern int rl_vi_bword PARAMS((int, int)); +extern int rl_vi_eword PARAMS((int, int)); + +/* **************************************************************** */ +/* */ +/* Well Published Functions */ +/* */ +/* **************************************************************** */ + +/* Readline functions. */ +/* Read a line of input. Prompt with PROMPT. A NULL PROMPT means none. */ +extern char *readline PARAMS((const char *)); + +extern int rl_set_prompt PARAMS((const char *)); +extern int rl_expand_prompt PARAMS((char *)); + +extern int rl_initialize PARAMS((void)); + +/* Undocumented; unused by readline */ +extern int rl_discard_argument PARAMS((void)); + +/* Utility functions to bind keys to readline commands. */ +extern int rl_add_defun PARAMS((const char *, rl_command_func_t *, int)); +extern int rl_bind_key PARAMS((int, rl_command_func_t *)); +extern int rl_bind_key_in_map PARAMS((int, rl_command_func_t *, Keymap)); +extern int rl_unbind_key PARAMS((int)); +extern int rl_unbind_key_in_map PARAMS((int, Keymap)); +extern int rl_bind_key_if_unbound PARAMS((int, rl_command_func_t *)); +extern int rl_bind_key_if_unbound_in_map PARAMS((int, rl_command_func_t *, Keymap)); +extern int rl_unbind_function_in_map PARAMS((rl_command_func_t *, Keymap)); +extern int rl_unbind_command_in_map PARAMS((const char *, Keymap)); +extern int rl_bind_keyseq PARAMS((const char *, rl_command_func_t *)); +extern int rl_bind_keyseq_in_map PARAMS((const char *, rl_command_func_t *, Keymap)); +extern int rl_bind_keyseq_if_unbound PARAMS((const char *, rl_command_func_t *)); +extern int rl_bind_keyseq_if_unbound_in_map PARAMS((const char *, rl_command_func_t *, Keymap)); +extern int rl_generic_bind PARAMS((int, const char *, char *, Keymap)); + +extern const char *rl_variable_value PARAMS((const char *)); +extern int rl_variable_bind PARAMS((const char *, const char *)); + +/* Backwards compatibility, use rl_bind_keyseq_in_map instead. */ +extern int rl_set_key PARAMS((const char *, rl_command_func_t *, Keymap)); + +/* Backwards compatibility, use rl_generic_bind instead. */ +extern int rl_macro_bind PARAMS((const char *, const char *, Keymap)); + +/* Undocumented in the texinfo manual; not really useful to programs. */ +extern int rl_translate_keyseq PARAMS((const char *, char *, int *)); +extern char *rl_untranslate_keyseq PARAMS((int)); + +extern rl_command_func_t *rl_named_function PARAMS((const char *)); +extern rl_command_func_t *rl_function_of_keyseq PARAMS((const char *, Keymap, int *)); + +extern void rl_list_funmap_names PARAMS((void)); +extern char **rl_invoking_keyseqs_in_map PARAMS((rl_command_func_t *, Keymap)); +extern char **rl_invoking_keyseqs PARAMS((rl_command_func_t *)); + +extern void rl_function_dumper PARAMS((int)); +extern void rl_macro_dumper PARAMS((int)); +extern void rl_variable_dumper PARAMS((int)); + +extern int rl_read_init_file PARAMS((const char *)); +extern int rl_parse_and_bind PARAMS((char *)); + +/* Functions for manipulating keymaps. */ +extern Keymap rl_make_bare_keymap PARAMS((void)); +extern Keymap rl_copy_keymap PARAMS((Keymap)); +extern Keymap rl_make_keymap PARAMS((void)); +extern void rl_discard_keymap PARAMS((Keymap)); + +extern Keymap rl_get_keymap_by_name PARAMS((const char *)); +extern char *rl_get_keymap_name PARAMS((Keymap)); +extern void rl_set_keymap PARAMS((Keymap)); +extern Keymap rl_get_keymap PARAMS((void)); +/* Undocumented; used internally only. */ +extern void rl_set_keymap_from_edit_mode PARAMS((void)); +extern const char *rl_get_keymap_name_from_edit_mode PARAMS((void)); + +/* Functions for manipulating the funmap, which maps command names to functions. */ +extern int rl_add_funmap_entry PARAMS((const char *, rl_command_func_t *)); +extern const char **rl_funmap_names PARAMS((void)); +/* Undocumented, only used internally -- there is only one funmap, and this + function may be called only once. */ +extern void rl_initialize_funmap PARAMS((void)); + +/* Utility functions for managing keyboard macros. */ +extern void rl_push_macro_input PARAMS((char *)); + +/* Functions for undoing, from undo.c */ +extern void rl_add_undo PARAMS((enum undo_code, int, int, char *)); +extern void rl_free_undo_list PARAMS((void)); +extern int rl_do_undo PARAMS((void)); +extern int rl_begin_undo_group PARAMS((void)); +extern int rl_end_undo_group PARAMS((void)); +extern int rl_modifying PARAMS((int, int)); + +/* Functions for redisplay. */ +extern void rl_redisplay PARAMS((void)); +extern int rl_on_new_line PARAMS((void)); +extern int rl_on_new_line_with_prompt PARAMS((void)); +extern int rl_forced_update_display PARAMS((void)); +extern int rl_clear_message PARAMS((void)); +extern int rl_reset_line_state PARAMS((void)); +extern int rl_crlf PARAMS((void)); + +#if defined (USE_VARARGS) && defined (PREFER_STDARG) +extern int rl_message (const char *, ...) __attribute__((__format__ (printf, 1, 2))); +#else +extern int rl_message (); +#endif + +extern int rl_show_char PARAMS((int)); + +/* Undocumented in texinfo manual. */ +extern int rl_character_len PARAMS((int, int)); + +/* Save and restore internal prompt redisplay information. */ +extern void rl_save_prompt PARAMS((void)); +extern void rl_restore_prompt PARAMS((void)); + +/* Modifying text. */ +extern void rl_replace_line PARAMS((const char *, int)); +extern int rl_insert_text PARAMS((const char *)); +extern int rl_delete_text PARAMS((int, int)); +extern int rl_kill_text PARAMS((int, int)); +extern char *rl_copy_text PARAMS((int, int)); + +/* Terminal and tty mode management. */ +extern void rl_prep_terminal PARAMS((int)); +extern void rl_deprep_terminal PARAMS((void)); +extern void rl_tty_set_default_bindings PARAMS((Keymap)); +extern void rl_tty_unset_default_bindings PARAMS((Keymap)); + +extern int rl_reset_terminal PARAMS((const char *)); +extern void rl_resize_terminal PARAMS((void)); +extern void rl_set_screen_size PARAMS((int, int)); +extern void rl_get_screen_size PARAMS((int *, int *)); +extern void rl_reset_screen_size PARAMS((void)); + +extern const char *rl_get_termcap PARAMS((const char *)); + +/* Functions for character input. */ +extern int rl_stuff_char PARAMS((int)); +extern int rl_execute_next PARAMS((int)); +extern int rl_clear_pending_input PARAMS((void)); +extern int rl_read_key PARAMS((void)); +extern int rl_getc PARAMS((FILE *)); +extern int rl_set_keyboard_input_timeout PARAMS((int)); + +/* `Public' utility functions . */ +extern void rl_extend_line_buffer PARAMS((int)); +extern int rl_ding PARAMS((void)); +extern int rl_alphabetic PARAMS((int)); + +/* Readline signal handling, from signals.c */ +extern int rl_set_signals PARAMS((void)); +extern int rl_clear_signals PARAMS((void)); +extern void rl_cleanup_after_signal PARAMS((void)); +extern void rl_reset_after_signal PARAMS((void)); +extern void rl_free_line_state PARAMS((void)); + +extern int rl_set_paren_blink_timeout PARAMS((int)); + +/* Undocumented. */ +extern int rl_maybe_save_line PARAMS((void)); +extern int rl_maybe_unsave_line PARAMS((void)); +extern int rl_maybe_replace_line PARAMS((void)); + +/* Completion functions. */ +extern int rl_complete_internal PARAMS((int)); +extern void rl_display_match_list PARAMS((char **, int, int)); + +extern char **rl_completion_matches PARAMS((const char *, rl_compentry_func_t *)); +extern char *rl_username_completion_function PARAMS((const char *, int)); +extern char *rl_filename_completion_function PARAMS((const char *, int)); + +extern int rl_completion_mode PARAMS((rl_command_func_t *)); + +#if 0 +/* Backwards compatibility (compat.c). These will go away sometime. */ +extern void free_undo_list PARAMS((void)); +extern int maybe_save_line PARAMS((void)); +extern int maybe_unsave_line PARAMS((void)); +extern int maybe_replace_line PARAMS((void)); + +extern int ding PARAMS((void)); +extern int alphabetic PARAMS((int)); +extern int crlf PARAMS((void)); + +extern char **completion_matches PARAMS((char *, rl_compentry_func_t *)); +extern char *username_completion_function PARAMS((const char *, int)); +extern char *filename_completion_function PARAMS((const char *, int)); +#endif + +/* **************************************************************** */ +/* */ +/* Well Published Variables */ +/* */ +/* **************************************************************** */ + +/* The version of this incarnation of the readline library. */ +extern const char *rl_library_version; /* e.g., "4.2" */ +extern int rl_readline_version; /* e.g., 0x0402 */ + +/* True if this is real GNU readline. */ +extern int rl_gnu_readline_p; + +/* Flags word encapsulating the current readline state. */ +extern int rl_readline_state; + +/* Says which editing mode readline is currently using. 1 means emacs mode; + 0 means vi mode. */ +extern int rl_editing_mode; + +/* Insert or overwrite mode for emacs mode. 1 means insert mode; 0 means + overwrite mode. Reset to insert mode on each input line. */ +extern int rl_insert_mode; + +/* The name of the calling program. You should initialize this to + whatever was in argv[0]. It is used when parsing conditionals. */ +extern const char *rl_readline_name; + +/* The prompt readline uses. This is set from the argument to + readline (), and should not be assigned to directly. */ +extern char *rl_prompt; + +/* The line buffer that is in use. */ +extern char *rl_line_buffer; + +/* The location of point, and end. */ +extern int rl_point; +extern int rl_end; + +/* The mark, or saved cursor position. */ +extern int rl_mark; + +/* Flag to indicate that readline has finished with the current input + line and should return it. */ +extern int rl_done; + +/* If set to a character value, that will be the next keystroke read. */ +extern int rl_pending_input; + +/* Non-zero if we called this function from _rl_dispatch(). It's present + so functions can find out whether they were called from a key binding + or directly from an application. */ +extern int rl_dispatching; + +/* Non-zero if the user typed a numeric argument before executing the + current function. */ +extern int rl_explicit_arg; + +/* The current value of the numeric argument specified by the user. */ +extern int rl_numeric_arg; + +/* The address of the last command function Readline executed. */ +extern rl_command_func_t *rl_last_func; + +/* The name of the terminal to use. */ +extern const char *rl_terminal_name; + +/* The input and output streams. */ +extern FILE *rl_instream; +extern FILE *rl_outstream; + +/* If non-zero, Readline gives values of LINES and COLUMNS from the environment + greater precedence than values fetched from the kernel when computing the + screen dimensions. */ +extern int rl_prefer_env_winsize; + +/* If non-zero, then this is the address of a function to call just + before readline_internal () prints the first prompt. */ +extern rl_hook_func_t *rl_startup_hook; + +/* If non-zero, this is the address of a function to call just before + readline_internal_setup () returns and readline_internal starts + reading input characters. */ +extern rl_hook_func_t *rl_pre_input_hook; + +/* The address of a function to call periodically while Readline is + awaiting character input, or NULL, for no event handling. */ +extern rl_hook_func_t *rl_event_hook; + +/* The address of the function to call to fetch a character from the current + Readline input stream */ +extern rl_getc_func_t *rl_getc_function; + +extern rl_voidfunc_t *rl_redisplay_function; + +extern rl_vintfunc_t *rl_prep_term_function; +extern rl_voidfunc_t *rl_deprep_term_function; + +/* Dispatch variables. */ +extern Keymap rl_executing_keymap; +extern Keymap rl_binding_keymap; + +/* Display variables. */ +/* If non-zero, readline will erase the entire line, including any prompt, + if the only thing typed on an otherwise-blank line is something bound to + rl_newline. */ +extern int rl_erase_empty_line; + +/* If non-zero, the application has already printed the prompt (rl_prompt) + before calling readline, so readline should not output it the first time + redisplay is done. */ +extern int rl_already_prompted; + +/* A non-zero value means to read only this many characters rather than + up to a character bound to accept-line. */ +extern int rl_num_chars_to_read; + +/* The text of a currently-executing keyboard macro. */ +extern char *rl_executing_macro; + +/* Variables to control readline signal handling. */ +/* If non-zero, readline will install its own signal handlers for + SIGINT, SIGTERM, SIGQUIT, SIGALRM, SIGTSTP, SIGTTIN, and SIGTTOU. */ +extern int rl_catch_signals; + +/* If non-zero, readline will install a signal handler for SIGWINCH + that also attempts to call any calling application's SIGWINCH signal + handler. Note that the terminal is not cleaned up before the + application's signal handler is called; use rl_cleanup_after_signal() + to do that. */ +extern int rl_catch_sigwinch; + +/* Completion variables. */ +/* Pointer to the generator function for completion_matches (). + NULL means to use rl_filename_completion_function (), the default + filename completer. */ +extern rl_compentry_func_t *rl_completion_entry_function; + +/* If rl_ignore_some_completions_function is non-NULL it is the address + of a function to call after all of the possible matches have been + generated, but before the actual completion is done to the input line. + The function is called with one argument; a NULL terminated array + of (char *). If your function removes any of the elements, they + must be free()'ed. */ +extern rl_compignore_func_t *rl_ignore_some_completions_function; + +/* Pointer to alternative function to create matches. + Function is called with TEXT, START, and END. + START and END are indices in RL_LINE_BUFFER saying what the boundaries + of TEXT are. + If this function exists and returns NULL then call the value of + rl_completion_entry_function to try to match, otherwise use the + array of strings returned. */ +extern rl_completion_func_t *rl_attempted_completion_function; + +/* The basic list of characters that signal a break between words for the + completer routine. The initial contents of this variable is what + breaks words in the shell, i.e. "n\"\\'`@$>". */ +extern const char *rl_basic_word_break_characters; + +/* The list of characters that signal a break between words for + rl_complete_internal. The default list is the contents of + rl_basic_word_break_characters. */ +extern /*const*/ char *rl_completer_word_break_characters; + +/* Hook function to allow an application to set the completion word + break characters before readline breaks up the line. Allows + position-dependent word break characters. */ +extern rl_cpvfunc_t *rl_completion_word_break_hook; + +/* List of characters which can be used to quote a substring of the line. + Completion occurs on the entire substring, and within the substring + rl_completer_word_break_characters are treated as any other character, + unless they also appear within this list. */ +extern const char *rl_completer_quote_characters; + +/* List of quote characters which cause a word break. */ +extern const char *rl_basic_quote_characters; + +/* List of characters that need to be quoted in filenames by the completer. */ +extern const char *rl_filename_quote_characters; + +/* List of characters that are word break characters, but should be left + in TEXT when it is passed to the completion function. The shell uses + this to help determine what kind of completing to do. */ +extern const char *rl_special_prefixes; + +/* If non-zero, then this is the address of a function to call when + completing on a directory name. The function is called with + the address of a string (the current directory name) as an arg. It + changes what is displayed when the possible completions are printed + or inserted. */ +extern rl_icppfunc_t *rl_directory_completion_hook; + +/* If non-zero, this is the address of a function to call when completing + a directory name. This function takes the address of the directory name + to be modified as an argument. Unlike rl_directory_completion_hook, it + only modifies the directory name used in opendir(2), not what is displayed + when the possible completions are printed or inserted. It is called + before rl_directory_completion_hook. I'm not happy with how this works + yet, so it's undocumented. */ +extern rl_icppfunc_t *rl_directory_rewrite_hook; + +/* Backwards compatibility with previous versions of readline. */ +#define rl_symbolic_link_hook rl_directory_completion_hook + +/* If non-zero, then this is the address of a function to call when + completing a word would normally display the list of possible matches. + This function is called instead of actually doing the display. + It takes three arguments: (char **matches, int num_matches, int max_length) + where MATCHES is the array of strings that matched, NUM_MATCHES is the + number of strings in that array, and MAX_LENGTH is the length of the + longest string in that array. */ +extern rl_compdisp_func_t *rl_completion_display_matches_hook; + +/* Non-zero means that the results of the matches are to be treated + as filenames. This is ALWAYS zero on entry, and can only be changed + within a completion entry finder function. */ +extern int rl_filename_completion_desired; + +/* Non-zero means that the results of the matches are to be quoted using + double quotes (or an application-specific quoting mechanism) if the + filename contains any characters in rl_word_break_chars. This is + ALWAYS non-zero on entry, and can only be changed within a completion + entry finder function. */ +extern int rl_filename_quoting_desired; + +/* Set to a function to quote a filename in an application-specific fashion. + Called with the text to quote, the type of match found (single or multiple) + and a pointer to the quoting character to be used, which the function can + reset if desired. */ +extern rl_quote_func_t *rl_filename_quoting_function; + +/* Function to call to remove quoting characters from a filename. Called + before completion is attempted, so the embedded quotes do not interfere + with matching names in the file system. */ +extern rl_dequote_func_t *rl_filename_dequoting_function; + +/* Function to call to decide whether or not a word break character is + quoted. If a character is quoted, it does not break words for the + completer. */ +extern rl_linebuf_func_t *rl_char_is_quoted_p; + +/* Non-zero means to suppress normal filename completion after the + user-specified completion function has been called. */ +extern int rl_attempted_completion_over; + +/* Set to a character describing the type of completion being attempted by + rl_complete_internal; available for use by application completion + functions. */ +extern int rl_completion_type; + +/* Up to this many items will be displayed in response to a + possible-completions call. After that, we ask the user if she + is sure she wants to see them all. The default value is 100. */ +extern int rl_completion_query_items; + +/* Character appended to completed words when at the end of the line. The + default is a space. Nothing is added if this is '\0'. */ +extern int rl_completion_append_character; + +/* If set to non-zero by an application completion function, + rl_completion_append_character will not be appended. */ +extern int rl_completion_suppress_append; + +/* Set to any quote character readline thinks it finds before any application + completion function is called. */ +extern int rl_completion_quote_character; + +/* Set to a non-zero value if readline found quoting anywhere in the word to + be completed; set before any application completion function is called. */ +extern int rl_completion_found_quote; + +/* If non-zero, the completion functions don't append any closing quote. + This is set to 0 by rl_complete_internal and may be changed by an + application-specific completion function. */ +extern int rl_completion_suppress_quote; + +/* If non-zero, a slash will be appended to completed filenames that are + symbolic links to directory names, subject to the value of the + mark-directories variable (which is user-settable). This exists so + that application completion functions can override the user's preference + (set via the mark-symlinked-directories variable) if appropriate. + It's set to the value of _rl_complete_mark_symlink_dirs in + rl_complete_internal before any application-specific completion + function is called, so without that function doing anything, the user's + preferences are honored. */ +extern int rl_completion_mark_symlink_dirs; + +/* If non-zero, then disallow duplicates in the matches. */ +extern int rl_ignore_completion_duplicates; + +/* If this is non-zero, completion is (temporarily) inhibited, and the + completion character will be inserted as any other. */ +extern int rl_inhibit_completion; + +/* Input error; can be returned by (*rl_getc_function) if readline is reading + a top-level command (RL_ISSTATE (RL_STATE_READCMD)). */ +#define READERR (-2) + +/* Definitions available for use by readline clients. */ +#define RL_PROMPT_START_IGNORE '\001' +#define RL_PROMPT_END_IGNORE '\002' + +/* Possible values for do_replace argument to rl_filename_quoting_function, + called by rl_complete_internal. */ +#define NO_MATCH 0 +#define SINGLE_MATCH 1 +#define MULT_MATCH 2 + +/* Possible state values for rl_readline_state */ +#define RL_STATE_NONE 0x000000 /* no state; before first call */ + +#define RL_STATE_INITIALIZING 0x000001 /* initializing */ +#define RL_STATE_INITIALIZED 0x000002 /* initialization done */ +#define RL_STATE_TERMPREPPED 0x000004 /* terminal is prepped */ +#define RL_STATE_READCMD 0x000008 /* reading a command key */ +#define RL_STATE_METANEXT 0x000010 /* reading input after ESC */ +#define RL_STATE_DISPATCHING 0x000020 /* dispatching to a command */ +#define RL_STATE_MOREINPUT 0x000040 /* reading more input in a command function */ +#define RL_STATE_ISEARCH 0x000080 /* doing incremental search */ +#define RL_STATE_NSEARCH 0x000100 /* doing non-inc search */ +#define RL_STATE_SEARCH 0x000200 /* doing a history search */ +#define RL_STATE_NUMERICARG 0x000400 /* reading numeric argument */ +#define RL_STATE_MACROINPUT 0x000800 /* getting input from a macro */ +#define RL_STATE_MACRODEF 0x001000 /* defining keyboard macro */ +#define RL_STATE_OVERWRITE 0x002000 /* overwrite mode */ +#define RL_STATE_COMPLETING 0x004000 /* doing completion */ +#define RL_STATE_SIGHANDLER 0x008000 /* in readline sighandler */ +#define RL_STATE_UNDOING 0x010000 /* doing an undo */ +#define RL_STATE_INPUTPENDING 0x020000 /* rl_execute_next called */ +#define RL_STATE_TTYCSAVED 0x040000 /* tty special chars saved */ +#define RL_STATE_CALLBACK 0x080000 /* using the callback interface */ +#define RL_STATE_VIMOTION 0x100000 /* reading vi motion arg */ +#define RL_STATE_MULTIKEY 0x200000 /* reading multiple-key command */ +#define RL_STATE_VICMDONCE 0x400000 /* entered vi command mode at least once */ + +#define RL_STATE_DONE 0x800000 /* done; accepted line */ + +#define RL_SETSTATE(x) (rl_readline_state |= (x)) +#define RL_UNSETSTATE(x) (rl_readline_state &= ~(x)) +#define RL_ISSTATE(x) (rl_readline_state & (x)) + +struct readline_state { + /* line state */ + int point; + int end; + int mark; + char *buffer; + int buflen; + UNDO_LIST *ul; + char *prompt; + + /* global state */ + int rlstate; + int done; + Keymap kmap; + + /* input state */ + rl_command_func_t *lastfunc; + int insmode; + int edmode; + int kseqlen; + FILE *inf; + FILE *outf; + int pendingin; + char *macro; + + /* signal state */ + int catchsigs; + int catchsigwinch; + + /* search state */ + + /* completion state */ + + /* options state */ + + /* reserved for future expansion, so the struct size doesn't change */ + char reserved[64]; +}; + +extern int rl_save_state PARAMS((struct readline_state *)); +extern int rl_restore_state PARAMS((struct readline_state *)); + +#ifdef __cplusplus +} +#endif + +#endif /* _READLINE_H_ */ diff --git a/extra/readline/rlconf.h b/extra/readline/rlconf.h new file mode 100644 index 00000000000..4aacbb9e7f4 --- /dev/null +++ b/extra/readline/rlconf.h @@ -0,0 +1,63 @@ +/* rlconf.h -- readline configuration definitions */ + +/* Copyright (C) 1994 Free Software Foundation, Inc. + + This file contains the Readline Library (the Library), a set of + routines for providing Emacs style line input to programs that ask + for it. + + The Library is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + The 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 + General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#if !defined (_RLCONF_H_) +#define _RLCONF_H_ + +/* Define this if you want the vi-mode editing available. */ +#define VI_MODE + +/* Define this to get an indication of file type when listing completions. */ +#define VISIBLE_STATS + +/* This definition is needed by readline.c, rltty.c, and signals.c. */ +/* If on, then readline handles signals in a way that doesn't screw. */ +#define HANDLE_SIGNALS + +/* Ugly but working hack for binding prefix meta. */ +#define PREFIX_META_HACK + +/* The next-to-last-ditch effort file name for a user-specific init file. */ +#define DEFAULT_INPUTRC "~/.inputrc" + +/* The ultimate last-ditch filenname for an init file -- system-wide. */ +#define SYS_INPUTRC "/etc/inputrc" + +/* If defined, expand tabs to spaces. */ +#define DISPLAY_TABS + +/* If defined, use the terminal escape sequence to move the cursor forward + over a character when updating the line rather than rewriting it. */ +/* #define HACK_TERMCAP_MOTION */ + +/* The string inserted by the `insert comment' command. */ +#define RL_COMMENT_BEGIN_DEFAULT "#" + +/* Define this if you want code that allows readline to be used in an + X `callback' style. */ +#define READLINE_CALLBACKS + +/* Define this if you want the cursor to indicate insert or overwrite mode. */ +/* #define CURSOR_MODE */ + +#endif /* _RLCONF_H_ */ diff --git a/extra/readline/rldefs.h b/extra/readline/rldefs.h new file mode 100644 index 00000000000..732bd54baa9 --- /dev/null +++ b/extra/readline/rldefs.h @@ -0,0 +1,160 @@ +/* rldefs.h -- an attempt to isolate some of the system-specific defines + for readline. This should be included after any files that define + system-specific constants like _POSIX_VERSION or USG. */ + +/* Copyright (C) 1987-2005 Free Software Foundation, Inc. + + This file contains the Readline Library (the Library), a set of + routines for providing Emacs style line input to programs that ask + for it. + + The Library is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + The 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 + General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#if !defined (_RLDEFS_H_) +#define _RLDEFS_H_ + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include "rlstdc.h" + +#if defined (_POSIX_VERSION) && !defined (TERMIOS_MISSING) +# define TERMIOS_TTY_DRIVER +#else +# if defined (HAVE_TERMIO_H) +# define TERMIO_TTY_DRIVER +# else +# if !defined (__MINGW32__) +# define NEW_TTY_DRIVER +# else +# define NO_TTY_DRIVER +# endif +# endif +#endif + +/* Posix macro to check file in statbuf for directory-ness. + This requires that <sys/stat.h> be included before this test. */ +#if defined (S_IFDIR) && !defined (S_ISDIR) +# define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR) +#endif + +/* Decide which flavor of the header file describing the C library + string functions to include and include it. */ + +#if defined (HAVE_STRING_H) +# include <string.h> +#else /* !HAVE_STRING_H */ +# include <strings.h> +#endif /* !HAVE_STRING_H */ + +#if !defined (strchr) && !defined (__STDC__) +extern char *strchr (), *strrchr (); +#endif /* !strchr && !__STDC__ */ + +#if defined (PREFER_STDARG) +# include <stdarg.h> +#else +# if defined (PREFER_VARARGS) +# include <varargs.h> +# endif +#endif + +#if defined (HAVE_STRCASECMP) +#define _rl_stricmp strcasecmp +#define _rl_strnicmp strncasecmp +#else +extern int _rl_stricmp PARAMS((char *, char *)); +extern int _rl_strnicmp PARAMS((char *, char *, int)); +#endif + +#if defined (HAVE_STRPBRK) && !defined (HAVE_MULTIBYTE) +# define _rl_strpbrk(a,b) strpbrk((a),(b)) +#else +extern char *_rl_strpbrk PARAMS((const char *, const char *)); +#endif + +#if !defined (emacs_mode) +# define no_mode -1 +# define vi_mode 0 +# define emacs_mode 1 +#endif + +#if !defined (RL_IM_INSERT) +# define RL_IM_INSERT 1 +# define RL_IM_OVERWRITE 0 +# +# define RL_IM_DEFAULT RL_IM_INSERT +#endif + +/* If you cast map[key].function to type (Keymap) on a Cray, + the compiler takes the value of map[key].function and + divides it by 4 to convert between pointer types (pointers + to functions and pointers to structs are different sizes). + This is not what is wanted. */ +#if defined (CRAY) +# define FUNCTION_TO_KEYMAP(map, key) (Keymap)((int)map[key].function) +# define KEYMAP_TO_FUNCTION(data) (rl_command_func_t *)((int)(data)) +#else +# define FUNCTION_TO_KEYMAP(map, key) (Keymap)(map[key].function) +# define KEYMAP_TO_FUNCTION(data) (rl_command_func_t *)(data) +#endif + +#ifndef savestring +#define savestring(x) strcpy ((char *)xmalloc (1 + strlen (x)), (x)) +#endif + +/* Possible values for _rl_bell_preference. */ +#define NO_BELL 0 +#define AUDIBLE_BELL 1 +#define VISIBLE_BELL 2 + +/* Definitions used when searching the line for characters. */ +/* NOTE: it is necessary that opposite directions are inverses */ +#define FTO 1 /* forward to */ +#define BTO -1 /* backward to */ +#define FFIND 2 /* forward find */ +#define BFIND -2 /* backward find */ + +/* Possible values for the found_quote flags word used by the completion + functions. It says what kind of (shell-like) quoting we found anywhere + in the line. */ +#define RL_QF_SINGLE_QUOTE 0x01 +#define RL_QF_DOUBLE_QUOTE 0x02 +#define RL_QF_BACKSLASH 0x04 +#define RL_QF_OTHER_QUOTE 0x08 + +/* Default readline line buffer length. */ +#define DEFAULT_BUFFER_SIZE 256 + +#if !defined (STREQ) +#define STREQ(a, b) (((a)[0] == (b)[0]) && (strcmp ((a), (b)) == 0)) +#define STREQN(a, b, n) (((n) == 0) ? (1) \ + : ((a)[0] == (b)[0]) && (strncmp ((a), (b), (n)) == 0)) +#endif + +#if !defined (FREE) +# define FREE(x) if (x) free (x) +#endif + +#if !defined (SWAP) +# define SWAP(s, e) do { int t; t = s; s = e; e = t; } while (0) +#endif + +/* CONFIGURATION SECTION */ +#include "rlconf.h" + +#endif /* !_RLDEFS_H_ */ diff --git a/extra/readline/rlmbutil.h b/extra/readline/rlmbutil.h new file mode 100644 index 00000000000..63c4055f9f4 --- /dev/null +++ b/extra/readline/rlmbutil.h @@ -0,0 +1,155 @@ +/* rlmbutil.h -- utility functions for multibyte characters. */ + +/* Copyright (C) 2001 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#if !defined (_RL_MBUTIL_H_) +#define _RL_MBUTIL_H_ + +#include "rlstdc.h" + +/************************************************/ +/* check multibyte capability for I18N code */ +/************************************************/ + +/* For platforms which support the ISO C amendement 1 functionality we + support user defined character classes. */ + /* Solaris 2.5 has a bug: <wchar.h> must be included before <wctype.h>. */ +#if defined (HAVE_WCTYPE_H) && defined (HAVE_WCHAR_H) && defined (HAVE_LOCALE_H) +# include <wchar.h> +# include <wctype.h> +# if defined (HAVE_ISWCTYPE) && \ + defined (HAVE_ISWLOWER) && \ + defined (HAVE_ISWUPPER) && \ + defined (HAVE_MBSRTOWCS) && \ + defined (HAVE_MBRTOWC) && \ + defined (HAVE_MBRLEN) && \ + defined (HAVE_TOWLOWER) && \ + defined (HAVE_TOWUPPER) && \ + defined (HAVE_WCHAR_T) && \ + defined (HAVE_WCWIDTH) + /* system is supposed to support XPG5 */ +# define HANDLE_MULTIBYTE 1 +# endif +#endif + +/* If we don't want multibyte chars even on a system that supports them, let + the configuring user turn multibyte support off. */ +#if defined (NO_MULTIBYTE_SUPPORT) +# undef HANDLE_MULTIBYTE +#endif + +/* Some systems, like BeOS, have multibyte encodings but lack mbstate_t. */ +#if HANDLE_MULTIBYTE && !defined (HAVE_MBSTATE_T) +# define wcsrtombs(dest, src, len, ps) (wcsrtombs) (dest, src, len, 0) +# define mbsrtowcs(dest, src, len, ps) (mbsrtowcs) (dest, src, len, 0) +# define wcrtomb(s, wc, ps) (wcrtomb) (s, wc, 0) +# define mbrtowc(pwc, s, n, ps) (mbrtowc) (pwc, s, n, 0) +# define mbrlen(s, n, ps) (mbrlen) (s, n, 0) +# define mbstate_t int +#endif + +/* Make sure MB_LEN_MAX is at least 16 on systems that claim to be able to + handle multibyte chars (some systems define MB_LEN_MAX as 1) */ +#ifdef HANDLE_MULTIBYTE +# include <limits.h> +# if defined(MB_LEN_MAX) && (MB_LEN_MAX < 16) +# undef MB_LEN_MAX +# endif +# if !defined (MB_LEN_MAX) +# define MB_LEN_MAX 16 +# endif +#endif + +/************************************************/ +/* end of multibyte capability checks for I18N */ +/************************************************/ + +/* + * Flags for _rl_find_prev_mbchar and _rl_find_next_mbchar: + * + * MB_FIND_ANY find any multibyte character + * MB_FIND_NONZERO find a non-zero-width multibyte character + */ + +#define MB_FIND_ANY 0x00 +#define MB_FIND_NONZERO 0x01 + +extern int _rl_find_prev_mbchar PARAMS((char *, int, int)); +extern int _rl_find_next_mbchar PARAMS((char *, int, int, int)); + +#ifdef HANDLE_MULTIBYTE + +extern int _rl_compare_chars PARAMS((char *, int, mbstate_t *, char *, int, mbstate_t *)); +extern int _rl_get_char_len PARAMS((char *, mbstate_t *)); +extern int _rl_adjust_point PARAMS((char *, int, mbstate_t *)); + +extern int _rl_read_mbchar PARAMS((char *, int)); +extern int _rl_read_mbstring PARAMS((int, char *, int)); + +extern int _rl_is_mbchar_matched PARAMS((char *, int, int, char *, int)); + +extern wchar_t _rl_char_value PARAMS((char *, int)); +extern int _rl_walphabetic PARAMS((wchar_t)); + +#define _rl_to_wupper(wc) (iswlower (wc) ? (wchar_t)towupper (wc) : (wc)) +#define _rl_to_wlower(wc) (iswupper (wc) ? (wchar_t)towlower (wc) : (wc)) + +#define MB_NEXTCHAR(b,s,c,f) \ + ((MB_CUR_MAX > 1 && rl_byte_oriented == 0) \ + ? _rl_find_next_mbchar ((b), (s), (c), (f)) \ + : ((s) + (c))) +#define MB_PREVCHAR(b,s,f) \ + ((MB_CUR_MAX > 1 && rl_byte_oriented == 0) \ + ? _rl_find_prev_mbchar ((b), (s), (f)) \ + : ((s) - 1)) + +#define MB_INVALIDCH(x) ((x) == (size_t)-1 || (x) == (size_t)-2) +#define MB_NULLWCH(x) ((x) == 0) + +#else /* !HANDLE_MULTIBYTE */ + +#undef MB_LEN_MAX +#undef MB_CUR_MAX + +#define MB_LEN_MAX 1 +#define MB_CUR_MAX 1 + +#define _rl_find_prev_mbchar(b, i, f) (((i) == 0) ? (i) : ((i) - 1)) +#define _rl_find_next_mbchar(b, i1, i2, f) ((i1) + (i2)) + +#define _rl_char_value(buf,ind) ((buf)[(ind)]) + +#define _rl_walphabetic(c) (rl_alphabetic (c)) + +#define _rl_to_wupper(c) (_rl_to_upper (c)) +#define _rl_to_wlower(c) (_rl_to_lower (c)) + +#define MB_NEXTCHAR(b,s,c,f) ((s) + (c)) +#define MB_PREVCHAR(b,s,f) ((s) - 1) + +#define MB_INVALIDCH(x) (0) +#define MB_NULLWCH(x) (0) + +#endif /* !HANDLE_MULTIBYTE */ + +extern int rl_byte_oriented; + +#endif /* _RL_MBUTIL_H_ */ diff --git a/extra/readline/rlprivate.h b/extra/readline/rlprivate.h new file mode 100644 index 00000000000..a693bd988b6 --- /dev/null +++ b/extra/readline/rlprivate.h @@ -0,0 +1,425 @@ +/* rlprivate.h -- functions and variables global to the readline library, + but not intended for use by applications. */ + +/* Copyright (C) 1999-2005 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#if !defined (_RL_PRIVATE_H_) +#define _RL_PRIVATE_H_ + +#include "rlconf.h" /* for VISIBLE_STATS */ +#include "rlstdc.h" +#include "posixjmp.h" /* defines procenv_t */ + +/************************************************************************* + * * + * Global structs undocumented in texinfo manual and not in readline.h * + * * + *************************************************************************/ +/* search types */ +#define RL_SEARCH_ISEARCH 0x01 /* incremental search */ +#define RL_SEARCH_NSEARCH 0x02 /* non-incremental search */ +#define RL_SEARCH_CSEARCH 0x04 /* intra-line char search */ + +/* search flags */ +#define SF_REVERSE 0x01 +#define SF_FOUND 0x02 +#define SF_FAILED 0x04 + +typedef struct __rl_search_context +{ + int type; + int sflags; + + char *search_string; + int search_string_index; + int search_string_size; + + char **lines; + char *allocated_line; + int hlen; + int hindex; + + int save_point; + int save_mark; + int save_line; + int last_found_line; + char *prev_line_found; + + UNDO_LIST *save_undo_list; + + int history_pos; + int direction; + + int lastc; +#if defined (HANDLE_MULTIBYTE) + char mb[MB_LEN_MAX]; +#endif + + char *sline; + int sline_len; + int sline_index; + + const char *search_terminators; +} _rl_search_cxt; + +/* Callback data for reading numeric arguments */ +#define NUM_SAWMINUS 0x01 +#define NUM_SAWDIGITS 0x02 +#define NUM_READONE 0x04 + +typedef int _rl_arg_cxt; + +/* A context for reading key sequences longer than a single character when + using the callback interface. */ +#define KSEQ_DISPATCHED 0x01 +#define KSEQ_SUBSEQ 0x02 +#define KSEQ_RECURSIVE 0x04 + +typedef struct __rl_keyseq_context +{ + int flags; + int subseq_arg; + int subseq_retval; /* XXX */ + Keymap dmap; + + Keymap oldmap; + int okey; + struct __rl_keyseq_context *ocxt; + int childval; +} _rl_keyseq_cxt; + + /* fill in more as needed */ +/* `Generic' callback data and functions */ +typedef struct __rl_callback_generic_arg +{ + int count; + int i1, i2; + /* add here as needed */ +} _rl_callback_generic_arg; + +typedef int _rl_callback_func_t PARAMS((_rl_callback_generic_arg *)); + +/************************************************************************* + * * + * Global functions undocumented in texinfo manual and not in readline.h * + * * + *************************************************************************/ + +/************************************************************************* + * * + * Global variables undocumented in texinfo manual and not in readline.h * + * * + *************************************************************************/ + +/* complete.c */ +extern int rl_complete_with_tilde_expansion; +#if defined (VISIBLE_STATS) +extern int rl_visible_stats; +#endif /* VISIBLE_STATS */ + +/* readline.c */ +extern int rl_line_buffer_len; +extern int rl_arg_sign; +extern int rl_visible_prompt_length; +extern int readline_echoing_p; +extern int rl_key_sequence_length; +extern int rl_byte_oriented; + +extern _rl_keyseq_cxt *_rl_kscxt; + +/* display.c */ +extern int rl_display_fixed; + +/* parens.c */ +extern int rl_blink_matching_paren; + +/************************************************************************* + * * + * Global functions and variables unsed and undocumented * + * * + *************************************************************************/ + +/* kill.c */ +extern int rl_set_retained_kills PARAMS((int)); + +/* terminal.c */ +extern void _rl_set_screen_size PARAMS((int, int)); + +/* undo.c */ +extern int _rl_fix_last_undo_of_type PARAMS((enum undo_code, int, int)); + +/* util.c */ +extern char *_rl_savestring PARAMS((const char *)); + +/************************************************************************* + * * + * Functions and variables private to the readline library * + * * + *************************************************************************/ + +/* NOTE: Functions and variables prefixed with `_rl_' are + pseudo-global: they are global so they can be shared + between files in the readline library, but are not intended + to be visible to readline callers. */ + +/************************************************************************* + * Undocumented private functions * + *************************************************************************/ + +#if defined(READLINE_CALLBACKS) + +/* readline.c */ +extern void readline_internal_setup PARAMS((void)); +extern char *readline_internal_teardown PARAMS((int)); +extern int readline_internal_char PARAMS((void)); + +extern _rl_keyseq_cxt *_rl_keyseq_cxt_alloc PARAMS((void)); +extern void _rl_keyseq_cxt_dispose PARAMS((_rl_keyseq_cxt *)); +extern void _rl_keyseq_chain_dispose PARAMS((void)); + +extern int _rl_dispatch_callback PARAMS((_rl_keyseq_cxt *)); + +/* callback.c */ +extern _rl_callback_generic_arg *_rl_callback_data_alloc PARAMS((int)); +extern void _rl_callback_data_dispose PARAMS((_rl_callback_generic_arg *)); + +#endif /* READLINE_CALLBACKS */ + +/* bind.c */ + +/* complete.c */ +extern char _rl_find_completion_word PARAMS((int *, int *)); +extern void _rl_free_match_list PARAMS((char **)); + +/* display.c */ +extern char *_rl_strip_prompt PARAMS((char *)); +extern void _rl_move_cursor_relative PARAMS((int, const char *)); +extern void _rl_move_vert PARAMS((int)); +extern void _rl_save_prompt PARAMS((void)); +extern void _rl_restore_prompt PARAMS((void)); +extern char *_rl_make_prompt_for_search PARAMS((int)); +extern void _rl_erase_at_end_of_line PARAMS((int)); +extern void _rl_clear_to_eol PARAMS((int)); +extern void _rl_clear_screen PARAMS((void)); +extern void _rl_update_final PARAMS((void)); +extern void _rl_redisplay_after_sigwinch PARAMS((void)); +extern void _rl_clean_up_for_exit PARAMS((void)); +extern void _rl_erase_entire_line PARAMS((void)); +extern int _rl_current_display_line PARAMS((void)); + +/* input.c */ +extern int _rl_any_typein PARAMS((void)); +extern int _rl_input_available PARAMS((void)); +extern int _rl_input_queued PARAMS((int)); +extern void _rl_insert_typein PARAMS((int)); +extern int _rl_unget_char PARAMS((int)); +extern int _rl_pushed_input_available PARAMS((void)); + +/* isearch.c */ +extern _rl_search_cxt *_rl_scxt_alloc PARAMS((int, int)); +extern void _rl_scxt_dispose PARAMS((_rl_search_cxt *, int)); + +extern int _rl_isearch_dispatch PARAMS((_rl_search_cxt *, int)); +extern int _rl_isearch_callback PARAMS((_rl_search_cxt *)); + +extern int _rl_search_getchar PARAMS((_rl_search_cxt *)); + +/* macro.c */ +extern void _rl_with_macro_input PARAMS((char *)); +extern int _rl_next_macro_key PARAMS((void)); +extern void _rl_push_executing_macro PARAMS((void)); +extern void _rl_pop_executing_macro PARAMS((void)); +extern void _rl_add_macro_char PARAMS((int)); +extern void _rl_kill_kbd_macro PARAMS((void)); + +/* misc.c */ +extern int _rl_arg_overflow PARAMS((void)); +extern void _rl_arg_init PARAMS((void)); +extern int _rl_arg_getchar PARAMS((void)); +extern int _rl_arg_callback PARAMS((_rl_arg_cxt)); +extern void _rl_reset_argument PARAMS((void)); + +extern void _rl_start_using_history PARAMS((void)); +extern int _rl_free_saved_history_line PARAMS((void)); +extern void _rl_set_insert_mode PARAMS((int, int)); + +/* nls.c */ +extern int _rl_init_eightbit PARAMS((void)); + +/* parens.c */ +extern void _rl_enable_paren_matching PARAMS((int)); + +/* readline.c */ +extern void _rl_init_line_state PARAMS((void)); +extern void _rl_set_the_line PARAMS((void)); +extern int _rl_dispatch PARAMS((int, Keymap)); +extern int _rl_dispatch_subseq PARAMS((int, Keymap, int)); +extern void _rl_internal_char_cleanup PARAMS((void)); + +/* rltty.c */ +extern int _rl_disable_tty_signals PARAMS((void)); +extern int _rl_restore_tty_signals PARAMS((void)); + +/* search.c */ +extern int _rl_nsearch_callback PARAMS((_rl_search_cxt *)); + +/* terminal.c */ +extern void _rl_get_screen_size PARAMS((int, int)); +extern int _rl_init_terminal_io PARAMS((const char *)); +#ifdef _MINIX +extern void _rl_output_character_function PARAMS((int)); +#else +extern int _rl_output_character_function PARAMS((int)); +#endif +extern void _rl_output_some_chars PARAMS((const char *, int)); +extern int _rl_backspace PARAMS((int)); +extern void _rl_enable_meta_key PARAMS((void)); +extern void _rl_control_keypad PARAMS((int)); +extern void _rl_set_cursor PARAMS((int, int)); + +/* text.c */ +extern void _rl_fix_point PARAMS((int)); +extern int _rl_replace_text PARAMS((const char *, int, int)); +extern int _rl_insert_char PARAMS((int, int)); +extern int _rl_overwrite_char PARAMS((int, int)); +extern int _rl_overwrite_rubout PARAMS((int, int)); +extern int _rl_rubout_char PARAMS((int, int)); +#if defined (HANDLE_MULTIBYTE) +extern int _rl_char_search_internal PARAMS((int, int, char *, int)); +#else +extern int _rl_char_search_internal PARAMS((int, int, int)); +#endif +extern int _rl_set_mark_at_pos PARAMS((int)); + +/* undo.c */ +extern UNDO_LIST *_rl_copy_undo_entry PARAMS((UNDO_LIST *)); +extern UNDO_LIST *_rl_copy_undo_list PARAMS((UNDO_LIST *)); + +/* util.c */ +extern int _rl_abort_internal PARAMS((void)); +extern char *_rl_strindex PARAMS((const char *, const char *)); +extern int _rl_qsort_string_compare PARAMS((char **, char **)); +extern int (_rl_uppercase_p) PARAMS((int)); +extern int (_rl_lowercase_p) PARAMS((int)); +extern int (_rl_pure_alphabetic) PARAMS((int)); +extern int (_rl_digit_p) PARAMS((int)); +extern int (_rl_to_lower) PARAMS((int)); +extern int (_rl_to_upper) PARAMS((int)); +extern int (_rl_digit_value) PARAMS((int)); + +/* vi_mode.c */ +extern void _rl_vi_initialize_line PARAMS((void)); +extern void _rl_vi_reset_last PARAMS((void)); +extern void _rl_vi_set_last PARAMS((int, int, int)); +extern int _rl_vi_textmod_command PARAMS((int)); +extern void _rl_vi_done_inserting PARAMS((void)); + +/************************************************************************* + * Undocumented private variables * + *************************************************************************/ + +/* bind.c */ +extern const char *_rl_possible_control_prefixes[]; +extern const char *_rl_possible_meta_prefixes[]; + +/* callback.c */ +extern _rl_callback_func_t *_rl_callback_func; +extern _rl_callback_generic_arg *_rl_callback_data; + +/* complete.c */ +extern int _rl_complete_show_all; +extern int _rl_complete_show_unmodified; +extern int _rl_complete_mark_directories; +extern int _rl_complete_mark_symlink_dirs; +extern int _rl_print_completions_horizontally; +extern int _rl_completion_case_fold; +extern int _rl_match_hidden_files; +extern int _rl_page_completions; + +/* display.c */ +extern int _rl_vis_botlin; +extern int _rl_last_c_pos; +extern int _rl_suppress_redisplay; +extern int _rl_want_redisplay; +extern const char *rl_display_prompt; + +/* isearch.c */ +extern char *_rl_isearch_terminators; + +extern _rl_search_cxt *_rl_iscxt; + +/* macro.c */ +extern char *_rl_executing_macro; + +/* misc.c */ +extern int _rl_history_preserve_point; +extern int _rl_history_saved_point; + +extern _rl_arg_cxt _rl_argcxt; + +/* readline.c */ +extern int _rl_horizontal_scroll_mode; +extern int _rl_mark_modified_lines; +extern int _rl_bell_preference; +extern int _rl_meta_flag; +extern int _rl_convert_meta_chars_to_ascii; +extern int _rl_output_meta_chars; +extern int _rl_bind_stty_chars; +extern char *_rl_comment_begin; +extern unsigned char _rl_parsing_conditionalized_out; +extern Keymap _rl_keymap; +extern FILE *_rl_in_stream; +extern FILE *_rl_out_stream; +extern int _rl_last_command_was_kill; +extern int _rl_eof_char; +extern procenv_t readline_top_level; + +/* search.c */ +extern _rl_search_cxt *_rl_nscxt; + +/* terminal.c */ +extern int _rl_enable_keypad; +extern int _rl_enable_meta; +extern const char *_rl_term_clreol; +extern const char *_rl_term_clrpag; +extern const char *_rl_term_im; +extern const char *_rl_term_ic; +extern const char *_rl_term_ei; +extern const char *_rl_term_DC; +extern const char *_rl_term_up; +extern const char *_rl_term_dc; +extern const char *_rl_term_cr; +extern const char *_rl_term_IC; +extern const char *_rl_term_forward_char; +extern int _rl_screenheight; +extern int _rl_screenwidth; +extern int _rl_screenchars; +extern int _rl_terminal_can_insert; +extern int _rl_term_autowrap; + +/* undo.c */ +extern int _rl_doing_an_undo; +extern int _rl_undo_group_level; + +/* vi_mode.c */ +extern int _rl_vi_last_command; + +#endif /* _RL_PRIVATE_H_ */ diff --git a/extra/readline/rlshell.h b/extra/readline/rlshell.h new file mode 100644 index 00000000000..629b0e03b46 --- /dev/null +++ b/extra/readline/rlshell.h @@ -0,0 +1,34 @@ +/* rlshell.h -- utility functions normally provided by bash. */ + +/* Copyright (C) 1999 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#if !defined (_RL_SHELL_H_) +#define _RL_SHELL_H_ + +#include "rlstdc.h" + +extern char *sh_single_quote PARAMS((char *)); +extern void sh_set_lines_and_columns PARAMS((int, int)); +extern char *sh_get_env_value PARAMS((const char *)); +extern char *sh_get_home_dir PARAMS((void)); +extern int sh_unset_nodelay_mode PARAMS((int)); + +#endif /* _RL_SHELL_H_ */ diff --git a/extra/readline/rlstdc.h b/extra/readline/rlstdc.h new file mode 100644 index 00000000000..2a2272895ce --- /dev/null +++ b/extra/readline/rlstdc.h @@ -0,0 +1,45 @@ +/* stdc.h -- macros to make source compile on both ANSI C and K&R C + compilers. */ + +/* Copyright (C) 1993 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + Bash 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 General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with Bash; see the file COPYING. If not, write to the Free + Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#if !defined (_RL_STDC_H_) +#define _RL_STDC_H_ + +/* Adapted from BSD /usr/include/sys/cdefs.h. */ + +/* A function can be defined using prototypes and compile on both ANSI C + and traditional C compilers with something like this: + extern char *func PARAMS((char *, char *, int)); */ + +#if !defined (PARAMS) +# if defined (__STDC__) || defined (__GNUC__) || defined (__cplusplus) +# define PARAMS(protos) protos +# else +# define PARAMS(protos) () +# endif +#endif + +#ifndef __attribute__ +# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) +# define __attribute__(x) +# endif +#endif + +#endif /* !_RL_STDC_H_ */ diff --git a/extra/readline/rltty.c b/extra/readline/rltty.c new file mode 100644 index 00000000000..ca8caee7de7 --- /dev/null +++ b/extra/readline/rltty.c @@ -0,0 +1,1033 @@ +/* rltty.c -- functions to prepare and restore the terminal for readline's + use. */ + +/* Copyright (C) 1992-2005 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <sys/types.h> +#include <signal.h> +#include <errno.h> +#include <stdio.h> + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +#include "rldefs.h" + +#if defined (GWINSZ_IN_SYS_IOCTL) +# include <sys/ioctl.h> +#endif /* GWINSZ_IN_SYS_IOCTL */ + +#include "rltty.h" +#include "readline.h" +#include "rlprivate.h" + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +rl_vintfunc_t *rl_prep_term_function = rl_prep_terminal; +rl_voidfunc_t *rl_deprep_term_function = rl_deprep_terminal; + +static void block_sigint PARAMS((void)); +static void release_sigint PARAMS((void)); + +static void set_winsize PARAMS((int)); + +/* **************************************************************** */ +/* */ +/* Signal Management */ +/* */ +/* **************************************************************** */ + +#if defined (HAVE_POSIX_SIGNALS) +static sigset_t sigint_set, sigint_oset; +#else /* !HAVE_POSIX_SIGNALS */ +# if defined (HAVE_BSD_SIGNALS) +static int sigint_oldmask; +# endif /* HAVE_BSD_SIGNALS */ +#endif /* !HAVE_POSIX_SIGNALS */ + +static int sigint_blocked; + +/* Cause SIGINT to not be delivered until the corresponding call to + release_sigint(). */ +static void +block_sigint () +{ + if (sigint_blocked) + return; + +#if defined (HAVE_POSIX_SIGNALS) + sigemptyset (&sigint_set); + sigemptyset (&sigint_oset); + sigaddset (&sigint_set, SIGINT); + sigprocmask (SIG_BLOCK, &sigint_set, &sigint_oset); +#else /* !HAVE_POSIX_SIGNALS */ +# if defined (HAVE_BSD_SIGNALS) + sigint_oldmask = sigblock (sigmask (SIGINT)); +# else /* !HAVE_BSD_SIGNALS */ +# if defined (HAVE_USG_SIGHOLD) + sighold (SIGINT); +# endif /* HAVE_USG_SIGHOLD */ +# endif /* !HAVE_BSD_SIGNALS */ +#endif /* !HAVE_POSIX_SIGNALS */ + + sigint_blocked = 1; +} + +/* Allow SIGINT to be delivered. */ +static void +release_sigint () +{ + if (sigint_blocked == 0) + return; + +#if defined (HAVE_POSIX_SIGNALS) + sigprocmask (SIG_SETMASK, &sigint_oset, (sigset_t *)NULL); +#else +# if defined (HAVE_BSD_SIGNALS) + sigsetmask (sigint_oldmask); +# else /* !HAVE_BSD_SIGNALS */ +# if defined (HAVE_USG_SIGHOLD) + sigrelse (SIGINT); +# endif /* HAVE_USG_SIGHOLD */ +# endif /* !HAVE_BSD_SIGNALS */ +#endif /* !HAVE_POSIX_SIGNALS */ + + sigint_blocked = 0; +} + +/* **************************************************************** */ +/* */ +/* Saving and Restoring the TTY */ +/* */ +/* **************************************************************** */ + +/* Non-zero means that the terminal is in a prepped state. */ +static int terminal_prepped; + +static _RL_TTY_CHARS _rl_tty_chars, _rl_last_tty_chars; + +/* If non-zero, means that this process has called tcflow(fd, TCOOFF) + and output is suspended. */ +#if defined (__ksr1__) +static int ksrflow; +#endif + +/* Dummy call to force a backgrounded readline to stop before it tries + to get the tty settings. */ +static void +set_winsize (int tty __attribute__((unused))) +{ +#if defined (TIOCGWINSZ) + struct winsize w; + + if (ioctl (tty, TIOCGWINSZ, &w) == 0) + (void) ioctl (tty, TIOCSWINSZ, &w); +#endif /* TIOCGWINSZ */ +} + +#if defined (NO_TTY_DRIVER) +/* Nothing */ +#elif defined (NEW_TTY_DRIVER) + +/* Values for the `flags' field of a struct bsdtty. This tells which + elements of the struct bsdtty have been fetched from the system and + are valid. */ +#define SGTTY_SET 0x01 +#define LFLAG_SET 0x02 +#define TCHARS_SET 0x04 +#define LTCHARS_SET 0x08 + +struct bsdtty { + struct sgttyb sgttyb; /* Basic BSD tty driver information. */ + int lflag; /* Local mode flags, like LPASS8. */ +#if defined (TIOCGETC) + struct tchars tchars; /* Terminal special characters, including ^S and ^Q. */ +#endif +#if defined (TIOCGLTC) + struct ltchars ltchars; /* 4.2 BSD editing characters */ +#endif + int flags; /* Bitmap saying which parts of the struct are valid. */ +}; + +#define TIOTYPE struct bsdtty + +static TIOTYPE otio; + +static void save_tty_chars PARAMS((TIOTYPE *)); +static int _get_tty_settings PARAMS((int, TIOTYPE *)); +static int get_tty_settings PARAMS((int, TIOTYPE *)); +static int _set_tty_settings PARAMS((int, TIOTYPE *)); +static int set_tty_settings PARAMS((int, TIOTYPE *)); + +static void prepare_terminal_settings PARAMS((int, TIOTYPE, TIOTYPE *)); + +static void set_special_char PARAMS((Keymap, TIOTYPE *, int, rl_command_func_t)); + +static void +save_tty_chars (tiop) + TIOTYPE *tiop; +{ + _rl_last_tty_chars = _rl_tty_chars; + + if (tiop->flags & SGTTY_SET) + { + _rl_tty_chars.t_erase = tiop->sgttyb.sg_erase; + _rl_tty_chars.t_kill = tiop->sgttyb.sg_kill; + } + + if (tiop->flags & TCHARS_SET) + { + _rl_tty_chars.t_intr = tiop->tchars.t_intrc; + _rl_tty_chars.t_quit = tiop->tchars.t_quitc; + _rl_tty_chars.t_start = tiop->tchars.t_startc; + _rl_tty_chars.t_stop = tiop->tchars.t_stopc; + _rl_tty_chars.t_eof = tiop->tchars.t_eofc; + _rl_tty_chars.t_eol = '\n'; + _rl_tty_chars.t_eol2 = tiop->tchars.t_brkc; + } + + if (tiop->flags & LTCHARS_SET) + { + _rl_tty_chars.t_susp = tiop->ltchars.t_suspc; + _rl_tty_chars.t_dsusp = tiop->ltchars.t_dsuspc; + _rl_tty_chars.t_reprint = tiop->ltchars.t_rprntc; + _rl_tty_chars.t_flush = tiop->ltchars.t_flushc; + _rl_tty_chars.t_werase = tiop->ltchars.t_werasc; + _rl_tty_chars.t_lnext = tiop->ltchars.t_lnextc; + } + + _rl_tty_chars.t_status = -1; +} + +static int +get_tty_settings (tty, tiop) + int tty; + TIOTYPE *tiop; +{ + set_winsize (tty); + + tiop->flags = tiop->lflag = 0; + + errno = 0; + if (ioctl (tty, TIOCGETP, &(tiop->sgttyb)) < 0) + return -1; + tiop->flags |= SGTTY_SET; + +#if defined (TIOCLGET) + if (ioctl (tty, TIOCLGET, &(tiop->lflag)) == 0) + tiop->flags |= LFLAG_SET; +#endif + +#if defined (TIOCGETC) + if (ioctl (tty, TIOCGETC, &(tiop->tchars)) == 0) + tiop->flags |= TCHARS_SET; +#endif + +#if defined (TIOCGLTC) + if (ioctl (tty, TIOCGLTC, &(tiop->ltchars)) == 0) + tiop->flags |= LTCHARS_SET; +#endif + + return 0; +} + +static int +set_tty_settings (tty, tiop) + int tty; + TIOTYPE *tiop; +{ + if (tiop->flags & SGTTY_SET) + { + ioctl (tty, TIOCSETN, &(tiop->sgttyb)); + tiop->flags &= ~SGTTY_SET; + } + readline_echoing_p = 1; + +#if defined (TIOCLSET) + if (tiop->flags & LFLAG_SET) + { + ioctl (tty, TIOCLSET, &(tiop->lflag)); + tiop->flags &= ~LFLAG_SET; + } +#endif + +#if defined (TIOCSETC) + if (tiop->flags & TCHARS_SET) + { + ioctl (tty, TIOCSETC, &(tiop->tchars)); + tiop->flags &= ~TCHARS_SET; + } +#endif + +#if defined (TIOCSLTC) + if (tiop->flags & LTCHARS_SET) + { + ioctl (tty, TIOCSLTC, &(tiop->ltchars)); + tiop->flags &= ~LTCHARS_SET; + } +#endif + + return 0; +} + +static void +prepare_terminal_settings (meta_flag, oldtio, tiop) + int meta_flag; + TIOTYPE oldtio, *tiop; +{ + readline_echoing_p = (oldtio.sgttyb.sg_flags & ECHO); + + /* Copy the original settings to the structure we're going to use for + our settings. */ + tiop->sgttyb = oldtio.sgttyb; + tiop->lflag = oldtio.lflag; +#if defined (TIOCGETC) + tiop->tchars = oldtio.tchars; +#endif +#if defined (TIOCGLTC) + tiop->ltchars = oldtio.ltchars; +#endif + tiop->flags = oldtio.flags; + + /* First, the basic settings to put us into character-at-a-time, no-echo + input mode. */ + tiop->sgttyb.sg_flags &= ~(ECHO | CRMOD); + tiop->sgttyb.sg_flags |= CBREAK; + + /* If this terminal doesn't care how the 8th bit is used, then we can + use it for the meta-key. If only one of even or odd parity is + specified, then the terminal is using parity, and we cannot. */ +#if !defined (ANYP) +# define ANYP (EVENP | ODDP) +#endif + if (((oldtio.sgttyb.sg_flags & ANYP) == ANYP) || + ((oldtio.sgttyb.sg_flags & ANYP) == 0)) + { + tiop->sgttyb.sg_flags |= ANYP; + + /* Hack on local mode flags if we can. */ +#if defined (TIOCLGET) +# if defined (LPASS8) + tiop->lflag |= LPASS8; +# endif /* LPASS8 */ +#endif /* TIOCLGET */ + } + +#if defined (TIOCGETC) +# if defined (USE_XON_XOFF) + /* Get rid of terminal output start and stop characters. */ + tiop->tchars.t_stopc = -1; /* C-s */ + tiop->tchars.t_startc = -1; /* C-q */ + + /* If there is an XON character, bind it to restart the output. */ + if (oldtio.tchars.t_startc != -1) + rl_bind_key (oldtio.tchars.t_startc, rl_restart_output); +# endif /* USE_XON_XOFF */ + + /* If there is an EOF char, bind _rl_eof_char to it. */ + if (oldtio.tchars.t_eofc != -1) + _rl_eof_char = oldtio.tchars.t_eofc; + +# if defined (NO_KILL_INTR) + /* Get rid of terminal-generated SIGQUIT and SIGINT. */ + tiop->tchars.t_quitc = -1; /* C-\ */ + tiop->tchars.t_intrc = -1; /* C-c */ +# endif /* NO_KILL_INTR */ +#endif /* TIOCGETC */ + +#if defined (TIOCGLTC) + /* Make the interrupt keys go away. Just enough to make people happy. */ + tiop->ltchars.t_dsuspc = -1; /* C-y */ + tiop->ltchars.t_lnextc = -1; /* C-v */ +#endif /* TIOCGLTC */ +} + +#else /* !defined (NEW_TTY_DRIVER) */ + +#if !defined (VMIN) +# define VMIN VEOF +#endif + +#if !defined (VTIME) +# define VTIME VEOL +#endif + +#if defined (TERMIOS_TTY_DRIVER) +# define TIOTYPE struct termios +# define DRAIN_OUTPUT(fd) tcdrain (fd) +# define GETATTR(tty, tiop) (tcgetattr (tty, tiop)) +# ifdef M_UNIX +# define SETATTR(tty, tiop) (tcsetattr (tty, TCSANOW, tiop)) +# else +# define SETATTR(tty, tiop) (tcsetattr (tty, TCSADRAIN, tiop)) +# endif /* !M_UNIX */ +#else +# define TIOTYPE struct termio +# define DRAIN_OUTPUT(fd) +# define GETATTR(tty, tiop) (ioctl (tty, TCGETA, tiop)) +# define SETATTR(tty, tiop) (ioctl (tty, TCSETAW, tiop)) +#endif /* !TERMIOS_TTY_DRIVER */ + +static TIOTYPE otio; + +static void save_tty_chars PARAMS((TIOTYPE *)); +static int _get_tty_settings PARAMS((int, TIOTYPE *)); +static int get_tty_settings PARAMS((int, TIOTYPE *)); +static int _set_tty_settings PARAMS((int, TIOTYPE *)); +static int set_tty_settings PARAMS((int, TIOTYPE *)); + +static void prepare_terminal_settings PARAMS((int, TIOTYPE, TIOTYPE *)); + +static void set_special_char PARAMS((Keymap, TIOTYPE *, int, rl_command_func_t)); +static void _rl_bind_tty_special_chars PARAMS((Keymap, TIOTYPE)); + +#if defined (FLUSHO) +# define OUTPUT_BEING_FLUSHED(tp) (tp->c_lflag & FLUSHO) +#else +# define OUTPUT_BEING_FLUSHED(tp) 0 +#endif + +static void +save_tty_chars (tiop) + TIOTYPE *tiop; +{ + _rl_last_tty_chars = _rl_tty_chars; + + _rl_tty_chars.t_eof = tiop->c_cc[VEOF]; + _rl_tty_chars.t_eol = tiop->c_cc[VEOL]; +#ifdef VEOL2 + _rl_tty_chars.t_eol2 = tiop->c_cc[VEOL2]; +#endif + _rl_tty_chars.t_erase = tiop->c_cc[VERASE]; +#ifdef VWERASE + _rl_tty_chars.t_werase = tiop->c_cc[VWERASE]; +#endif + _rl_tty_chars.t_kill = tiop->c_cc[VKILL]; +#ifdef VREPRINT + _rl_tty_chars.t_reprint = tiop->c_cc[VREPRINT]; +#endif + _rl_tty_chars.t_intr = tiop->c_cc[VINTR]; + _rl_tty_chars.t_quit = tiop->c_cc[VQUIT]; +#ifdef VSUSP + _rl_tty_chars.t_susp = tiop->c_cc[VSUSP]; +#endif +#ifdef VDSUSP + _rl_tty_chars.t_dsusp = tiop->c_cc[VDSUSP]; +#endif +#ifdef VSTART + _rl_tty_chars.t_start = tiop->c_cc[VSTART]; +#endif +#ifdef VSTOP + _rl_tty_chars.t_stop = tiop->c_cc[VSTOP]; +#endif +#ifdef VLNEXT + _rl_tty_chars.t_lnext = tiop->c_cc[VLNEXT]; +#endif +#ifdef VDISCARD + _rl_tty_chars.t_flush = tiop->c_cc[VDISCARD]; +#endif +#ifdef VSTATUS + _rl_tty_chars.t_status = tiop->c_cc[VSTATUS]; +#endif +} + +#if defined (_AIX) || defined (_AIX41) +/* Currently this is only used on AIX */ +static void +rltty_warning (msg) + char *msg; +{ + fprintf (stderr, "readline: warning: %s\n", msg); +} +#endif + +#if defined (_AIX) +void +setopost(tp) +TIOTYPE *tp; +{ + if ((tp->c_oflag & OPOST) == 0) + { + rltty_warning ("turning on OPOST for terminal\r"); + tp->c_oflag |= OPOST|ONLCR; + } +} +#endif + +static int +_get_tty_settings (tty, tiop) + int tty; + TIOTYPE *tiop; +{ + int ioctl_ret; + + while (1) + { + ioctl_ret = GETATTR (tty, tiop); + if (ioctl_ret < 0) + { + if (errno != EINTR) + return -1; + else + continue; + } + if (OUTPUT_BEING_FLUSHED (tiop)) + { +#if defined (FLUSHO) && defined (_AIX41) + rltty_warning ("turning off output flushing"); + tiop->c_lflag &= ~FLUSHO; + break; +#else + continue; +#endif + } + break; + } + + return 0; +} + +static int +get_tty_settings (tty, tiop) + int tty; + TIOTYPE *tiop; +{ + set_winsize (tty); + + errno = 0; + if (_get_tty_settings (tty, tiop) < 0) + return -1; + +#if defined (_AIX) + setopost(tiop); +#endif + + return 0; +} + +static int +_set_tty_settings (tty, tiop) + int tty; + TIOTYPE *tiop; +{ + while (SETATTR (tty, tiop) < 0) + { + if (errno != EINTR) + return -1; + errno = 0; + } + return 0; +} + +static int +set_tty_settings (tty, tiop) + int tty; + TIOTYPE *tiop; +{ + if (_set_tty_settings (tty, tiop) < 0) + return -1; + +#if 0 + +#if defined (TERMIOS_TTY_DRIVER) +# if defined (__ksr1__) + if (ksrflow) + { + ksrflow = 0; + tcflow (tty, TCOON); + } +# else /* !ksr1 */ + tcflow (tty, TCOON); /* Simulate a ^Q. */ +# endif /* !ksr1 */ +#else + ioctl (tty, TCXONC, 1); /* Simulate a ^Q. */ +#endif /* !TERMIOS_TTY_DRIVER */ + +#endif /* 0 */ + + return 0; +} + +static void +prepare_terminal_settings (meta_flag, oldtio, tiop) + int meta_flag; + TIOTYPE oldtio, *tiop; +{ + readline_echoing_p = (oldtio.c_lflag & ECHO); + + tiop->c_lflag &= ~(ICANON | ECHO); + + if ((unsigned char) oldtio.c_cc[VEOF] != (unsigned char) _POSIX_VDISABLE) + _rl_eof_char = oldtio.c_cc[VEOF]; + +#if defined (USE_XON_XOFF) +#if defined (IXANY) + tiop->c_iflag &= ~(IXON | IXOFF | IXANY); +#else + /* `strict' Posix systems do not define IXANY. */ + tiop->c_iflag &= ~(IXON | IXOFF); +#endif /* IXANY */ +#endif /* USE_XON_XOFF */ + + /* Only turn this off if we are using all 8 bits. */ + if (((tiop->c_cflag & CSIZE) == CS8) || meta_flag) + tiop->c_iflag &= ~(ISTRIP | INPCK); + + /* Make sure we differentiate between CR and NL on input. */ + tiop->c_iflag &= ~(ICRNL | INLCR); + +#if !defined (HANDLE_SIGNALS) + tiop->c_lflag &= ~ISIG; +#else + tiop->c_lflag |= ISIG; +#endif + + tiop->c_cc[VMIN] = 1; + tiop->c_cc[VTIME] = 0; + +#if defined (FLUSHO) + if (OUTPUT_BEING_FLUSHED (tiop)) + { + tiop->c_lflag &= ~FLUSHO; + oldtio.c_lflag &= ~FLUSHO; + } +#endif + + /* Turn off characters that we need on Posix systems with job control, + just to be sure. This includes ^Y and ^V. This should not really + be necessary. */ +#if defined (TERMIOS_TTY_DRIVER) && defined (_POSIX_VDISABLE) + +#if defined (VLNEXT) + tiop->c_cc[VLNEXT] = _POSIX_VDISABLE; +#endif + +#if defined (VDSUSP) + tiop->c_cc[VDSUSP] = _POSIX_VDISABLE; +#endif + +#endif /* TERMIOS_TTY_DRIVER && _POSIX_VDISABLE */ +} +#endif /* !NEW_TTY_DRIVER */ + +/* Put the terminal in CBREAK mode so that we can detect key presses. */ +#if defined (NO_TTY_DRIVER) +void +rl_prep_terminal (meta_flag) + int meta_flag; +{ + readline_echoing_p = 1; +} + +void +rl_deprep_terminal () +{ +} + +#else /* ! NO_TTY_DRIVER */ +void +rl_prep_terminal (meta_flag) + int meta_flag; +{ + int tty; + TIOTYPE tio; + + if (terminal_prepped) + return; + + /* Try to keep this function from being INTerrupted. */ + block_sigint (); + + tty = fileno (rl_instream); + + if (get_tty_settings (tty, &tio) < 0) + { +#if defined (ENOTSUP) + /* MacOS X, at least, lies about the value of errno if tcgetattr fails. */ + if (errno == ENOTTY || errno == ENOTSUP) +#else + if (errno == ENOTTY) +#endif + readline_echoing_p = 1; /* XXX */ + release_sigint (); + return; + } + + otio = tio; + + if (_rl_bind_stty_chars) + { +#if defined (VI_MODE) + /* If editing in vi mode, make sure we restore the bindings in the + insertion keymap no matter what keymap we ended up in. */ + if (rl_editing_mode == vi_mode) + rl_tty_unset_default_bindings (vi_insertion_keymap); + else +#endif + rl_tty_unset_default_bindings (_rl_keymap); + } + save_tty_chars (&otio); + RL_SETSTATE(RL_STATE_TTYCSAVED); + if (_rl_bind_stty_chars) + { +#if defined (VI_MODE) + /* If editing in vi mode, make sure we set the bindings in the + insertion keymap no matter what keymap we ended up in. */ + if (rl_editing_mode == vi_mode) + _rl_bind_tty_special_chars (vi_insertion_keymap, tio); + else +#endif + _rl_bind_tty_special_chars (_rl_keymap, tio); + } + + prepare_terminal_settings (meta_flag, otio, &tio); + + if (set_tty_settings (tty, &tio) < 0) + { + release_sigint (); + return; + } + + if (_rl_enable_keypad) + _rl_control_keypad (1); + + fflush (rl_outstream); + terminal_prepped = 1; + RL_SETSTATE(RL_STATE_TERMPREPPED); + + release_sigint (); +} + +/* Restore the terminal's normal settings and modes. */ +void +rl_deprep_terminal () +{ + int tty; + + if (!terminal_prepped) + return; + + /* Try to keep this function from being interrupted. */ + block_sigint (); + + tty = fileno (rl_instream); + + if (_rl_enable_keypad) + _rl_control_keypad (0); + + fflush (rl_outstream); + + if (set_tty_settings (tty, &otio) < 0) + { + release_sigint (); + return; + } + + terminal_prepped = 0; + RL_UNSETSTATE(RL_STATE_TERMPREPPED); + + release_sigint (); +} +#endif /* !NO_TTY_DRIVER */ + +/* **************************************************************** */ +/* */ +/* Bogus Flow Control */ +/* */ +/* **************************************************************** */ + +int +rl_restart_output (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ +#if defined (__MINGW32__) + return 0; +#else /* !__MING32__ */ + + int fildes = fileno (rl_outstream); +#if defined (TIOCSTART) +#if defined (apollo) + ioctl (&fildes, TIOCSTART, 0); +#else + ioctl (fildes, TIOCSTART, 0); +#endif /* apollo */ + +#else /* !TIOCSTART */ +# if defined (TERMIOS_TTY_DRIVER) +# if defined (__ksr1__) + if (ksrflow) + { + ksrflow = 0; + tcflow (fildes, TCOON); + } +# else /* !ksr1 */ + tcflow (fildes, TCOON); /* Simulate a ^Q. */ +# endif /* !ksr1 */ +# else /* !TERMIOS_TTY_DRIVER */ +# if defined (TCXONC) + ioctl (fildes, TCXONC, TCOON); +# endif /* TCXONC */ +# endif /* !TERMIOS_TTY_DRIVER */ +#endif /* !TIOCSTART */ + + return 0; +#endif /* !__MINGW32__ */ +} + +int +rl_stop_output (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ +#if defined (__MINGW32__) + return 0; +#else + + int fildes = fileno (rl_instream); + +#if defined (TIOCSTOP) +# if defined (apollo) + ioctl (&fildes, TIOCSTOP, 0); +# else + ioctl (fildes, TIOCSTOP, 0); +# endif /* apollo */ +#else /* !TIOCSTOP */ +# if defined (TERMIOS_TTY_DRIVER) +# if defined (__ksr1__) + ksrflow = 1; +# endif /* ksr1 */ + tcflow (fildes, TCOOFF); +# else +# if defined (TCXONC) + ioctl (fildes, TCXONC, TCOON); +# endif /* TCXONC */ +# endif /* !TERMIOS_TTY_DRIVER */ +#endif /* !TIOCSTOP */ + + return 0; +#endif /* !__MINGW32__ */ +} + +/* **************************************************************** */ +/* */ +/* Default Key Bindings */ +/* */ +/* **************************************************************** */ + +#if !defined (NO_TTY_DRIVER) +#define SET_SPECIAL(sc, func) set_special_char(kmap, &ttybuff, sc, func) +#endif + +#if defined (NO_TTY_DRIVER) + +#define SET_SPECIAL(sc, func) +#define RESET_SPECIAL(c) + +#elif defined (NEW_TTY_DRIVER) +static void +set_special_char (kmap, tiop, sc, func) + Keymap kmap; + TIOTYPE *tiop; + int sc; + rl_command_func_t *func; +{ + if (sc != -1 && kmap[(unsigned char)sc].type == ISFUNC) + kmap[(unsigned char)sc].function = func; +} + +#define RESET_SPECIAL(c) \ + if (c != -1 && kmap[(unsigned char)c].type == ISFUNC) + kmap[(unsigned char)c].function = rl_insert; + +static void +_rl_bind_tty_special_chars (kmap, ttybuff) + Keymap kmap; + TIOTYPE ttybuff; +{ + if (ttybuff.flags & SGTTY_SET) + { + SET_SPECIAL (ttybuff.sgttyb.sg_erase, rl_rubout); + SET_SPECIAL (ttybuff.sgttyb.sg_kill, rl_unix_line_discard); + } + +# if defined (TIOCGLTC) + if (ttybuff.flags & LTCHARS_SET) + { + SET_SPECIAL (ttybuff.ltchars.t_werasc, rl_unix_word_rubout); + SET_SPECIAL (ttybuff.ltchars.t_lnextc, rl_quoted_insert); + } +# endif /* TIOCGLTC */ +} + +#else /* !NEW_TTY_DRIVER */ +static void +set_special_char (kmap, tiop, sc, func) + Keymap kmap; + TIOTYPE *tiop; + int sc; + rl_command_func_t *func; +{ + unsigned char uc; + + uc = tiop->c_cc[sc]; + if (uc != (unsigned char)_POSIX_VDISABLE && kmap[uc].type == ISFUNC) + kmap[uc].function = func; +} + +/* used later */ +#define RESET_SPECIAL(uc) \ + if (uc != (unsigned char)_POSIX_VDISABLE && kmap[uc].type == ISFUNC) \ + kmap[uc].function = rl_insert; + +static void +_rl_bind_tty_special_chars (kmap, ttybuff) + Keymap kmap; + TIOTYPE ttybuff; +{ + SET_SPECIAL (VERASE, rl_rubout); + SET_SPECIAL (VKILL, rl_unix_line_discard); + +# if defined (VLNEXT) && defined (TERMIOS_TTY_DRIVER) + SET_SPECIAL (VLNEXT, rl_quoted_insert); +# endif /* VLNEXT && TERMIOS_TTY_DRIVER */ + +# if defined (VWERASE) && defined (TERMIOS_TTY_DRIVER) + SET_SPECIAL (VWERASE, rl_unix_word_rubout); +# endif /* VWERASE && TERMIOS_TTY_DRIVER */ +} + +#endif /* !NEW_TTY_DRIVER */ + +/* Set the system's default editing characters to their readline equivalents + in KMAP. Should be static, now that we have rl_tty_set_default_bindings. */ +void +rltty_set_default_bindings (kmap) + Keymap kmap; +{ +#if !defined (NO_TTY_DRIVER) + TIOTYPE ttybuff; + int tty; + + tty = fileno (rl_instream); + + if (get_tty_settings (tty, &ttybuff) == 0) + _rl_bind_tty_special_chars (kmap, ttybuff); +#endif +} + +/* New public way to set the system default editing chars to their readline + equivalents. */ +void +rl_tty_set_default_bindings (kmap) + Keymap kmap; +{ + rltty_set_default_bindings (kmap); +} + +/* Rebind all of the tty special chars that readline worries about back + to self-insert. Call this before saving the current terminal special + chars with save_tty_chars(). This only works on POSIX termios or termio + systems. */ +void +rl_tty_unset_default_bindings (kmap) + Keymap kmap; +{ + /* Don't bother before we've saved the tty special chars at least once. */ + if (RL_ISSTATE(RL_STATE_TTYCSAVED) == 0) + return; + + RESET_SPECIAL (_rl_tty_chars.t_erase); + RESET_SPECIAL (_rl_tty_chars.t_kill); + +# if defined (VLNEXT) && defined (TERMIOS_TTY_DRIVER) + RESET_SPECIAL (_rl_tty_chars.t_lnext); +# endif /* VLNEXT && TERMIOS_TTY_DRIVER */ + +# if defined (VWERASE) && defined (TERMIOS_TTY_DRIVER) + RESET_SPECIAL (_rl_tty_chars.t_werase); +# endif /* VWERASE && TERMIOS_TTY_DRIVER */ +} + +#if defined (HANDLE_SIGNALS) + +#if defined (NEW_TTY_DRIVER) || defined (NO_TTY_DRIVER) +int +_rl_disable_tty_signals () +{ + return 0; +} + +int +_rl_restore_tty_signals () +{ + return 0; +} +#else + +static TIOTYPE sigstty, nosigstty; +static int tty_sigs_disabled = 0; + +int +_rl_disable_tty_signals () +{ + if (tty_sigs_disabled) + return 0; + + if (_get_tty_settings (fileno (rl_instream), &sigstty) < 0) + return -1; + + nosigstty = sigstty; + + nosigstty.c_lflag &= ~ISIG; + nosigstty.c_iflag &= ~IXON; + + if (_set_tty_settings (fileno (rl_instream), &nosigstty) < 0) + return (_set_tty_settings (fileno (rl_instream), &sigstty)); + + tty_sigs_disabled = 1; + return 0; +} + +int +_rl_restore_tty_signals () +{ + int r; + + if (tty_sigs_disabled == 0) + return 0; + + r = _set_tty_settings (fileno (rl_instream), &sigstty); + + if (r == 0) + tty_sigs_disabled = 0; + + return r; +} +#endif /* !NEW_TTY_DRIVER */ + +#endif /* HANDLE_SIGNALS */ diff --git a/extra/readline/rltty.h b/extra/readline/rltty.h new file mode 100644 index 00000000000..3a4770d25cb --- /dev/null +++ b/extra/readline/rltty.h @@ -0,0 +1,82 @@ +/* rltty.h - tty driver-related definitions used by some library files. */ + +/* Copyright (C) 1995 Free Software Foundation, Inc. + + This file contains the Readline Library (the Library), a set of + routines for providing Emacs style line input to programs that ask + for it. + + The Library is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + The 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 + General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#if !defined (_RLTTY_H_) +#define _RLTTY_H_ + +/* Posix systems use termios and the Posix signal functions. */ +#if defined (TERMIOS_TTY_DRIVER) +# include <termios.h> +#endif /* TERMIOS_TTY_DRIVER */ + +/* System V machines use termio. */ +#if defined (TERMIO_TTY_DRIVER) +# include <termio.h> +# if !defined (TCOON) +# define TCOON 1 +# endif +#endif /* TERMIO_TTY_DRIVER */ + +/* Other (BSD) machines use sgtty. */ +#if defined (NEW_TTY_DRIVER) +# include <sgtty.h> +#endif + +#include "rlwinsize.h" + +/* Define _POSIX_VDISABLE if we are not using the `new' tty driver and + it is not already defined. It is used both to determine if a + special character is disabled and to disable certain special + characters. Posix systems should set to 0, USG systems to -1. */ +#if !defined (NEW_TTY_DRIVER) && !defined (_POSIX_VDISABLE) +# if defined (_SVR4_VDISABLE) +# define _POSIX_VDISABLE _SVR4_VDISABLE +# else +# if defined (_POSIX_VERSION) +# define _POSIX_VDISABLE 0 +# else /* !_POSIX_VERSION */ +# define _POSIX_VDISABLE -1 +# endif /* !_POSIX_VERSION */ +# endif /* !_SVR4_DISABLE */ +#endif /* !NEW_TTY_DRIVER && !_POSIX_VDISABLE */ + +typedef struct _rl_tty_chars { + unsigned char t_eof; + unsigned char t_eol; + unsigned char t_eol2; + unsigned char t_erase; + unsigned char t_werase; + unsigned char t_kill; + unsigned char t_reprint; + unsigned char t_intr; + unsigned char t_quit; + unsigned char t_susp; + unsigned char t_dsusp; + unsigned char t_start; + unsigned char t_stop; + unsigned char t_lnext; + unsigned char t_flush; + unsigned char t_status; +} _RL_TTY_CHARS; + +#endif /* _RLTTY_H_ */ diff --git a/extra/readline/rltypedefs.h b/extra/readline/rltypedefs.h new file mode 100644 index 00000000000..2a0773b0672 --- /dev/null +++ b/extra/readline/rltypedefs.h @@ -0,0 +1,94 @@ +/* rltypedefs.h -- Type declarations for readline functions. */ + +/* Copyright (C) 2000-2004 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#ifndef _RL_TYPEDEFS_H_ +#define _RL_TYPEDEFS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Old-style */ + +#if !defined (_FUNCTION_DEF) +# define _FUNCTION_DEF + +typedef int Function (); +typedef void VFunction (); +typedef char *CPFunction (); +typedef char **CPPFunction (); + +#endif /* _FUNCTION_DEF */ + +/* New style. */ + +#if !defined (_RL_FUNCTION_TYPEDEF) +# define _RL_FUNCTION_TYPEDEF + +/* Bindable functions */ +typedef int rl_command_func_t PARAMS((int, int)); + +/* Typedefs for the completion system */ +typedef char *rl_compentry_func_t PARAMS((const char *, int)); +typedef char **rl_completion_func_t PARAMS((const char *, int, int)); + +typedef char *rl_quote_func_t PARAMS((char *, int, char *)); +typedef char *rl_dequote_func_t PARAMS((char *, int)); + +typedef int rl_compignore_func_t PARAMS((char **)); + +typedef void rl_compdisp_func_t PARAMS((char **, int, int)); + +/* Type for input and pre-read hook functions like rl_event_hook */ +typedef int rl_hook_func_t PARAMS((void)); + +/* Input function type */ +typedef int rl_getc_func_t PARAMS((FILE *)); + +/* Generic function that takes a character buffer (which could be the readline + line buffer) and an index into it (which could be rl_point) and returns + an int. */ +typedef int rl_linebuf_func_t PARAMS((char *, int)); + +/* `Generic' function pointer typedefs */ +typedef int rl_intfunc_t PARAMS((int)); +#define rl_ivoidfunc_t rl_hook_func_t +typedef int rl_icpfunc_t PARAMS((char *)); +typedef int rl_icppfunc_t PARAMS((char **)); + +typedef void rl_voidfunc_t PARAMS((void)); +typedef void rl_vintfunc_t PARAMS((int)); +typedef void rl_vcpfunc_t PARAMS((char *)); +typedef void rl_vcppfunc_t PARAMS((char **)); + +typedef char *rl_cpvfunc_t PARAMS((void)); +typedef char *rl_cpifunc_t PARAMS((int)); +typedef char *rl_cpcpfunc_t PARAMS((char *)); +typedef char *rl_cpcppfunc_t PARAMS((char **)); + +#endif /* _RL_FUNCTION_TYPEDEF */ + +#ifdef __cplusplus +} +#endif + +#endif /* _RL_TYPEDEFS_H_ */ diff --git a/extra/readline/rlwinsize.h b/extra/readline/rlwinsize.h new file mode 100644 index 00000000000..ad671693c7b --- /dev/null +++ b/extra/readline/rlwinsize.h @@ -0,0 +1,57 @@ +/* rlwinsize.h -- an attempt to isolate some of the system-specific defines + for `struct winsize' and TIOCGWINSZ. */ + +/* Copyright (C) 1997 Free Software Foundation, Inc. + + This file contains the Readline Library (the Library), a set of + routines for providing Emacs style line input to programs that ask + for it. + + The Library is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + The 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 + General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#if !defined (_RLWINSIZE_H_) +#define _RLWINSIZE_H_ + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +/* Try to find the definitions of `struct winsize' and TIOGCWINSZ */ + +#if defined (GWINSZ_IN_SYS_IOCTL) && !defined (TIOCGWINSZ) +# include <sys/ioctl.h> +#endif /* GWINSZ_IN_SYS_IOCTL && !TIOCGWINSZ */ + +#if defined (STRUCT_WINSIZE_IN_TERMIOS) && !defined (STRUCT_WINSIZE_IN_SYS_IOCTL) +# include <termios.h> +#endif /* STRUCT_WINSIZE_IN_TERMIOS && !STRUCT_WINSIZE_IN_SYS_IOCTL */ + +/* Not in either of the standard places, look around. */ +#if !defined (STRUCT_WINSIZE_IN_TERMIOS) && !defined (STRUCT_WINSIZE_IN_SYS_IOCTL) +# if defined (HAVE_SYS_STREAM_H) +# include <sys/stream.h> +# endif /* HAVE_SYS_STREAM_H */ +# if defined (HAVE_SYS_PTEM_H) /* SVR4.2, at least, has it here */ +# include <sys/ptem.h> +# define _IO_PTEM_H /* work around SVR4.2 1.1.4 bug */ +# endif /* HAVE_SYS_PTEM_H */ +# if defined (HAVE_SYS_PTE_H) /* ??? */ +# include <sys/pte.h> +# endif /* HAVE_SYS_PTE_H */ +#endif /* !STRUCT_WINSIZE_IN_TERMIOS && !STRUCT_WINSIZE_IN_SYS_IOCTL */ + +#endif /* _RL_WINSIZE_H */ + diff --git a/extra/readline/savestring.c b/extra/readline/savestring.c new file mode 100644 index 00000000000..9c431a0e852 --- /dev/null +++ b/extra/readline/savestring.c @@ -0,0 +1,37 @@ +/* savestring.c */ + +/* Copyright (C) 1998,2003 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#include "config_readline.h" +#ifdef HAVE_STRING_H +# include <string.h> +#endif +#include "xmalloc.h" + +/* Backwards compatibility, now that savestring has been removed from + all `public' readline header files. */ +char * +savestring (s) + const char *s; +{ + return ((char *)strcpy ((char *)xmalloc (1 + strlen (s)), (s))); +} diff --git a/extra/readline/search.c b/extra/readline/search.c new file mode 100644 index 00000000000..cf50a7cc499 --- /dev/null +++ b/extra/readline/search.c @@ -0,0 +1,571 @@ +/* search.c - code for non-incremental searching in emacs and vi modes. */ + +/* Copyright (C) 1992-2005 Free Software Foundation, Inc. + + This file is part of the Readline Library (the Library), a set of + routines for providing Emacs style line input to programs that ask + for it. + + The Library is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + The 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 + General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <sys/types.h> +#include <stdio.h> + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif + +#include "rldefs.h" +#include "rlmbutil.h" + +#include "readline.h" +#include "history.h" + +#include "rlprivate.h" +#include "xmalloc.h" + +#ifdef abs +# undef abs +#endif +#define abs(x) (((x) >= 0) ? (x) : -(x)) + +_rl_search_cxt *_rl_nscxt = 0; + +extern HIST_ENTRY *_rl_saved_line_for_history; + +/* Functions imported from the rest of the library. */ +extern int _rl_free_history_entry PARAMS((HIST_ENTRY *)); + +static char *noninc_search_string = (char *) NULL; +static int noninc_history_pos; + +static char *prev_line_found = (char *) NULL; + +static int rl_history_search_len; +static int rl_history_search_pos; +static char *history_search_string; +static int history_string_size; + +static void make_history_line_current PARAMS((HIST_ENTRY *)); +static int noninc_search_from_pos PARAMS((char *, int, int)); +static int noninc_dosearch PARAMS((char *, int)); +static int noninc_search PARAMS((int, int)); +static int rl_history_search_internal PARAMS((int, int)); +static void rl_history_search_reinit PARAMS((void)); + +static _rl_search_cxt *_rl_nsearch_init PARAMS((int, int)); +static int _rl_nsearch_cleanup PARAMS((_rl_search_cxt *, int)); +static void _rl_nsearch_abort PARAMS((_rl_search_cxt *)); +static int _rl_nsearch_dispatch PARAMS((_rl_search_cxt *, int)); + +/* Make the data from the history entry ENTRY be the contents of the + current line. This doesn't do anything with rl_point; the caller + must set it. */ +static void +make_history_line_current (entry) + HIST_ENTRY *entry; +{ + _rl_replace_text (entry->line, 0, rl_end); + _rl_fix_point (1); +#if defined (VI_MODE) + if (rl_editing_mode == vi_mode) + /* POSIX.2 says that the `U' command doesn't affect the copy of any + command lines to the edit line. We're going to implement that by + making the undo list start after the matching line is copied to the + current editing buffer. */ + rl_free_undo_list (); +#endif + + if (_rl_saved_line_for_history) + _rl_free_history_entry (_rl_saved_line_for_history); + _rl_saved_line_for_history = (HIST_ENTRY *)NULL; +} + +/* Search the history list for STRING starting at absolute history position + POS. If STRING begins with `^', the search must match STRING at the + beginning of a history line, otherwise a full substring match is performed + for STRING. DIR < 0 means to search backwards through the history list, + DIR >= 0 means to search forward. */ +static int +noninc_search_from_pos (string, pos, dir) + char *string; + int pos, dir; +{ + int ret, old; + + if (pos < 0) + return -1; + + old = where_history (); + if (history_set_pos (pos) == 0) + return -1; + + RL_SETSTATE(RL_STATE_SEARCH); + if (*string == '^') + ret = history_search_prefix (string + 1, dir); + else + ret = history_search (string, dir); + RL_UNSETSTATE(RL_STATE_SEARCH); + + if (ret != -1) + ret = where_history (); + + history_set_pos (old); + return (ret); +} + +/* Search for a line in the history containing STRING. If DIR is < 0, the + search is backwards through previous entries, else through subsequent + entries. Returns 1 if the search was successful, 0 otherwise. */ +static int +noninc_dosearch (string, dir) + char *string; + int dir; +{ + int oldpos, pos; + HIST_ENTRY *entry; + + if (string == 0 || *string == '\0' || noninc_history_pos < 0) + { + rl_ding (); + return 0; + } + + pos = noninc_search_from_pos (string, noninc_history_pos + dir, dir); + if (pos == -1) + { + /* Search failed, current history position unchanged. */ + rl_maybe_unsave_line (); + rl_clear_message (); + rl_point = 0; + rl_ding (); + return 0; + } + + noninc_history_pos = pos; + + oldpos = where_history (); + history_set_pos (noninc_history_pos); + entry = current_history (); +#if defined (VI_MODE) + if (rl_editing_mode != vi_mode) +#endif + history_set_pos (oldpos); + + make_history_line_current (entry); + + rl_point = 0; + rl_mark = rl_end; + + rl_clear_message (); + return 1; +} + +static _rl_search_cxt * +_rl_nsearch_init (dir, pchar) + int dir, pchar; +{ + _rl_search_cxt *cxt; + char *p; + + cxt = _rl_scxt_alloc (RL_SEARCH_NSEARCH, 0); + if (dir < 0) + cxt->sflags |= SF_REVERSE; /* not strictly needed */ + + cxt->direction = dir; + cxt->history_pos = cxt->save_line; + + rl_maybe_save_line (); + + /* Clear the undo list, since reading the search string should create its + own undo list, and the whole list will end up being freed when we + finish reading the search string. */ + rl_undo_list = 0; + + /* Use the line buffer to read the search string. */ + rl_line_buffer[0] = 0; + rl_end = rl_point = 0; + + p = _rl_make_prompt_for_search (pchar ? pchar : ':'); + rl_message ("%s", p); + free (p); + + RL_SETSTATE(RL_STATE_NSEARCH); + + _rl_nscxt = cxt; + + return cxt; +} + +static int +_rl_nsearch_cleanup (cxt, r) + _rl_search_cxt *cxt; + int r; +{ + _rl_scxt_dispose (cxt, 0); + _rl_nscxt = 0; + + RL_UNSETSTATE(RL_STATE_NSEARCH); + + return (r != 1); +} + +static void +_rl_nsearch_abort (cxt) + _rl_search_cxt *cxt; +{ + rl_maybe_unsave_line (); + rl_clear_message (); + rl_point = cxt->save_point; + rl_mark = cxt->save_mark; + rl_restore_prompt (); + + RL_UNSETSTATE (RL_STATE_NSEARCH); +} + +/* Process just-read character C according to search context CXT. Return -1 + if the caller should abort the search, 0 if we should break out of the + loop, and 1 if we should continue to read characters. */ +static int +_rl_nsearch_dispatch (cxt, c) + _rl_search_cxt *cxt; + int c; +{ + switch (c) + { + case CTRL('W'): + rl_unix_word_rubout (1, c); + break; + + case CTRL('U'): + rl_unix_line_discard (1, c); + break; + + case RETURN: + case NEWLINE: + return 0; + + case CTRL('H'): + case RUBOUT: + if (rl_point == 0) + { + _rl_nsearch_abort (cxt); + return -1; + } + _rl_rubout_char (1, c); + break; + + case CTRL('C'): + case CTRL('G'): + rl_ding (); + _rl_nsearch_abort (cxt); + return -1; + + default: +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + rl_insert_text (cxt->mb); + else +#endif + _rl_insert_char (1, c); + break; + } + + (*rl_redisplay_function) (); + return 1; +} + +/* Perform one search according to CXT, using NONINC_SEARCH_STRING. Return + -1 if the search should be aborted, any other value means to clean up + using _rl_nsearch_cleanup (). Returns 1 if the search was successful, + 0 otherwise. */ +static int +_rl_nsearch_dosearch (cxt) + _rl_search_cxt *cxt; +{ + rl_mark = cxt->save_mark; + + /* If rl_point == 0, we want to re-use the previous search string and + start from the saved history position. If there's no previous search + string, punt. */ + if (rl_point == 0) + { + if (noninc_search_string == 0) + { + rl_ding (); + rl_restore_prompt (); + RL_UNSETSTATE (RL_STATE_NSEARCH); + return -1; + } + } + else + { + /* We want to start the search from the current history position. */ + noninc_history_pos = cxt->save_line; + FREE (noninc_search_string); + noninc_search_string = savestring (rl_line_buffer); + + /* If we don't want the subsequent undo list generated by the search + matching a history line to include the contents of the search string, + we need to clear rl_line_buffer here. For now, we just clear the + undo list generated by reading the search string. (If the search + fails, the old undo list will be restored by rl_maybe_unsave_line.) */ + rl_free_undo_list (); + } + + rl_restore_prompt (); + return (noninc_dosearch (noninc_search_string, cxt->direction)); +} + +/* Search non-interactively through the history list. DIR < 0 means to + search backwards through the history of previous commands; otherwise + the search is for commands subsequent to the current position in the + history list. PCHAR is the character to use for prompting when reading + the search string; if not specified (0), it defaults to `:'. */ +static int +noninc_search (dir, pchar) + int dir; + int pchar; +{ + _rl_search_cxt *cxt; + int c, r; + + cxt = _rl_nsearch_init (dir, pchar); + + if (RL_ISSTATE (RL_STATE_CALLBACK)) + return (0); + + /* Read the search string. */ + r = 0; + while (1) + { + c = _rl_search_getchar (cxt); + + if (c == 0) + break; + + r = _rl_nsearch_dispatch (cxt, c); + if (r < 0) + return 1; + else if (r == 0) + break; + } + + r = _rl_nsearch_dosearch (cxt); + return ((r >= 0) ? _rl_nsearch_cleanup (cxt, r) : (r != 1)); +} + +/* Search forward through the history list for a string. If the vi-mode + code calls this, KEY will be `?'. */ +int +rl_noninc_forward_search (count, key) + int count __attribute__((unused)), key; +{ + return noninc_search (1, (key == '?') ? '?' : 0); +} + +/* Reverse search the history list for a string. If the vi-mode code + calls this, KEY will be `/'. */ +int +rl_noninc_reverse_search (count, key) + int count __attribute__((unused)), key; +{ + return noninc_search (-1, (key == '/') ? '/' : 0); +} + +/* Search forward through the history list for the last string searched + for. If there is no saved search string, abort. */ +int +rl_noninc_forward_search_again (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ + int r; + + if (!noninc_search_string) + { + rl_ding (); + return (-1); + } + r = noninc_dosearch (noninc_search_string, 1); + return (r != 1); +} + +/* Reverse search in the history list for the last string searched + for. If there is no saved search string, abort. */ +int +rl_noninc_reverse_search_again (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ + int r; + + if (!noninc_search_string) + { + rl_ding (); + return (-1); + } + r = noninc_dosearch (noninc_search_string, -1); + return (r != 1); +} + +#if defined (READLINE_CALLBACKS) +int +_rl_nsearch_callback (cxt) + _rl_search_cxt *cxt; +{ + int c, r; + + c = _rl_search_getchar (cxt); + r = _rl_nsearch_dispatch (cxt, c); + if (r != 0) + return 1; + + r = _rl_nsearch_dosearch (cxt); + return ((r >= 0) ? _rl_nsearch_cleanup (cxt, r) : (r != 1)); +} +#endif + +static int +rl_history_search_internal (count, dir) + int count, dir; +{ + HIST_ENTRY *temp; + int ret, oldpos; + + rl_maybe_save_line (); + temp = (HIST_ENTRY *)NULL; + + /* Search COUNT times through the history for a line whose prefix + matches history_search_string. When this loop finishes, TEMP, + if non-null, is the history line to copy into the line buffer. */ + while (count) + { + ret = noninc_search_from_pos (history_search_string, rl_history_search_pos + dir, dir); + if (ret == -1) + break; + + /* Get the history entry we found. */ + rl_history_search_pos = ret; + oldpos = where_history (); + history_set_pos (rl_history_search_pos); + temp = current_history (); + history_set_pos (oldpos); + + /* Don't find multiple instances of the same line. */ + if (prev_line_found && STREQ (prev_line_found, temp->line)) + continue; + prev_line_found = temp->line; + count--; + } + + /* If we didn't find anything at all, return. */ + if (temp == 0) + { + rl_maybe_unsave_line (); + rl_ding (); + /* If you don't want the saved history line (last match) to show up + in the line buffer after the search fails, change the #if 0 to + #if 1 */ +#if 0 + if (rl_point > rl_history_search_len) + { + rl_point = rl_end = rl_history_search_len; + rl_line_buffer[rl_end] = '\0'; + rl_mark = 0; + } +#else + rl_point = rl_history_search_len; /* rl_maybe_unsave_line changes it */ + rl_mark = rl_end; +#endif + return 1; + } + + /* Copy the line we found into the current line buffer. */ + make_history_line_current (temp); + + rl_point = rl_history_search_len; + rl_mark = rl_end; + + return 0; +} + +static void +rl_history_search_reinit () +{ + rl_history_search_pos = where_history (); + rl_history_search_len = rl_point; + prev_line_found = (char *)NULL; + if (rl_point) + { + if (rl_history_search_len >= history_string_size - 2) + { + history_string_size = rl_history_search_len + 2; + history_search_string = (char *)xrealloc (history_search_string, history_string_size); + } + history_search_string[0] = '^'; + strncpy (history_search_string + 1, rl_line_buffer, rl_point); + history_search_string[rl_point + 1] = '\0'; + } + _rl_free_saved_history_line (); +} + +/* Search forward in the history for the string of characters + from the start of the line to rl_point. This is a non-incremental + search. */ +int +rl_history_search_forward (count, ignore) + int count, ignore; +{ + if (count == 0) + return (0); + + if (rl_last_func != rl_history_search_forward && + rl_last_func != rl_history_search_backward) + rl_history_search_reinit (); + + if (rl_history_search_len == 0) + return (rl_get_next_history (count, ignore)); + return (rl_history_search_internal (abs (count), (count > 0) ? 1 : -1)); +} + +/* Search backward through the history for the string of characters + from the start of the line to rl_point. This is a non-incremental + search. */ +int +rl_history_search_backward (count, ignore) + int count, ignore; +{ + if (count == 0) + return (0); + + if (rl_last_func != rl_history_search_forward && + rl_last_func != rl_history_search_backward) + rl_history_search_reinit (); + + if (rl_history_search_len == 0) + return (rl_get_previous_history (count, ignore)); + return (rl_history_search_internal (abs (count), (count > 0) ? -1 : 1)); +} diff --git a/extra/readline/shell.c b/extra/readline/shell.c new file mode 100644 index 00000000000..d67f3e65017 --- /dev/null +++ b/extra/readline/shell.c @@ -0,0 +1,208 @@ +/* shell.c -- readline utility functions that are normally provided by + bash when readline is linked as part of the shell. */ + +/* Copyright (C) 1997 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <sys/types.h> + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#if defined (HAVE_STRING_H) +# include <string.h> +#else +# include <strings.h> +#endif /* !HAVE_STRING_H */ + +#if defined (HAVE_LIMITS_H) +# include <limits.h> +#endif + +#if defined (HAVE_FCNTL_H) +#include <fcntl.h> +#endif +#if defined (HAVE_PWD_H) +#include <pwd.h> +#endif + +#include <stdio.h> + +#include "rlstdc.h" +#include "rlshell.h" +#include "xmalloc.h" + +#if defined (HAVE_GETPWUID) && !defined (HAVE_GETPW_DECLS) +extern struct passwd *getpwuid PARAMS((uid_t)); +#endif /* HAVE_GETPWUID && !HAVE_GETPW_DECLS */ + +#ifndef NULL +# define NULL 0 +#endif + +#ifndef CHAR_BIT +# define CHAR_BIT 8 +#endif + +/* Nonzero if the integer type T is signed. */ +#define TYPE_SIGNED(t) (! ((t) 0 < (t) -1)) + +/* Bound on length of the string representing an integer value of type T. + Subtract one for the sign bit if T is signed; + 302 / 1000 is log10 (2) rounded up; + add one for integer division truncation; + add one more for a minus sign if t is signed. */ +#define INT_STRLEN_BOUND(t) \ + ((sizeof (t) * CHAR_BIT - TYPE_SIGNED (t)) * 302 / 1000 \ + + 1 + TYPE_SIGNED (t)) + +/* All of these functions are resolved from bash if we are linking readline + as part of bash. */ + +/* Does shell-like quoting using single quotes. */ +char * +sh_single_quote (string) + char *string; +{ + register int c; + char *result, *r, *s; + + result = (char *)xmalloc (3 + (4 * strlen (string))); + r = result; + *r++ = '\''; + + for (s = string; s && (c = *s); s++) + { + *r++ = c; + + if (c == '\'') + { + *r++ = '\\'; /* insert escaped single quote */ + *r++ = '\''; + *r++ = '\''; /* start new quoted string */ + } + } + + *r++ = '\''; + *r = '\0'; + + return (result); +} + +/* Set the environment variables LINES and COLUMNS to lines and cols, + respectively. */ +void +sh_set_lines_and_columns (lines, cols) + int lines, cols; +{ + char *b; + +#if defined (HAVE_SETENV) + b = (char *)xmalloc (INT_STRLEN_BOUND (int) + 1); + sprintf (b, "%d", lines); + setenv ("LINES", b, 1); + free (b); + + b = (char *)xmalloc (INT_STRLEN_BOUND (int) + 1); + sprintf (b, "%d", cols); + setenv ("COLUMNS", b, 1); + free (b); +#else /* !HAVE_SETENV */ +# if defined (HAVE_PUTENV) + b = (char *)xmalloc (INT_STRLEN_BOUND (int) + sizeof ("LINES=") + 1); + sprintf (b, "LINES=%d", lines); + putenv (b); + + b = (char *)xmalloc (INT_STRLEN_BOUND (int) + sizeof ("COLUMNS=") + 1); + sprintf (b, "COLUMNS=%d", cols); + putenv (b); +# endif /* HAVE_PUTENV */ +#endif /* !HAVE_SETENV */ +} + +char * +sh_get_env_value (varname) + const char *varname; +{ + return ((char *)getenv (varname)); +} + +char * +sh_get_home_dir () +{ + char *home_dir; + struct passwd *entry; + + home_dir = (char *)NULL; +#if defined (HAVE_GETPWUID) + entry = getpwuid (getuid ()); + if (entry) + home_dir = entry->pw_dir; +#endif + return (home_dir); +} + +#if !defined (O_NDELAY) +# if defined (FNDELAY) +# define O_NDELAY FNDELAY +# endif +#endif + +int +sh_unset_nodelay_mode (fd) + int fd; +{ +#if defined (HAVE_FCNTL) + int flags, bflags; + + if ((flags = fcntl (fd, F_GETFL, 0)) < 0) + return -1; + + bflags = 0; + +#ifdef O_NONBLOCK + bflags |= O_NONBLOCK; +#endif + +#ifdef O_NDELAY + bflags |= O_NDELAY; +#endif + + if (flags & bflags) + { + flags &= ~bflags; + return (fcntl (fd, F_SETFL, flags)); + } +#endif + + return 0; +} diff --git a/extra/readline/signals.c b/extra/readline/signals.c new file mode 100644 index 00000000000..db392b3dcc4 --- /dev/null +++ b/extra/readline/signals.c @@ -0,0 +1,466 @@ +/* signals.c -- signal handling support for readline. */ + +/* Copyright (C) 1987-2005 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <stdio.h> /* Just for NULL. Yuck. */ +#include <sys/types.h> +#include <signal.h> + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +/* System-specific feature definitions and include files. */ +#include "rldefs.h" + +#if defined (GWINSZ_IN_SYS_IOCTL) +# include <sys/ioctl.h> +#endif /* GWINSZ_IN_SYS_IOCTL */ + +#if defined (HANDLE_SIGNALS) +/* Some standard library routines. */ +#include "readline.h" +#include "history.h" + +#include "rlprivate.h" + +#if !defined (RETSIGTYPE) +# if defined (VOID_SIGHANDLER) +# define RETSIGTYPE void +# else +# define RETSIGTYPE int +# endif /* !VOID_SIGHANDLER */ +#endif /* !RETSIGTYPE */ + +#if defined (VOID_SIGHANDLER) +# define SIGHANDLER_RETURN return +#else +# define SIGHANDLER_RETURN return (0) +#endif + +/* This typedef is equivalent to the one for Function; it allows us + to say SigHandler *foo = signal (SIGKILL, SIG_IGN); */ +typedef RETSIGTYPE SigHandler (); + +#if defined (HAVE_POSIX_SIGNALS) +typedef struct sigaction sighandler_cxt; +# define rl_sigaction(s, nh, oh) sigaction(s, nh, oh) +#else +typedef struct { SigHandler *sa_handler; int sa_mask, sa_flags; } sighandler_cxt; +# define sigemptyset(m) +#endif /* !HAVE_POSIX_SIGNALS */ + +#ifndef SA_RESTART +# define SA_RESTART 0 +#endif + +static SigHandler *rl_set_sighandler PARAMS((int, SigHandler *, sighandler_cxt *)); +static void rl_maybe_set_sighandler PARAMS((int, SigHandler *, sighandler_cxt *)); + +/* Exported variables for use by applications. */ + +/* If non-zero, readline will install its own signal handlers for + SIGINT, SIGTERM, SIGQUIT, SIGALRM, SIGTSTP, SIGTTIN, and SIGTTOU. */ +int rl_catch_signals = 1; + +/* If non-zero, readline will install a signal handler for SIGWINCH. */ +#ifdef SIGWINCH +int rl_catch_sigwinch = 1; +#else +int rl_catch_sigwinch = 0; /* for the readline state struct in readline.c */ +#endif + +static int signals_set_flag; +static int sigwinch_set_flag; + +/* **************************************************************** */ +/* */ +/* Signal Handling */ +/* */ +/* **************************************************************** */ + +static sighandler_cxt old_int, old_term, old_alrm, old_quit; +#if defined (SIGTSTP) +static sighandler_cxt old_tstp, old_ttou, old_ttin; +#endif +#if defined (SIGWINCH) +static sighandler_cxt old_winch; +#endif + +/* Readline signal handler functions. */ + +static RETSIGTYPE +rl_signal_handler (sig) + int sig; +{ +#if defined (HAVE_POSIX_SIGNALS) + sigset_t set; +#else /* !HAVE_POSIX_SIGNALS */ +# if defined (HAVE_BSD_SIGNALS) + long omask; +# else /* !HAVE_BSD_SIGNALS */ + sighandler_cxt dummy_cxt; /* needed for rl_set_sighandler call */ +# endif /* !HAVE_BSD_SIGNALS */ +#endif /* !HAVE_POSIX_SIGNALS */ + + RL_SETSTATE(RL_STATE_SIGHANDLER); + +#if !defined (HAVE_BSD_SIGNALS) && !defined (HAVE_POSIX_SIGNALS) + /* Since the signal will not be blocked while we are in the signal + handler, ignore it until rl_clear_signals resets the catcher. */ +# if defined (SIGALRM) + if (sig == SIGINT || sig == SIGALRM) +# else + if (sig == SIGINT) +# endif + rl_set_sighandler (sig, SIG_IGN, &dummy_cxt); +#endif /* !HAVE_BSD_SIGNALS && !HAVE_POSIX_SIGNALS */ + + switch (sig) + { + case SIGINT: + rl_free_line_state (); + /* FALLTHROUGH */ + + case SIGTERM: +#if defined (SIGTSTP) + case SIGTSTP: + case SIGTTOU: + case SIGTTIN: +#endif /* SIGTSTP */ +#if defined (SIGALRM) + case SIGALRM: +#endif +#if defined (SIGQUIT) + case SIGQUIT: +#endif + rl_cleanup_after_signal (); + +#if defined (HAVE_POSIX_SIGNALS) + sigemptyset (&set); + sigprocmask (SIG_BLOCK, (sigset_t *)NULL, &set); + sigdelset (&set, sig); +#else /* !HAVE_POSIX_SIGNALS */ +# if defined (HAVE_BSD_SIGNALS) + omask = sigblock (0); +# endif /* HAVE_BSD_SIGNALS */ +#endif /* !HAVE_POSIX_SIGNALS */ + +#if defined (__EMX__) + signal (sig, SIG_ACK); +#endif + +#if defined (HAVE_KILL) + kill (getpid (), sig); +#else + raise (sig); /* assume we have raise */ +#endif + + /* Let the signal that we just sent through. */ +#if defined (HAVE_POSIX_SIGNALS) + sigprocmask (SIG_SETMASK, &set, (sigset_t *)NULL); +#else /* !HAVE_POSIX_SIGNALS */ +# if defined (HAVE_BSD_SIGNALS) + sigsetmask (omask & ~(sigmask (sig))); +# endif /* HAVE_BSD_SIGNALS */ +#endif /* !HAVE_POSIX_SIGNALS */ + + rl_reset_after_signal (); + } + + RL_UNSETSTATE(RL_STATE_SIGHANDLER); + SIGHANDLER_RETURN; +} + +#if defined (SIGWINCH) +static RETSIGTYPE +rl_sigwinch_handler (sig) + int sig; +{ + SigHandler *oh; + +#if defined (MUST_REINSTALL_SIGHANDLERS) + sighandler_cxt dummy_winch; + + /* We don't want to change old_winch -- it holds the state of SIGWINCH + disposition set by the calling application. We need this state + because we call the application's SIGWINCH handler after updating + our own idea of the screen size. */ + rl_set_sighandler (SIGWINCH, rl_sigwinch_handler, &dummy_winch); +#endif + + RL_SETSTATE(RL_STATE_SIGHANDLER); + rl_resize_terminal (); + + /* If another sigwinch handler has been installed, call it. */ + oh = (SigHandler *)old_winch.sa_handler; + if (oh && oh != (SigHandler *)SIG_IGN && oh != (SigHandler *)SIG_DFL) + (*oh) (sig); + + RL_UNSETSTATE(RL_STATE_SIGHANDLER); + SIGHANDLER_RETURN; +} +#endif /* SIGWINCH */ + +/* Functions to manage signal handling. */ + +#if !defined (HAVE_POSIX_SIGNALS) +static int +rl_sigaction (sig, nh, oh) + int sig; + sighandler_cxt *nh, *oh; +{ + oh->sa_handler = signal (sig, nh->sa_handler); + return 0; +} +#endif /* !HAVE_POSIX_SIGNALS */ + +/* Set up a readline-specific signal handler, saving the old signal + information in OHANDLER. Return the old signal handler, like + signal(). */ +static SigHandler * +rl_set_sighandler (sig, handler, ohandler) + int sig; + SigHandler *handler; + sighandler_cxt *ohandler; +{ + sighandler_cxt old_handler; +#if defined (HAVE_POSIX_SIGNALS) + struct sigaction act; + + act.sa_handler = handler; + act.sa_flags = (sig == SIGWINCH) ? SA_RESTART : 0; + sigemptyset (&act.sa_mask); + sigemptyset (&ohandler->sa_mask); + sigaction (sig, &act, &old_handler); +#else + old_handler.sa_handler = (SigHandler *)signal (sig, handler); +#endif /* !HAVE_POSIX_SIGNALS */ + + /* XXX -- assume we have memcpy */ + /* If rl_set_signals is called twice in a row, don't set the old handler to + rl_signal_handler, because that would cause infinite recursion. */ + if (handler != rl_signal_handler || old_handler.sa_handler != rl_signal_handler) + memcpy (ohandler, &old_handler, sizeof (sighandler_cxt)); + + return (ohandler->sa_handler); +} + +static void +rl_maybe_set_sighandler (sig, handler, ohandler) + int sig; + SigHandler *handler; + sighandler_cxt *ohandler; +{ + sighandler_cxt dummy; + SigHandler *oh; + + sigemptyset (&dummy.sa_mask); + oh = rl_set_sighandler (sig, handler, ohandler); + if (oh == (SigHandler *)SIG_IGN) + rl_sigaction (sig, ohandler, &dummy); +} + +int +rl_set_signals () +{ + sighandler_cxt dummy; + SigHandler *oh; +#if defined (HAVE_POSIX_SIGNALS) + static int sigmask_set = 0; + static sigset_t bset, oset; +#endif + +#if defined (HAVE_POSIX_SIGNALS) + if (rl_catch_signals && sigmask_set == 0) + { + sigemptyset (&bset); + + sigaddset (&bset, SIGINT); + sigaddset (&bset, SIGINT); +#if defined (SIGQUIT) + sigaddset (&bset, SIGQUIT); +#endif +#if defined (SIGALRM) + sigaddset (&bset, SIGALRM); +#endif +#if defined (SIGTSTP) + sigaddset (&bset, SIGTSTP); +#endif +#if defined (SIGTTIN) + sigaddset (&bset, SIGTTIN); +#endif +#if defined (SIGTTOU) + sigaddset (&bset, SIGTTOU); +#endif + sigmask_set = 1; + } +#endif /* HAVE_POSIX_SIGNALS */ + + if (rl_catch_signals && signals_set_flag == 0) + { +#if defined (HAVE_POSIX_SIGNALS) + sigemptyset (&oset); + sigprocmask (SIG_BLOCK, &bset, &oset); +#endif + + rl_maybe_set_sighandler (SIGINT, rl_signal_handler, &old_int); + rl_maybe_set_sighandler (SIGTERM, rl_signal_handler, &old_term); +#if defined (SIGQUIT) + rl_maybe_set_sighandler (SIGQUIT, rl_signal_handler, &old_quit); +#endif + +#if defined (SIGALRM) + oh = rl_set_sighandler (SIGALRM, rl_signal_handler, &old_alrm); + if (oh == (SigHandler *)SIG_IGN) + rl_sigaction (SIGALRM, &old_alrm, &dummy); +#if defined (HAVE_POSIX_SIGNALS) && defined (SA_RESTART) + /* If the application using readline has already installed a signal + handler with SA_RESTART, SIGALRM will cause reads to be restarted + automatically, so readline should just get out of the way. Since + we tested for SIG_IGN above, we can just test for SIG_DFL here. */ + if (oh != (SigHandler *)SIG_DFL && (old_alrm.sa_flags & SA_RESTART)) + rl_sigaction (SIGALRM, &old_alrm, &dummy); +#endif /* HAVE_POSIX_SIGNALS */ +#endif /* SIGALRM */ + +#if defined (SIGTSTP) + rl_maybe_set_sighandler (SIGTSTP, rl_signal_handler, &old_tstp); +#endif /* SIGTSTP */ + +#if defined (SIGTTOU) + rl_maybe_set_sighandler (SIGTTOU, rl_signal_handler, &old_ttou); +#endif /* SIGTTOU */ + +#if defined (SIGTTIN) + rl_maybe_set_sighandler (SIGTTIN, rl_signal_handler, &old_ttin); +#endif /* SIGTTIN */ + + signals_set_flag = 1; + +#if defined (HAVE_POSIX_SIGNALS) + sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL); +#endif + } + +#if defined (SIGWINCH) + if (rl_catch_sigwinch && sigwinch_set_flag == 0) + { + rl_maybe_set_sighandler (SIGWINCH, rl_sigwinch_handler, &old_winch); + sigwinch_set_flag = 1; + } +#endif /* SIGWINCH */ + + return 0; +} + +int +rl_clear_signals () +{ + sighandler_cxt dummy; + + if (rl_catch_signals && signals_set_flag == 1) + { + sigemptyset (&dummy.sa_mask); + + rl_sigaction (SIGINT, &old_int, &dummy); + rl_sigaction (SIGTERM, &old_term, &dummy); +#if defined (SIGQUIT) + rl_sigaction (SIGQUIT, &old_quit, &dummy); +#endif +#if defined (SIGALRM) + rl_sigaction (SIGALRM, &old_alrm, &dummy); +#endif + +#if defined (SIGTSTP) + rl_sigaction (SIGTSTP, &old_tstp, &dummy); +#endif /* SIGTSTP */ + +#if defined (SIGTTOU) + rl_sigaction (SIGTTOU, &old_ttou, &dummy); +#endif /* SIGTTOU */ + +#if defined (SIGTTIN) + rl_sigaction (SIGTTIN, &old_ttin, &dummy); +#endif /* SIGTTIN */ + + signals_set_flag = 0; + } + +#if defined (SIGWINCH) + if (rl_catch_sigwinch && sigwinch_set_flag == 1) + { + sigemptyset (&dummy.sa_mask); + rl_sigaction (SIGWINCH, &old_winch, &dummy); + sigwinch_set_flag = 0; + } +#endif + + return 0; +} + +/* Clean up the terminal and readline state after catching a signal, before + resending it to the calling application. */ +void +rl_cleanup_after_signal () +{ + _rl_clean_up_for_exit (); + if (rl_deprep_term_function) + (*rl_deprep_term_function) (); + rl_clear_pending_input (); + rl_clear_signals (); +} + +/* Reset the terminal and readline state after a signal handler returns. */ +void +rl_reset_after_signal () +{ + if (rl_prep_term_function) + (*rl_prep_term_function) (_rl_meta_flag); + rl_set_signals (); +} + +/* Free up the readline variable line state for the current line (undo list, + any partial history entry, any keyboard macros in progress, and any + numeric arguments in process) after catching a signal, before calling + rl_cleanup_after_signal(). */ +void +rl_free_line_state () +{ + register HIST_ENTRY *entry; + + rl_free_undo_list (); + + entry = current_history (); + if (entry) + entry->data = (char *)NULL; + + _rl_kill_kbd_macro (); + rl_clear_message (); + _rl_reset_argument (); +} + +#endif /* HANDLE_SIGNALS */ diff --git a/extra/readline/tcap.h b/extra/readline/tcap.h new file mode 100644 index 00000000000..d1e212869ce --- /dev/null +++ b/extra/readline/tcap.h @@ -0,0 +1,60 @@ +/* tcap.h -- termcap library functions and variables. */ + +/* Copyright (C) 1996 Free Software Foundation, Inc. + + This file contains the Readline Library (the Library), a set of + routines for providing Emacs style line input to programs that ask + for it. + + The Library is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + The 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 + General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#if !defined (_RLTCAP_H_) +#define _RLTCAP_H_ + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#if defined (HAVE_TERMCAP_H) +# if defined (__linux__) && !defined (SPEED_T_IN_SYS_TYPES) +# include "rltty.h" +# endif +# include <termcap.h> +#else + +/* On Solaris2, sys/types.h #includes sys/reg.h, which #defines PC. + Unfortunately, PC is a global variable used by the termcap library. */ +#ifdef PC +# undef PC +#endif + +extern char PC; +extern char *UP, *BC; + +extern short ospeed; + +extern int tgetent (); +extern int tgetflag (); +extern int tgetnum (); +extern char *tgetstr (); + +extern int tputs (); + +extern char *tgoto (); + +#endif /* HAVE_TERMCAP_H */ + +#endif /* !_RLTCAP_H_ */ diff --git a/extra/readline/terminal.c b/extra/readline/terminal.c new file mode 100644 index 00000000000..43086d42e2d --- /dev/null +++ b/extra/readline/terminal.c @@ -0,0 +1,735 @@ +/* terminal.c -- controlling the terminal with termcap. */ + +/* Copyright (C) 1996-2006 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <sys/types.h> +#include "posixstat.h" +#include <fcntl.h> +#if defined (HAVE_SYS_FILE_H) +# include <sys/file.h> +#endif /* HAVE_SYS_FILE_H */ + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#if defined (HAVE_LOCALE_H) +# include <locale.h> +#endif + +#include <stdio.h> + +/* System-specific feature definitions and include files. */ +#include "rldefs.h" + +#if defined (GWINSZ_IN_SYS_IOCTL) && !defined (TIOCGWINSZ) +# include <sys/ioctl.h> +#endif /* GWINSZ_IN_SYS_IOCTL && !TIOCGWINSZ */ + +#include "rltty.h" +#include "tcap.h" + +/* Some standard library routines. */ +#include "readline.h" +#include "history.h" + +#include "rlprivate.h" +#include "rlshell.h" +#include "xmalloc.h" + +#if defined (__MINGW32__) +# include <windows.h> +# include <wincon.h> + +static void _win_get_screensize PARAMS((int *, int *)); +#endif + +#if defined (__EMX__) +static void _emx_get_screensize PARAMS((int *, int *)); +#endif + +#define CUSTOM_REDISPLAY_FUNC() (rl_redisplay_function != rl_redisplay) +#define CUSTOM_INPUT_FUNC() (rl_getc_function != rl_getc) + +/* If the calling application sets this to a non-zero value, readline will + use the $LINES and $COLUMNS environment variables to set its idea of the + window size before interrogating the kernel. */ +int rl_prefer_env_winsize = 0; + +/* **************************************************************** */ +/* */ +/* Terminal and Termcap */ +/* */ +/* **************************************************************** */ + +static char *term_buffer = (char *)NULL; +static char *term_string_buffer = (char *)NULL; + +static int tcap_initialized; + +#if !defined (__linux__) +# if defined (__EMX__) || defined (NEED_EXTERN_PC) +extern +# endif /* __EMX__ || NEED_EXTERN_PC */ +char PC, *BC, *UP; +#endif /* __linux__ */ + +/* Some strings to control terminal actions. These are output by tputs (). */ +const char *_rl_term_clreol; +const char *_rl_term_clrpag; +const char *_rl_term_cr; +const char *_rl_term_backspace; +char _rl_term_backspace_default[2] = { '\b', 0 }; +const char *_rl_term_goto; +const char *_rl_term_pc; + +/* Non-zero if we determine that the terminal can do character insertion. */ +int _rl_terminal_can_insert = 0; + +/* How to insert characters. */ +const char *_rl_term_im; +const char *_rl_term_ei; +const char *_rl_term_ic; +const char *_rl_term_ip; +const char *_rl_term_IC; + +/* How to delete characters. */ +const char *_rl_term_dc; +const char *_rl_term_DC; + +const char *_rl_term_forward_char; + +/* How to go up a line. */ +const char *_rl_term_up; +char _rl_term_up_default[2] = { 0, 0 }; + +/* A visible bell; char if the terminal can be made to flash the screen. */ +static const char *_rl_visible_bell; + +/* Non-zero means the terminal can auto-wrap lines. */ +int _rl_term_autowrap = -1; + +/* Non-zero means that this terminal has a meta key. */ +static int term_has_meta; + +/* The sequences to write to turn on and off the meta key, if this + terminal has one. */ +static const char *_rl_term_mm; +static const char *_rl_term_mo; + +/* The key sequences output by the arrow keys, if this terminal has any. */ +static const char *_rl_term_ku; +static const char *_rl_term_kd; +static const char *_rl_term_kr; +static const char *_rl_term_kl; + +/* How to initialize and reset the arrow keys, if this terminal has any. */ +static const char *_rl_term_ks; +static const char *_rl_term_ke; + +/* The key sequences sent by the Home and End keys, if any. */ +static const char *_rl_term_kh; +static const char *_rl_term_kH; +static const char *_rl_term_at7; /* @7 */ + +/* Delete key */ +static const char *_rl_term_kD; + +/* Insert key */ +static const char *_rl_term_kI; + +/* Cursor control */ +static const char *_rl_term_vs; /* very visible */ +static const char *_rl_term_ve; /* normal */ + +static void bind_termcap_arrow_keys PARAMS((Keymap)); + +/* Variables that hold the screen dimensions, used by the display code. */ +int _rl_screenwidth, _rl_screenheight, _rl_screenchars; + +/* Non-zero means the user wants to enable the keypad. */ +int _rl_enable_keypad; + +/* Non-zero means the user wants to enable a meta key. */ +int _rl_enable_meta = 1; + +#if defined (__EMX__) +static void +_emx_get_screensize (swp, shp) + int *swp, *shp; +{ + int sz[2]; + + _scrsize (sz); + + if (swp) + *swp = sz[0]; + if (shp) + *shp = sz[1]; +} +#endif + +#if defined (__MINGW32__) +static void +_win_get_screensize (swp, shp) + int *swp, *shp; +{ + HANDLE hConOut; + CONSOLE_SCREEN_BUFFER_INFO scr; + + hConOut = GetStdHandle (STD_OUTPUT_HANDLE); + if (hConOut != INVALID_HANDLE_VALUE) + { + if (GetConsoleScreenBufferInfo (hConOut, &scr)) + { + *swp = scr.dwSize.X; + *shp = scr.srWindow.Bottom - scr.srWindow.Top + 1; + } + } +} +#endif + +/* Get readline's idea of the screen size. TTY is a file descriptor open + to the terminal. If IGNORE_ENV is true, we do not pay attention to the + values of $LINES and $COLUMNS. The tests for TERM_STRING_BUFFER being + non-null serve to check whether or not we have initialized termcap. */ +void +_rl_get_screen_size (tty, ignore_env) + int tty, ignore_env; +{ + char *ss; +#if defined (TIOCGWINSZ) + struct winsize window_size; +#endif /* TIOCGWINSZ */ + int wr, wc; + + wr = wc = -1; +#if defined (TIOCGWINSZ) + if (ioctl (tty, TIOCGWINSZ, &window_size) == 0) + { + wc = (int) window_size.ws_col; + wr = (int) window_size.ws_row; + } +#endif /* TIOCGWINSZ */ + +#if defined (__EMX__) + _emx_get_screensize (&wc, &wr); +#elif defined (__MINGW32__) + _win_get_screensize (&wc, &wr); +#endif + + if (ignore_env || rl_prefer_env_winsize == 0) + { + _rl_screenwidth = wc; + _rl_screenheight = wr; + } + else + _rl_screenwidth = _rl_screenheight = -1; + + /* Environment variable COLUMNS overrides setting of "co" if IGNORE_ENV + is unset. If we prefer the environment, check it first before + assigning the value returned by the kernel. */ + if (_rl_screenwidth <= 0) + { + if (ignore_env == 0 && (ss = sh_get_env_value ("COLUMNS"))) + _rl_screenwidth = atoi (ss); + + if (_rl_screenwidth <= 0) + _rl_screenwidth = wc; + +#if !defined (__DJGPP__) + if (_rl_screenwidth <= 0 && term_string_buffer) + _rl_screenwidth = tgetnum ((char *)"co"); +#endif + } + + /* Environment variable LINES overrides setting of "li" if IGNORE_ENV + is unset. */ + if (_rl_screenheight <= 0) + { + if (ignore_env == 0 && (ss = sh_get_env_value ("LINES"))) + _rl_screenheight = atoi (ss); + + if (_rl_screenheight <= 0) + _rl_screenheight = wr; + +#if !defined (__DJGPP__) + if (_rl_screenheight <= 0 && term_string_buffer) + _rl_screenheight = tgetnum ((char *)"li"); +#endif + } + + /* If all else fails, default to 80x24 terminal. */ + if (_rl_screenwidth <= 1) + _rl_screenwidth = 80; + + if (_rl_screenheight <= 0) + _rl_screenheight = 24; + + /* If we're being compiled as part of bash, set the environment + variables $LINES and $COLUMNS to new values. Otherwise, just + do a pair of putenv () or setenv () calls. */ + sh_set_lines_and_columns (_rl_screenheight, _rl_screenwidth); + + if (_rl_term_autowrap == 0) + _rl_screenwidth--; + + _rl_screenchars = _rl_screenwidth * _rl_screenheight; +} + +void +_rl_set_screen_size (rows, cols) + int rows, cols; +{ + if (_rl_term_autowrap == -1) + _rl_init_terminal_io (rl_terminal_name); + + if (rows > 0) + _rl_screenheight = rows; + if (cols > 0) + { + _rl_screenwidth = cols; + if (_rl_term_autowrap == 0) + _rl_screenwidth--; + } + + if (rows > 0 || cols > 0) + _rl_screenchars = _rl_screenwidth * _rl_screenheight; +} + +void +rl_set_screen_size (rows, cols) + int rows, cols; +{ + _rl_set_screen_size (rows, cols); +} + +void +rl_get_screen_size (rows, cols) + int *rows, *cols; +{ + if (rows) + *rows = _rl_screenheight; + if (cols) + *cols = _rl_screenwidth; +} + +void +rl_reset_screen_size () +{ + _rl_get_screen_size (fileno (rl_instream), 0); +} + +void +rl_resize_terminal () +{ + if (readline_echoing_p) + { + _rl_get_screen_size (fileno (rl_instream), 1); + if (CUSTOM_REDISPLAY_FUNC ()) + rl_forced_update_display (); + else + _rl_redisplay_after_sigwinch (); + } +} + +struct _tc_string { + const char *tc_var; + const char **tc_value; +}; + +/* This should be kept sorted, just in case we decide to change the + search algorithm to something smarter. */ +static struct _tc_string tc_strings[] = +{ + { "@7", &_rl_term_at7 }, + { "DC", &_rl_term_DC }, + { "IC", &_rl_term_IC }, + { "ce", &_rl_term_clreol }, + { "cl", &_rl_term_clrpag }, + { "cr", &_rl_term_cr }, + { "dc", &_rl_term_dc }, + { "ei", &_rl_term_ei }, + { "ic", &_rl_term_ic }, + { "im", &_rl_term_im }, + { "kD", &_rl_term_kD }, /* delete */ + { "kH", &_rl_term_kH }, /* home down ?? */ + { "kI", &_rl_term_kI }, /* insert */ + { "kd", &_rl_term_kd }, + { "ke", &_rl_term_ke }, /* end keypad mode */ + { "kh", &_rl_term_kh }, /* home */ + { "kl", &_rl_term_kl }, + { "kr", &_rl_term_kr }, + { "ks", &_rl_term_ks }, /* start keypad mode */ + { "ku", &_rl_term_ku }, + { "le", &_rl_term_backspace }, + { "mm", &_rl_term_mm }, + { "mo", &_rl_term_mo }, + { "nd", &_rl_term_forward_char }, + { "pc", &_rl_term_pc }, + { "up", &_rl_term_up }, + { "vb", &_rl_visible_bell }, + { "vs", &_rl_term_vs }, + { "ve", &_rl_term_ve }, +}; + +#define NUM_TC_STRINGS (sizeof (tc_strings) / sizeof (struct _tc_string)) + +/* Read the desired terminal capability strings into BP. The capabilities + are described in the TC_STRINGS table. */ +static void +get_term_capabilities (bp) + char **bp; +{ +#if !defined (__DJGPP__) /* XXX - doesn't DJGPP have a termcap library? */ + register unsigned int i; + + for (i = 0; i < NUM_TC_STRINGS; i++) + *(tc_strings[i].tc_value) = tgetstr ((char *)tc_strings[i].tc_var, bp); +#endif + tcap_initialized = 1; +} + +int +_rl_init_terminal_io (terminal_name) + const char *terminal_name; +{ + const char *term; + char *buffer; + int tty, tgetent_ret; + + term = terminal_name ? terminal_name : sh_get_env_value ("TERM"); + _rl_term_clrpag = _rl_term_cr = _rl_term_clreol = (char *)NULL; + tty = rl_instream ? fileno (rl_instream) : 0; + + if (term == 0) + term = "dumb"; + + /* I've separated this out for later work on not calling tgetent at all + if the calling application has supplied a custom redisplay function, + (and possibly if the application has supplied a custom input function). */ + if (CUSTOM_REDISPLAY_FUNC()) + { + tgetent_ret = -1; + } + else + { + if (term_string_buffer == 0) + term_string_buffer = (char *)xmalloc(2032); + + if (term_buffer == 0) + term_buffer = (char *)xmalloc(4080); + + buffer = term_string_buffer; + + tgetent_ret = tgetent (term_buffer, term); + } + + if (tgetent_ret <= 0) + { + FREE (term_string_buffer); + FREE (term_buffer); + buffer = term_buffer = term_string_buffer = (char *)NULL; + + _rl_term_autowrap = 0; /* used by _rl_get_screen_size */ + + /* Allow calling application to set default height and width, using + rl_set_screen_size */ + if (_rl_screenwidth <= 0 || _rl_screenheight <= 0) + { +#if defined (__EMX__) + _emx_get_screensize (&_rl_screenwidth, &_rl_screenheight); + _rl_screenwidth--; +#else /* !__EMX__ */ + _rl_get_screen_size (tty, 0); +#endif /* !__EMX__ */ + } + + /* Defaults. */ + if (_rl_screenwidth <= 0 || _rl_screenheight <= 0) + { + _rl_screenwidth = 79; + _rl_screenheight = 24; + } + + /* Everything below here is used by the redisplay code (tputs). */ + _rl_screenchars = _rl_screenwidth * _rl_screenheight; + _rl_term_cr = "\r"; + _rl_term_im = _rl_term_ei = _rl_term_ic = _rl_term_IC = (char *)NULL; + _rl_term_up = _rl_term_dc = _rl_term_DC = _rl_visible_bell = (char *)NULL; + _rl_term_ku = _rl_term_kd = _rl_term_kl = _rl_term_kr = (char *)NULL; + _rl_term_kh = _rl_term_kH = _rl_term_kI = _rl_term_kD = (char *)NULL; + _rl_term_ks = _rl_term_ke = _rl_term_at7 = (char *)NULL; + _rl_term_mm = _rl_term_mo = (char *)NULL; + _rl_term_ve = _rl_term_vs = (char *)NULL; + _rl_term_forward_char = (char *)NULL; + _rl_terminal_can_insert = term_has_meta = 0; + + /* Reasonable defaults for tgoto(). Readline currently only uses + tgoto if _rl_term_IC or _rl_term_DC is defined, but just in case we + change that later... */ + PC = '\0'; + _rl_term_backspace = _rl_term_backspace_default; + BC = (char*)_rl_term_backspace; + UP = (char*)_rl_term_up; + + return 0; + } + + get_term_capabilities (&buffer); + + /* Set up the variables that the termcap library expects the application + to provide. */ + PC = _rl_term_pc ? *_rl_term_pc : 0; + BC = (char*)_rl_term_backspace; + UP = (char*)_rl_term_up; + + if (!_rl_term_cr) + _rl_term_cr = "\r"; + + _rl_term_autowrap = tgetflag ((char *)"am") && tgetflag ((char *)"xn"); + + /* Allow calling application to set default height and width, using + rl_set_screen_size */ + if (_rl_screenwidth <= 0 || _rl_screenheight <= 0) + _rl_get_screen_size (tty, 0); + + /* "An application program can assume that the terminal can do + character insertion if *any one of* the capabilities `IC', + `im', `ic' or `ip' is provided." But we can't do anything if + only `ip' is provided, so... */ + _rl_terminal_can_insert = (_rl_term_IC || _rl_term_im || _rl_term_ic); + + /* Check to see if this terminal has a meta key and clear the capability + variables if there is none. */ + term_has_meta = (tgetflag ((char *)"km") || tgetflag ((char *)"MT")); + if (!term_has_meta) + _rl_term_mm = _rl_term_mo = (char *)NULL; + + /* Attempt to find and bind the arrow keys. Do not override already + bound keys in an overzealous attempt, however. */ + + bind_termcap_arrow_keys (emacs_standard_keymap); + +#if defined (VI_MODE) + bind_termcap_arrow_keys (vi_movement_keymap); + bind_termcap_arrow_keys (vi_insertion_keymap); +#endif /* VI_MODE */ + + return 0; +} + +/* Bind the arrow key sequences from the termcap description in MAP. */ +static void +bind_termcap_arrow_keys (map) + Keymap map; +{ + Keymap xkeymap; + + xkeymap = _rl_keymap; + _rl_keymap = map; + + rl_bind_keyseq_if_unbound (_rl_term_ku, rl_get_previous_history); + rl_bind_keyseq_if_unbound (_rl_term_kd, rl_get_next_history); + rl_bind_keyseq_if_unbound (_rl_term_kr, rl_forward_char); + rl_bind_keyseq_if_unbound (_rl_term_kl, rl_backward_char); + + rl_bind_keyseq_if_unbound (_rl_term_kh, rl_beg_of_line); /* Home */ + rl_bind_keyseq_if_unbound (_rl_term_at7, rl_end_of_line); /* End */ + + rl_bind_keyseq_if_unbound (_rl_term_kD, rl_delete); + + _rl_keymap = xkeymap; +} + +const char * +rl_get_termcap (cap) + const char *cap; +{ + register unsigned int i; + + if (tcap_initialized == 0) + return ((char *)NULL); + for (i = 0; i < NUM_TC_STRINGS; i++) + { + if (tc_strings[i].tc_var[0] == cap[0] && strcmp (tc_strings[i].tc_var, cap) == 0) + return *(tc_strings[i].tc_value); + } + return ((char *)NULL); +} + +/* Re-initialize the terminal considering that the TERM/TERMCAP variable + has changed. */ +int +rl_reset_terminal (terminal_name) + const char *terminal_name; +{ + _rl_screenwidth = _rl_screenheight = 0; + _rl_init_terminal_io (terminal_name); + return 0; +} + +/* A function for the use of tputs () */ +#ifdef _MINIX +void +_rl_output_character_function (c) + int c; +{ + putc (c, _rl_out_stream); +} +#else /* !_MINIX */ +int +_rl_output_character_function (c) + int c; +{ + return putc (c, _rl_out_stream); +} +#endif /* !_MINIX */ + +/* Write COUNT characters from STRING to the output stream. */ +void +_rl_output_some_chars (string, count) + const char *string; + int count; +{ + if (fwrite (string, 1, count, _rl_out_stream) != (size_t)count) + fprintf(stderr, "Write failed\n"); +} + +/* Move the cursor back. */ +int +_rl_backspace (count) + int count; +{ + register int i; + + if (_rl_term_backspace) + for (i = 0; i < count; i++) + tputs (_rl_term_backspace, 1, _rl_output_character_function); + else + for (i = 0; i < count; i++) + putc ('\b', _rl_out_stream); + return 0; +} + +/* Move to the start of the next line. */ +int +rl_crlf () +{ +#if defined (NEW_TTY_DRIVER) + if (_rl_term_cr) + tputs (_rl_term_cr, 1, _rl_output_character_function); +#endif /* NEW_TTY_DRIVER */ + putc ('\n', _rl_out_stream); + return 0; +} + +/* Ring the terminal bell. */ +int +rl_ding () +{ + if (readline_echoing_p) + { + switch (_rl_bell_preference) + { + case NO_BELL: + default: + break; + case VISIBLE_BELL: + if (_rl_visible_bell) + { + tputs (_rl_visible_bell, 1, _rl_output_character_function); + break; + } + /* FALLTHROUGH */ + case AUDIBLE_BELL: + fprintf (stderr, "\007"); + fflush (stderr); + break; + } + return (0); + } + return (-1); +} + +/* **************************************************************** */ +/* */ +/* Controlling the Meta Key and Keypad */ +/* */ +/* **************************************************************** */ + +void +_rl_enable_meta_key () +{ +#if !defined (__DJGPP__) + if (term_has_meta && _rl_term_mm) + tputs (_rl_term_mm, 1, _rl_output_character_function); +#endif +} + +void +_rl_control_keypad (on) + int on; +{ +#if !defined (__DJGPP__) + if (on && _rl_term_ks) + tputs (_rl_term_ks, 1, _rl_output_character_function); + else if (!on && _rl_term_ke) + tputs (_rl_term_ke, 1, _rl_output_character_function); +#endif +} + +/* **************************************************************** */ +/* */ +/* Controlling the Cursor */ +/* */ +/* **************************************************************** */ + +/* Set the cursor appropriately depending on IM, which is one of the + insert modes (insert or overwrite). Insert mode gets the normal + cursor. Overwrite mode gets a very visible cursor. Only does + anything if we have both capabilities. */ +void +_rl_set_cursor (im, force) + int im, force; +{ + if (_rl_term_ve && _rl_term_vs) + { + if (force || im != rl_insert_mode) + { + if (im == RL_IM_OVERWRITE) + tputs (_rl_term_vs, 1, _rl_output_character_function); + else + tputs (_rl_term_ve, 1, _rl_output_character_function); + } + } +} diff --git a/extra/readline/text.c b/extra/readline/text.c new file mode 100644 index 00000000000..90ad7787000 --- /dev/null +++ b/extra/readline/text.c @@ -0,0 +1,1639 @@ +/* text.c -- text handling commands for readline. */ + +/* Copyright (C) 1987-2005 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#if defined (HAVE_LOCALE_H) +# include <locale.h> +#endif + +#include <stdio.h> + +/* System-specific feature definitions and include files. */ +#include "rldefs.h" +#include "rlmbutil.h" + +#if defined (__EMX__) +# define INCL_DOSPROCESS +# include <os2.h> +#endif /* __EMX__ */ + +/* Some standard library routines. */ +#include "readline.h" +#include "history.h" + +#include "rlprivate.h" +#include "rlshell.h" +#include "xmalloc.h" + +/* Forward declarations. */ +static int rl_change_case PARAMS((int, int)); +static int _rl_char_search PARAMS((int, int, int)); + +#if defined (READLINE_CALLBACKS) +static int _rl_insert_next_callback PARAMS((_rl_callback_generic_arg *)); +static int _rl_char_search_callback PARAMS((_rl_callback_generic_arg *)); +#endif + +/* **************************************************************** */ +/* */ +/* Insert and Delete */ +/* */ +/* **************************************************************** */ + +/* Insert a string of text into the line at point. This is the only + way that you should do insertion. _rl_insert_char () calls this + function. Returns the number of characters inserted. */ +int +rl_insert_text (string) + const char *string; +{ + register int i, l; + + l = (string && *string) ? strlen (string) : 0; + if (l == 0) + return 0; + + if (rl_end + l >= rl_line_buffer_len) + rl_extend_line_buffer (rl_end + l); + + for (i = rl_end; i >= rl_point; i--) + rl_line_buffer[i + l] = rl_line_buffer[i]; + strncpy (rl_line_buffer + rl_point, string, l); + + /* Remember how to undo this if we aren't undoing something. */ + if (_rl_doing_an_undo == 0) + { + /* If possible and desirable, concatenate the undos. */ + if ((l == 1) && + rl_undo_list && + (rl_undo_list->what == UNDO_INSERT) && + (rl_undo_list->end == rl_point) && + (rl_undo_list->end - rl_undo_list->start < 20)) + rl_undo_list->end++; + else + rl_add_undo (UNDO_INSERT, rl_point, rl_point + l, (char *)NULL); + } + rl_point += l; + rl_end += l; + rl_line_buffer[rl_end] = '\0'; + return l; +} + +/* Delete the string between FROM and TO. FROM is inclusive, TO is not. + Returns the number of characters deleted. */ +int +rl_delete_text (from, to) + int from, to; +{ + register char *text; + register int diff, i; + + /* Fix it if the caller is confused. */ + if (from > to) + SWAP (from, to); + + /* fix boundaries */ + if (to > rl_end) + { + to = rl_end; + if (from > to) + from = to; + } + if (from < 0) + from = 0; + + text = rl_copy_text (from, to); + + /* Some versions of strncpy() can't handle overlapping arguments. */ + diff = to - from; + for (i = from; i < rl_end - diff; i++) + rl_line_buffer[i] = rl_line_buffer[i + diff]; + + /* Remember how to undo this delete. */ + if (_rl_doing_an_undo == 0) + rl_add_undo (UNDO_DELETE, from, to, text); + else + free (text); + + rl_end -= diff; + rl_line_buffer[rl_end] = '\0'; + return (diff); +} + +/* Fix up point so that it is within the line boundaries after killing + text. If FIX_MARK_TOO is non-zero, the mark is forced within line + boundaries also. */ + +#define _RL_FIX_POINT(x) \ + do { \ + if (x > rl_end) \ + x = rl_end; \ + else if (x < 0) \ + x = 0; \ + } while (0) + +void +_rl_fix_point (fix_mark_too) + int fix_mark_too; +{ + _RL_FIX_POINT (rl_point); + if (fix_mark_too) + _RL_FIX_POINT (rl_mark); +} +#undef _RL_FIX_POINT + +/* Replace the contents of the line buffer between START and END with + TEXT. The operation is undoable. To replace the entire line in an + undoable mode, use _rl_replace_text(text, 0, rl_end); */ +int +_rl_replace_text (text, start, end) + const char *text; + int start, end; +{ + int n; + + rl_begin_undo_group (); + rl_delete_text (start, end + 1); + rl_point = start; + n = rl_insert_text (text); + rl_end_undo_group (); + + return n; +} + +/* Replace the current line buffer contents with TEXT. If CLEAR_UNDO is + non-zero, we free the current undo list. */ +void +rl_replace_line (text, clear_undo) + const char *text; + int clear_undo; +{ + int len; + + len = strlen (text); + if (len >= rl_line_buffer_len) + rl_extend_line_buffer (len); + strcpy (rl_line_buffer, text); + rl_end = len; + + if (clear_undo) + rl_free_undo_list (); + + _rl_fix_point (1); +} + +/* **************************************************************** */ +/* */ +/* Readline character functions */ +/* */ +/* **************************************************************** */ + +/* This is not a gap editor, just a stupid line input routine. No hair + is involved in writing any of the functions, and none should be. */ + +/* Note that: + + rl_end is the place in the string that we would place '\0'; + i.e., it is always safe to place '\0' there. + + rl_point is the place in the string where the cursor is. Sometimes + this is the same as rl_end. + + Any command that is called interactively receives two arguments. + The first is a count: the numeric arg pased to this command. + The second is the key which invoked this command. +*/ + +/* **************************************************************** */ +/* */ +/* Movement Commands */ +/* */ +/* **************************************************************** */ + +/* Note that if you `optimize' the display for these functions, you cannot + use said functions in other functions which do not do optimizing display. + I.e., you will have to update the data base for rl_redisplay, and you + might as well let rl_redisplay do that job. */ + +/* Move forward COUNT bytes. */ +int +rl_forward_byte (count, key) + int count, key; +{ + if (count < 0) + return (rl_backward_byte (-count, key)); + + if (count > 0) + { + int end = rl_point + count; +#if defined (VI_MODE) + int lend = rl_end > 0 ? rl_end - (rl_editing_mode == vi_mode) : rl_end; +#else + int lend = rl_end; +#endif + + if (end > lend) + { + rl_point = lend; + rl_ding (); + } + else + rl_point = end; + } + + if (rl_end < 0) + rl_end = 0; + + return 0; +} + +#if defined (HANDLE_MULTIBYTE) +/* Move forward COUNT characters. */ +int +rl_forward_char (count, key) + int count, key; +{ + int point; + + if (MB_CUR_MAX == 1 || rl_byte_oriented) + return (rl_forward_byte (count, key)); + + if (count < 0) + return (rl_backward_char (-count, key)); + + if (count > 0) + { + point = _rl_find_next_mbchar (rl_line_buffer, rl_point, count, MB_FIND_NONZERO); + +#if defined (VI_MODE) + if (rl_end <= point && rl_editing_mode == vi_mode) + point = _rl_find_prev_mbchar (rl_line_buffer, rl_end, MB_FIND_NONZERO); +#endif + + if (rl_point == point) + rl_ding (); + + rl_point = point; + + if (rl_end < 0) + rl_end = 0; + } + + return 0; +} +#else /* !HANDLE_MULTIBYTE */ +int +rl_forward_char (count, key) + int count, key; +{ + return (rl_forward_byte (count, key)); +} +#endif /* !HANDLE_MULTIBYTE */ + +/* Backwards compatibility. */ +int +rl_forward (count, key) + int count, key; +{ + return (rl_forward_char (count, key)); +} + +/* Move backward COUNT bytes. */ +int +rl_backward_byte (count, key) + int count, key; +{ + if (count < 0) + return (rl_forward_byte (-count, key)); + + if (count > 0) + { + if (rl_point < count) + { + rl_point = 0; + rl_ding (); + } + else + rl_point -= count; + } + + if (rl_point < 0) + rl_point = 0; + + return 0; +} + +#if defined (HANDLE_MULTIBYTE) +/* Move backward COUNT characters. */ +int +rl_backward_char (count, key) + int count, key; +{ + int point; + + if (MB_CUR_MAX == 1 || rl_byte_oriented) + return (rl_backward_byte (count, key)); + + if (count < 0) + return (rl_forward_char (-count, key)); + + if (count > 0) + { + point = rl_point; + + while (count > 0 && point > 0) + { + point = _rl_find_prev_mbchar (rl_line_buffer, point, MB_FIND_NONZERO); + count--; + } + if (count > 0) + { + rl_point = 0; + rl_ding (); + } + else + rl_point = point; + } + + return 0; +} +#else +int +rl_backward_char (count, key) + int count, key; +{ + return (rl_backward_byte (count, key)); +} +#endif + +/* Backwards compatibility. */ +int +rl_backward (count, key) + int count, key; +{ + return (rl_backward_char (count, key)); +} + +/* Move to the beginning of the line. */ +int +rl_beg_of_line (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ + rl_point = 0; + return 0; +} + +/* Move to the end of the line. */ +int +rl_end_of_line (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ + rl_point = rl_end; + return 0; +} + +/* Move forward a word. We do what Emacs does. Handles multibyte chars. */ +int +rl_forward_word (count, key) + int count, key; +{ + int c; + + if (count < 0) + return (rl_backward_word (-count, key)); + + while (count) + { + if (rl_point == rl_end) + return 0; + + /* If we are not in a word, move forward until we are in one. + Then, move forward until we hit a non-alphabetic character. */ + c = _rl_char_value (rl_line_buffer, rl_point); + + if (_rl_walphabetic (c) == 0) + { + rl_point = MB_NEXTCHAR (rl_line_buffer, rl_point, 1, MB_FIND_NONZERO); + while (rl_point < rl_end) + { + c = _rl_char_value (rl_line_buffer, rl_point); + if (_rl_walphabetic (c)) + break; + rl_point = MB_NEXTCHAR (rl_line_buffer, rl_point, 1, MB_FIND_NONZERO); + } + } + + if (rl_point == rl_end) + return 0; + + rl_point = MB_NEXTCHAR (rl_line_buffer, rl_point, 1, MB_FIND_NONZERO); + while (rl_point < rl_end) + { + c = _rl_char_value (rl_line_buffer, rl_point); + if (_rl_walphabetic (c) == 0) + break; + rl_point = MB_NEXTCHAR (rl_line_buffer, rl_point, 1, MB_FIND_NONZERO); + } + + --count; + } + + return 0; +} + +/* Move backward a word. We do what Emacs does. Handles multibyte chars. */ +int +rl_backward_word (count, key) + int count, key; +{ + int c, p; + + if (count < 0) + return (rl_forward_word (-count, key)); + + while (count) + { + if (rl_point == 0) + return 0; + + /* Like rl_forward_word (), except that we look at the characters + just before point. */ + + p = MB_PREVCHAR (rl_line_buffer, rl_point, MB_FIND_NONZERO); + c = _rl_char_value (rl_line_buffer, p); + + if (_rl_walphabetic (c) == 0) + { + rl_point = p; + while (rl_point > 0) + { + p = MB_PREVCHAR (rl_line_buffer, rl_point, MB_FIND_NONZERO); + c = _rl_char_value (rl_line_buffer, p); + if (_rl_walphabetic (c)) + break; + rl_point = p; + } + } + + while (rl_point) + { + p = MB_PREVCHAR (rl_line_buffer, rl_point, MB_FIND_NONZERO); + c = _rl_char_value (rl_line_buffer, p); + if (_rl_walphabetic (c) == 0) + break; + else + rl_point = p; + } + + --count; + } + + return 0; +} + +/* Clear the current line. Numeric argument to C-l does this. */ +int +rl_refresh_line (ignore1, ignore2) + int ignore1 __attribute__((unused)), ignore2 __attribute__((unused)); +{ + int curr_line; + + curr_line = _rl_current_display_line (); + + _rl_move_vert (curr_line); + _rl_move_cursor_relative (0, rl_line_buffer); /* XXX is this right */ + + _rl_clear_to_eol (0); /* arg of 0 means to not use spaces */ + + rl_forced_update_display (); + rl_display_fixed = 1; + + return 0; +} + +/* C-l typed to a line without quoting clears the screen, and then reprints + the prompt and the current input line. Given a numeric arg, redraw only + the current line. */ +int +rl_clear_screen (count, key) + int count, key; +{ + if (rl_explicit_arg) + { + rl_refresh_line (count, key); + return 0; + } + + _rl_clear_screen (); /* calls termcap function to clear screen */ + rl_forced_update_display (); + rl_display_fixed = 1; + + return 0; +} + +int +rl_arrow_keys (count, c) + int count, c __attribute__((unused)); +{ + int ch; + + RL_SETSTATE(RL_STATE_MOREINPUT); + ch = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); + + switch (_rl_to_upper (ch)) + { + case 'A': + rl_get_previous_history (count, ch); + break; + + case 'B': + rl_get_next_history (count, ch); + break; + + case 'C': + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + rl_forward_char (count, ch); + else + rl_forward_byte (count, ch); + break; + + case 'D': + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + rl_backward_char (count, ch); + else + rl_backward_byte (count, ch); + break; + + default: + rl_ding (); + } + + return 0; +} + +/* **************************************************************** */ +/* */ +/* Text commands */ +/* */ +/* **************************************************************** */ + +#ifdef HANDLE_MULTIBYTE +static char pending_bytes[MB_LEN_MAX]; +static int pending_bytes_length = 0; +static mbstate_t ps; +#endif + +/* Insert the character C at the current location, moving point forward. + If C introduces a multibyte sequence, we read the whole sequence and + then insert the multibyte char into the line buffer. */ +int +_rl_insert_char (count, c) + int count, c; +{ + register int i; + char *string; +#ifdef HANDLE_MULTIBYTE + int string_size; + char incoming[MB_LEN_MAX + 1]; + int incoming_length = 0; + mbstate_t ps_back; + static int stored_count = 0; +#endif + + if (count <= 0) + return 0; + +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX == 1 || rl_byte_oriented) + { + incoming[0] = c; + incoming[1] = '\0'; + incoming_length = 1; + } + else + { + wchar_t wc; + size_t ret; + + if (stored_count <= 0) + stored_count = count; + else + count = stored_count; + + ps_back = ps; + pending_bytes[pending_bytes_length++] = c; + ret = mbrtowc (&wc, pending_bytes, pending_bytes_length, &ps); + + if (ret == (size_t)-2) + { + /* Bytes too short to compose character, try to wait for next byte. + Restore the state of the byte sequence, because in this case the + effect of mbstate is undefined. */ + ps = ps_back; + return 1; + } + else if (ret == (size_t)-1) + { + /* Invalid byte sequence for the current locale. Treat first byte + as a single character. */ + incoming[0] = pending_bytes[0]; + incoming[1] = '\0'; + incoming_length = 1; + pending_bytes_length--; + memmove (pending_bytes, pending_bytes + 1, pending_bytes_length); + /* Clear the state of the byte sequence, because in this case the + effect of mbstate is undefined. */ + memset (&ps, 0, sizeof (mbstate_t)); + } + else if (ret == (size_t)0) + { + incoming[0] = '\0'; + incoming_length = 0; + pending_bytes_length--; + /* Clear the state of the byte sequence, because in this case the + effect of mbstate is undefined. */ + memset (&ps, 0, sizeof (mbstate_t)); + } + else + { + /* We successfully read a single multibyte character. */ + memcpy (incoming, pending_bytes, pending_bytes_length); + incoming[pending_bytes_length] = '\0'; + incoming_length = pending_bytes_length; + pending_bytes_length = 0; + } + } +#endif /* HANDLE_MULTIBYTE */ + + /* If we can optimize, then do it. But don't let people crash + readline because of extra large arguments. */ + if (count > 1 && count <= 1024) + { +#if defined (HANDLE_MULTIBYTE) + string_size = count * incoming_length; + string = (char *)xmalloc (1 + string_size); + + i = 0; + while (i < string_size) + { + strncpy (string + i, incoming, incoming_length); + i += incoming_length; + } + incoming_length = 0; + stored_count = 0; +#else /* !HANDLE_MULTIBYTE */ + string = (char *)xmalloc (1 + count); + + for (i = 0; i < count; i++) + string[i] = c; +#endif /* !HANDLE_MULTIBYTE */ + + string[i] = '\0'; + rl_insert_text (string); + free (string); + + return 0; + } + + if (count > 1024) + { + int decreaser; +#if defined (HANDLE_MULTIBYTE) + string_size = incoming_length * 1024; + string = (char *)xmalloc (1 + string_size); + + i = 0; + while (i < string_size) + { + strncpy (string + i, incoming, incoming_length); + i += incoming_length; + } + + while (count) + { + decreaser = (count > 1024) ? 1024 : count; + string[decreaser*incoming_length] = '\0'; + rl_insert_text (string); + count -= decreaser; + } + + free (string); + incoming_length = 0; + stored_count = 0; +#else /* !HANDLE_MULTIBYTE */ + char str[1024+1]; + + for (i = 0; i < 1024; i++) + str[i] = c; + + while (count) + { + decreaser = (count > 1024 ? 1024 : count); + str[decreaser] = '\0'; + rl_insert_text (str); + count -= decreaser; + } +#endif /* !HANDLE_MULTIBYTE */ + + return 0; + } + + if (MB_CUR_MAX == 1 || rl_byte_oriented) + { + /* We are inserting a single character. + If there is pending input, then make a string of all of the + pending characters that are bound to rl_insert, and insert + them all. */ + if (_rl_any_typein ()) + _rl_insert_typein (c); + else + { + /* Inserting a single character. */ + char str[2]; + + str[1] = '\0'; + str[0] = c; + rl_insert_text (str); + } + } +#if defined (HANDLE_MULTIBYTE) + else + { + rl_insert_text (incoming); + stored_count = 0; + } +#endif + + return 0; +} + +/* Overwrite the character at point (or next COUNT characters) with C. + If C introduces a multibyte character sequence, read the entire sequence + before starting the overwrite loop. */ +int +_rl_overwrite_char (count, c) + int count, c; +{ + int i; +#if defined (HANDLE_MULTIBYTE) + char mbkey[MB_LEN_MAX]; + + /* Read an entire multibyte character sequence to insert COUNT times. */ + if (count > 0 && MB_CUR_MAX > 1 && rl_byte_oriented == 0) + _rl_read_mbstring (c, mbkey, MB_LEN_MAX); +#endif + + rl_begin_undo_group (); + + for (i = 0; i < count; i++) + { +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + rl_insert_text (mbkey); + else +#endif + _rl_insert_char (1, c); + + if (rl_point < rl_end) + rl_delete (1, c); + } + + rl_end_undo_group (); + + return 0; +} + +int +rl_insert (count, c) + int count, c; +{ + return (rl_insert_mode == RL_IM_INSERT ? _rl_insert_char (count, c) + : _rl_overwrite_char (count, c)); +} + +/* Insert the next typed character verbatim. */ +static int +_rl_insert_next (count) + int count; +{ + int c; + + RL_SETSTATE(RL_STATE_MOREINPUT); + c = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); + +#if defined (HANDLE_SIGNALS) + if (RL_ISSTATE (RL_STATE_CALLBACK) == 0) + _rl_restore_tty_signals (); +#endif + + return (_rl_insert_char (count, c)); +} + +#if defined (READLINE_CALLBACKS) +static int +_rl_insert_next_callback (data) + _rl_callback_generic_arg *data; +{ + int count; + + count = data->count; + + /* Deregister function, let rl_callback_read_char deallocate data */ + _rl_callback_func = 0; + _rl_want_redisplay = 1; + + return _rl_insert_next (count); +} +#endif + +int +rl_quoted_insert (count, key) + int count, key __attribute__((unused)); +{ + /* Let's see...should the callback interface futz with signal handling? */ +#if defined (HANDLE_SIGNALS) + if (RL_ISSTATE (RL_STATE_CALLBACK) == 0) + _rl_disable_tty_signals (); +#endif + +#if defined (READLINE_CALLBACKS) + if (RL_ISSTATE (RL_STATE_CALLBACK)) + { + _rl_callback_data = _rl_callback_data_alloc (count); + _rl_callback_func = _rl_insert_next_callback; + return (0); + } +#endif + + return _rl_insert_next (count); +} + +/* Insert a tab character. */ +int +rl_tab_insert (count, key) + int count, key __attribute__((unused)); +{ + return (_rl_insert_char (count, '\t')); +} + +/* What to do when a NEWLINE is pressed. We accept the whole line. + KEY is the key that invoked this command. I guess it could have + meaning in the future. */ +int +rl_newline (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ + rl_done = 1; + + if (_rl_history_preserve_point) + _rl_history_saved_point = (rl_point == rl_end) ? -1 : rl_point; + + RL_SETSTATE(RL_STATE_DONE); + +#if defined (VI_MODE) + if (rl_editing_mode == vi_mode) + { + _rl_vi_done_inserting (); + if (_rl_vi_textmod_command (_rl_vi_last_command) == 0) /* XXX */ + _rl_vi_reset_last (); + } +#endif /* VI_MODE */ + + /* If we've been asked to erase empty lines, suppress the final update, + since _rl_update_final calls rl_crlf(). */ + if (rl_erase_empty_line && rl_point == 0 && rl_end == 0) + return 0; + + if (readline_echoing_p) + _rl_update_final (); + return 0; +} + +/* What to do for some uppercase characters, like meta characters, + and some characters appearing in emacs_ctlx_keymap. This function + is just a stub, you bind keys to it and the code in _rl_dispatch () + is special cased. */ +int +rl_do_lowercase_version (ignore1, ignore2) + int ignore1 __attribute__((unused)), ignore2 __attribute__((unused)); +{ + return 0; +} + +/* This is different from what vi does, so the code's not shared. Emacs + rubout in overwrite mode has one oddity: it replaces a control + character that's displayed as two characters (^X) with two spaces. */ +int +_rl_overwrite_rubout (count, key) + int count, key; +{ + int opoint; + int i, l; + + if (rl_point == 0) + { + rl_ding (); + return 1; + } + + opoint = rl_point; + + /* L == number of spaces to insert */ + for (i = l = 0; i < count; i++) + { + rl_backward_char (1, key); + l += rl_character_len (rl_line_buffer[rl_point], rl_point); /* not exactly right */ + } + + rl_begin_undo_group (); + + if (count > 1 || rl_explicit_arg) + rl_kill_text (opoint, rl_point); + else + rl_delete_text (opoint, rl_point); + + /* Emacs puts point at the beginning of the sequence of spaces. */ + if (rl_point < rl_end) + { + opoint = rl_point; + _rl_insert_char (l, ' '); + rl_point = opoint; + } + + rl_end_undo_group (); + + return 0; +} + +/* Rubout the character behind point. */ +int +rl_rubout (count, key) + int count, key; +{ + if (count < 0) + return (rl_delete (-count, key)); + + if (!rl_point) + { + rl_ding (); + return -1; + } + + if (rl_insert_mode == RL_IM_OVERWRITE) + return (_rl_overwrite_rubout (count, key)); + + return (_rl_rubout_char (count, key)); +} + +int +_rl_rubout_char (count, key) + int count, key; +{ + int orig_point; + unsigned char c; + + /* Duplicated code because this is called from other parts of the library. */ + if (count < 0) + return (rl_delete (-count, key)); + + if (rl_point == 0) + { + rl_ding (); + return -1; + } + + orig_point = rl_point; + if (count > 1 || rl_explicit_arg) + { + rl_backward_char (count, key); + rl_kill_text (orig_point, rl_point); + } + else if (MB_CUR_MAX == 1 || rl_byte_oriented) + { + c = rl_line_buffer[--rl_point]; + rl_delete_text (rl_point, orig_point); + /* The erase-at-end-of-line hack is of questionable merit now. */ + if (rl_point == rl_end && ISPRINT (c) && _rl_last_c_pos) + { + int l; + l = rl_character_len (c, rl_point); + _rl_erase_at_end_of_line (l); + } + } + else + { + rl_point = _rl_find_prev_mbchar (rl_line_buffer, rl_point, MB_FIND_NONZERO); + rl_delete_text (rl_point, orig_point); + } + + return 0; +} + +/* Delete the character under the cursor. Given a numeric argument, + kill that many characters instead. */ +int +rl_delete (count, key) + int count, key; +{ + int xpoint; + + if (count < 0) + return (_rl_rubout_char (-count, key)); + + if (rl_point == rl_end) + { + rl_ding (); + return -1; + } + + if (count > 1 || rl_explicit_arg) + { + xpoint = rl_point; + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + rl_forward_char (count, key); + else + rl_forward_byte (count, key); + + rl_kill_text (xpoint, rl_point); + rl_point = xpoint; + } + else + { + xpoint = MB_NEXTCHAR (rl_line_buffer, rl_point, 1, MB_FIND_NONZERO); + rl_delete_text (rl_point, xpoint); + } + return 0; +} + +/* Delete the character under the cursor, unless the insertion + point is at the end of the line, in which case the character + behind the cursor is deleted. COUNT is obeyed and may be used + to delete forward or backward that many characters. */ +int +rl_rubout_or_delete (count, key) + int count, key; +{ + if (rl_end != 0 && rl_point == rl_end) + return (_rl_rubout_char (count, key)); + else + return (rl_delete (count, key)); +} + +/* Delete all spaces and tabs around point. */ +int +rl_delete_horizontal_space (count, ignore) + int count __attribute__((unused)), ignore __attribute__((unused)); +{ + int start = rl_point; + + while (rl_point && whitespace (rl_line_buffer[rl_point - 1])) + rl_point--; + + start = rl_point; + + while (rl_point < rl_end && whitespace (rl_line_buffer[rl_point])) + rl_point++; + + if (start != rl_point) + { + rl_delete_text (start, rl_point); + rl_point = start; + } + + if (rl_point < 0) + rl_point = 0; + + return 0; +} + +/* Like the tcsh editing function delete-char-or-list. The eof character + is caught before this is invoked, so this really does the same thing as + delete-char-or-list-or-eof, as long as it's bound to the eof character. */ +int +rl_delete_or_show_completions (count, key) + int count, key; +{ + if (rl_end != 0 && rl_point == rl_end) + return (rl_possible_completions (count, key)); + else + return (rl_delete (count, key)); +} + +#ifndef RL_COMMENT_BEGIN_DEFAULT +#define RL_COMMENT_BEGIN_DEFAULT "#" +#endif + +/* Turn the current line into a comment in shell history. + A K*rn shell style function. */ +int +rl_insert_comment (count, key) + int count __attribute__((unused)), key; +{ + const char *rl_comment_text; + int rl_comment_len; + + rl_beg_of_line (1, key); + rl_comment_text = _rl_comment_begin ? _rl_comment_begin : (char*) RL_COMMENT_BEGIN_DEFAULT; + + if (rl_explicit_arg == 0) + rl_insert_text (rl_comment_text); + else + { + rl_comment_len = strlen (rl_comment_text); + if (STREQN (rl_comment_text, rl_line_buffer, rl_comment_len)) + rl_delete_text (rl_point, rl_point + rl_comment_len); + else + rl_insert_text (rl_comment_text); + } + + (*rl_redisplay_function) (); + rl_newline (1, '\n'); + + return (0); +} + +/* **************************************************************** */ +/* */ +/* Changing Case */ +/* */ +/* **************************************************************** */ + +/* The three kinds of things that we know how to do. */ +#define UpCase 1 +#define DownCase 2 +#define CapCase 3 + +/* Uppercase the word at point. */ +int +rl_upcase_word (count, key) + int count, key __attribute__((unused)); +{ + return (rl_change_case (count, UpCase)); +} + +/* Lowercase the word at point. */ +int +rl_downcase_word (count, key) + int count, key __attribute__((unused)); +{ + return (rl_change_case (count, DownCase)); +} + +/* Upcase the first letter, downcase the rest. */ +int +rl_capitalize_word (count, key) + int count, key __attribute__((unused)); +{ + return (rl_change_case (count, CapCase)); +} + +/* The meaty function. + Change the case of COUNT words, performing OP on them. + OP is one of UpCase, DownCase, or CapCase. + If a negative argument is given, leave point where it started, + otherwise, leave it where it moves to. */ +static int +rl_change_case (count, op) + int count, op; +{ + int start, next, end; + int inword, c, nc, nop; +#if defined (HANDLE_MULTIBYTE) + wchar_t wc, nwc; + char mb[MB_LEN_MAX+1]; + int mlen; + mbstate_t mps; +#endif + + start = rl_point; + rl_forward_word (count, 0); + end = rl_point; + + if (op != UpCase && op != DownCase && op != CapCase) + { + rl_ding (); + return -1; + } + + if (count < 0) + SWAP (start, end); + +#if defined (HANDLE_MULTIBYTE) + memset (&mps, 0, sizeof (mbstate_t)); +#endif + + /* We are going to modify some text, so let's prepare to undo it. */ + rl_modifying (start, end); + + inword = 0; + while (start < end) + { + c = _rl_char_value (rl_line_buffer, start); + /* This assumes that the upper and lower case versions are the same width. */ + next = MB_NEXTCHAR (rl_line_buffer, start, 1, MB_FIND_NONZERO); + + if (_rl_walphabetic (c) == 0) + { + inword = 0; + start = next; + continue; + } + + if (op == CapCase) + { + nop = inword ? DownCase : UpCase; + inword = 1; + } + else + nop = op; + if (MB_CUR_MAX == 1 || rl_byte_oriented || isascii (c)) + { + nc = (nop == UpCase) ? _rl_to_upper (c) : _rl_to_lower (c); + rl_line_buffer[start] = nc; + } +#if defined (HANDLE_MULTIBYTE) + else + { + mbrtowc (&wc, rl_line_buffer + start, end - start, &mps); + nwc = (nop == UpCase) ? _rl_to_wupper (wc) : _rl_to_wlower (wc); + if (nwc != wc) /* just skip unchanged characters */ + { + mlen = wcrtomb (mb, nwc, &mps); + if (mlen > 0) + mb[mlen] = '\0'; + /* Assume the same width */ + strncpy (rl_line_buffer + start, mb, mlen); + } + } +#endif + + start = next; + } + + rl_point = end; + return 0; +} + +/* **************************************************************** */ +/* */ +/* Transposition */ +/* */ +/* **************************************************************** */ + +/* Transpose the words at point. If point is at the end of the line, + transpose the two words before point. */ +int +rl_transpose_words (count, key) + int count, key; +{ + char *word1, *word2; + int w1_beg, w1_end, w2_beg, w2_end; + int orig_point = rl_point; + + if (!count) + return 0; + + /* Find the two words. */ + rl_forward_word (count, key); + w2_end = rl_point; + rl_backward_word (1, key); + w2_beg = rl_point; + rl_backward_word (count, key); + w1_beg = rl_point; + rl_forward_word (1, key); + w1_end = rl_point; + + /* Do some check to make sure that there really are two words. */ + if ((w1_beg == w2_beg) || (w2_beg < w1_end)) + { + rl_ding (); + rl_point = orig_point; + return -1; + } + + /* Get the text of the words. */ + word1 = rl_copy_text (w1_beg, w1_end); + word2 = rl_copy_text (w2_beg, w2_end); + + /* We are about to do many insertions and deletions. Remember them + as one operation. */ + rl_begin_undo_group (); + + /* Do the stuff at word2 first, so that we don't have to worry + about word1 moving. */ + rl_point = w2_beg; + rl_delete_text (w2_beg, w2_end); + rl_insert_text (word1); + + rl_point = w1_beg; + rl_delete_text (w1_beg, w1_end); + rl_insert_text (word2); + + /* This is exactly correct since the text before this point has not + changed in length. */ + rl_point = w2_end; + + /* I think that does it. */ + rl_end_undo_group (); + free (word1); + free (word2); + + return 0; +} + +/* Transpose the characters at point. If point is at the end of the line, + then transpose the characters before point. */ +int +rl_transpose_chars (count, key) + int count, key __attribute__((unused)); +{ +#if defined (HANDLE_MULTIBYTE) + char *dummy; + int i; + int prev_point; +#else + char dummy[2]; +#endif + int char_length; + + if (count == 0) + return 0; + + if (!rl_point || rl_end < 2) + { + rl_ding (); + return -1; + } + + rl_begin_undo_group (); + + if (rl_point == rl_end) + { + rl_point = MB_PREVCHAR (rl_line_buffer, rl_point, MB_FIND_NONZERO); + count = 1; + } + +#if defined (HANDLE_MULTIBYTE) + prev_point = rl_point; +#endif + rl_point = MB_PREVCHAR (rl_line_buffer, rl_point, MB_FIND_NONZERO); + +#if defined (HANDLE_MULTIBYTE) + char_length = prev_point - rl_point; + dummy = (char *)xmalloc (char_length + 1); + for (i = 0; i < char_length; i++) + dummy[i] = rl_line_buffer[rl_point + i]; + dummy[i] = '\0'; +#else + dummy[0] = rl_line_buffer[rl_point]; + dummy[char_length = 1] = '\0'; +#endif + + rl_delete_text (rl_point, rl_point + char_length); + + rl_point = _rl_find_next_mbchar (rl_line_buffer, rl_point, count, MB_FIND_NONZERO); + + _rl_fix_point (0); + rl_insert_text (dummy); + rl_end_undo_group (); + +#if defined (HANDLE_MULTIBYTE) + free (dummy); +#endif + + return 0; +} + +/* **************************************************************** */ +/* */ +/* Character Searching */ +/* */ +/* **************************************************************** */ + +int +#if defined (HANDLE_MULTIBYTE) +_rl_char_search_internal (count, dir, smbchar, len) + int count, dir; + char *smbchar; + int len; +#else +_rl_char_search_internal (count, dir, schar) + int count, dir, schar; +#endif +{ + int pos, inc; +#if defined (HANDLE_MULTIBYTE) + int prepos; +#endif + + pos = rl_point; + inc = (dir < 0) ? -1 : 1; + while (count) + { + if ((dir < 0 && pos <= 0) || (dir > 0 && pos >= rl_end)) + { + rl_ding (); + return -1; + } + +#if defined (HANDLE_MULTIBYTE) + pos = (inc > 0) ? _rl_find_next_mbchar (rl_line_buffer, pos, 1, MB_FIND_ANY) + : _rl_find_prev_mbchar (rl_line_buffer, pos, MB_FIND_ANY); +#else + pos += inc; +#endif + do + { +#if defined (HANDLE_MULTIBYTE) + if (_rl_is_mbchar_matched (rl_line_buffer, pos, rl_end, smbchar, len)) +#else + if (rl_line_buffer[pos] == schar) +#endif + { + count--; + if (dir < 0) + rl_point = (dir == BTO) ? _rl_find_next_mbchar (rl_line_buffer, pos, 1, MB_FIND_ANY) + : pos; + else + rl_point = (dir == FTO) ? _rl_find_prev_mbchar (rl_line_buffer, pos, MB_FIND_ANY) + : pos; + break; + } +#if defined (HANDLE_MULTIBYTE) + prepos = pos; +#endif + } +#if defined (HANDLE_MULTIBYTE) + while ((dir < 0) ? (pos = _rl_find_prev_mbchar (rl_line_buffer, pos, MB_FIND_ANY)) != prepos + : (pos = _rl_find_next_mbchar (rl_line_buffer, pos, 1, MB_FIND_ANY)) != prepos); +#else + while ((dir < 0) ? pos-- : ++pos < rl_end); +#endif + } + return (0); +} + +/* Search COUNT times for a character read from the current input stream. + FDIR is the direction to search if COUNT is non-negative; otherwise + the search goes in BDIR. So much is dependent on HANDLE_MULTIBYTE + that there are two separate versions of this function. */ +#if defined (HANDLE_MULTIBYTE) +static int +_rl_char_search (count, fdir, bdir) + int count, fdir, bdir; +{ + char mbchar[MB_LEN_MAX]; + int mb_len; + + mb_len = _rl_read_mbchar (mbchar, MB_LEN_MAX); + + if (count < 0) + return (_rl_char_search_internal (-count, bdir, mbchar, mb_len)); + else + return (_rl_char_search_internal (count, fdir, mbchar, mb_len)); +} +#else /* !HANDLE_MULTIBYTE */ +static int +_rl_char_search (count, fdir, bdir) + int count, fdir, bdir; +{ + int c; + + RL_SETSTATE(RL_STATE_MOREINPUT); + c = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); + + if (count < 0) + return (_rl_char_search_internal (-count, bdir, c)); + else + return (_rl_char_search_internal (count, fdir, c)); +} +#endif /* !HANDLE_MULTIBYTE */ + +#if defined (READLINE_CALLBACKS) +static int +_rl_char_search_callback (data) + _rl_callback_generic_arg *data; +{ + _rl_callback_func = 0; + _rl_want_redisplay = 1; + + return (_rl_char_search (data->count, data->i1, data->i2)); +} +#endif + +int +rl_char_search (count, key) + int count, key __attribute__((unused)); +{ +#if defined (READLINE_CALLBACKS) + if (RL_ISSTATE (RL_STATE_CALLBACK)) + { + _rl_callback_data = _rl_callback_data_alloc (count); + _rl_callback_data->i1 = FFIND; + _rl_callback_data->i2 = BFIND; + _rl_callback_func = _rl_char_search_callback; + return (0); + } +#endif + + return (_rl_char_search (count, FFIND, BFIND)); +} + +int +rl_backward_char_search (count, key) + int count, key __attribute__((unused)); +{ +#if defined (READLINE_CALLBACKS) + if (RL_ISSTATE (RL_STATE_CALLBACK)) + { + _rl_callback_data = _rl_callback_data_alloc (count); + _rl_callback_data->i1 = BFIND; + _rl_callback_data->i2 = FFIND; + _rl_callback_func = _rl_char_search_callback; + return (0); + } +#endif + + return (_rl_char_search (count, BFIND, FFIND)); +} + +/* **************************************************************** */ +/* */ +/* The Mark and the Region. */ +/* */ +/* **************************************************************** */ + +/* Set the mark at POSITION. */ +int +_rl_set_mark_at_pos (position) + int position; +{ + if (position > rl_end) + return -1; + + rl_mark = position; + return 0; +} + +/* A bindable command to set the mark. */ +int +rl_set_mark (count, key) + int count, key __attribute__((unused)); +{ + return (_rl_set_mark_at_pos (rl_explicit_arg ? count : rl_point)); +} + +/* Exchange the position of mark and point. */ +int +rl_exchange_point_and_mark (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ + if (rl_mark > rl_end) + rl_mark = -1; + + if (rl_mark == -1) + { + rl_ding (); + return -1; + } + else + SWAP (rl_point, rl_mark); + + return 0; +} diff --git a/extra/readline/tilde.c b/extra/readline/tilde.c new file mode 100644 index 00000000000..3d84fd4815a --- /dev/null +++ b/extra/readline/tilde.c @@ -0,0 +1,502 @@ +/* tilde.c -- Tilde expansion code (~/foo := $HOME/foo). */ + +/* Copyright (C) 1988,1989 Free Software Foundation, Inc. + + This file is part of GNU Readline, a library for reading lines + of text with interactive input and history editing. + + Readline is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any + later version. + + Readline 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Readline; see the file COPYING. If not, write to the Free + Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include <sys/types.h> +# endif +# include <unistd.h> +#endif + +#if defined (HAVE_STRING_H) +# include <string.h> +#else /* !HAVE_STRING_H */ +# include <strings.h> +#endif /* !HAVE_STRING_H */ + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#include <sys/types.h> +#if defined (HAVE_PWD_H) +#include <pwd.h> +#endif + +#include "tilde.h" + +#if defined (TEST) || defined (STATIC_MALLOC) +static void *xmalloc (), *xrealloc (); +#else +# include "xmalloc.h" +#endif /* TEST || STATIC_MALLOC */ + +#if !defined (HAVE_GETPW_DECLS) +# if defined (HAVE_GETPWUID) +extern struct passwd *getpwuid PARAMS((uid_t)); +# endif +# if defined (HAVE_GETPWNAM) +extern struct passwd *getpwnam PARAMS((const char *)); +# endif +#endif /* !HAVE_GETPW_DECLS */ + +#if !defined (savestring) +#define savestring(x) strcpy ((char *)xmalloc (1 + strlen (x)), (x)) +#endif /* !savestring */ + +#if !defined (NULL) +# if defined (__STDC__) +# define NULL ((void *) 0) +# else +# define NULL 0x0 +# endif /* !__STDC__ */ +#endif /* !NULL */ + +/* If being compiled as part of bash, these will be satisfied from + variables.o. If being compiled as part of readline, they will + be satisfied from shell.o. */ +extern char *sh_get_home_dir PARAMS((void)); +extern char *sh_get_env_value PARAMS((const char *)); + +/* The default value of tilde_additional_prefixes. This is set to + whitespace preceding a tilde so that simple programs which do not + perform any word separation get desired behaviour. */ +static const char *default_prefixes[] = + { " ~", "\t~", (const char *)NULL }; + +/* The default value of tilde_additional_suffixes. This is set to + whitespace or newline so that simple programs which do not + perform any word separation get desired behaviour. */ +static const char *default_suffixes[] = + { " ", "\n", (const char *)NULL }; + +/* If non-null, this contains the address of a function that the application + wants called before trying the standard tilde expansions. The function + is called with the text sans tilde, and returns a malloc()'ed string + which is the expansion, or a NULL pointer if the expansion fails. */ +tilde_hook_func_t *tilde_expansion_preexpansion_hook = (tilde_hook_func_t *)NULL; + +/* If non-null, this contains the address of a function to call if the + standard meaning for expanding a tilde fails. The function is called + with the text (sans tilde, as in "foo"), and returns a malloc()'ed string + which is the expansion, or a NULL pointer if there is no expansion. */ +tilde_hook_func_t *tilde_expansion_failure_hook = (tilde_hook_func_t *)NULL; + +/* When non-null, this is a NULL terminated array of strings which + are duplicates for a tilde prefix. Bash uses this to expand + `=~' and `:~'. */ +char **tilde_additional_prefixes = (char **)default_prefixes; + +/* When non-null, this is a NULL terminated array of strings which match + the end of a username, instead of just "/". Bash sets this to + `:' and `=~'. */ +char **tilde_additional_suffixes = (char **)default_suffixes; + +static int tilde_find_prefix PARAMS((const char *, int *)); +static int tilde_find_suffix PARAMS((const char *)); +static char *isolate_tilde_prefix PARAMS((const char *, int *)); +static char *glue_prefix_and_suffix PARAMS((char *, const char *, int)); + +/* Find the start of a tilde expansion in STRING, and return the index of + the tilde which starts the expansion. Place the length of the text + which identified this tilde starter in LEN, excluding the tilde itself. */ +static int +tilde_find_prefix (string, len) + const char *string; + int *len; +{ + register int i, j, string_len; + register char **prefixes; + + prefixes = tilde_additional_prefixes; + + string_len = strlen (string); + *len = 0; + + if (*string == '\0' || *string == '~') + return (0); + + if (prefixes) + { + for (i = 0; i < string_len; i++) + { + for (j = 0; prefixes[j]; j++) + { + if (strncmp (string + i, prefixes[j], strlen (prefixes[j])) == 0) + { + *len = strlen (prefixes[j]) - 1; + return (i + *len); + } + } + } + } + return (string_len); +} + +/* Find the end of a tilde expansion in STRING, and return the index of + the character which ends the tilde definition. */ +static int +tilde_find_suffix (string) + const char *string; +{ + register int i, j, string_len; + register char **suffixes; + + suffixes = tilde_additional_suffixes; + string_len = strlen (string); + + for (i = 0; i < string_len; i++) + { +#if defined (__MSDOS__) + if (string[i] == '/' || string[i] == '\\' /* || !string[i] */) +#else + if (string[i] == '/' /* || !string[i] */) +#endif + break; + + for (j = 0; suffixes && suffixes[j]; j++) + { + if (strncmp (string + i, suffixes[j], strlen (suffixes[j])) == 0) + return (i); + } + } + return (i); +} + +/* Return a new string which is the result of tilde expanding STRING. */ +char * +tilde_expand (string) + const char *string; +{ + char *result; + int result_size, result_index; + + result_index = result_size = 0; + if ((result = strchr (string, '~'))) + result = (char *)xmalloc (result_size = (strlen (string) + 16)); + else + result = (char *)xmalloc (result_size = (strlen (string) + 1)); + + /* Scan through STRING expanding tildes as we come to them. */ + while (1) + { + register int start, end; + char *tilde_word, *expansion; + int len; + + /* Make START point to the tilde which starts the expansion. */ + start = tilde_find_prefix (string, &len); + + /* Copy the skipped text into the result. */ + if ((result_index + start + 1) > result_size) + result = (char *)xrealloc (result, 1 + (result_size += (start + 20))); + + strncpy (result + result_index, string, start); + result_index += start; + + /* Advance STRING to the starting tilde. */ + string += start; + + /* Make END be the index of one after the last character of the + username. */ + end = tilde_find_suffix (string); + + /* If both START and END are zero, we are all done. */ + if (!start && !end) + break; + + /* Expand the entire tilde word, and copy it into RESULT. */ + tilde_word = (char *)xmalloc (1 + end); + strncpy (tilde_word, string, end); + tilde_word[end] = '\0'; + string += end; + + expansion = tilde_expand_word (tilde_word); + free (tilde_word); + + len = strlen (expansion); +#ifdef __CYGWIN__ + /* Fix for Cygwin to prevent ~user/xxx from expanding to //xxx when + $HOME for `user' is /. On cygwin, // denotes a network drive. */ + if (len > 1 || *expansion != '/' || *string != '/') +#endif + { + if ((result_index + len + 1) > result_size) + result = (char *)xrealloc (result, 1 + (result_size += (len + 20))); + + strcpy (result + result_index, expansion); + result_index += len; + } + free (expansion); + } + + result[result_index] = '\0'; + + return (result); +} + +/* Take FNAME and return the tilde prefix we want expanded. If LENP is + non-null, the index of the end of the prefix into FNAME is returned in + the location it points to. */ +static char * +isolate_tilde_prefix (fname, lenp) + const char *fname; + int *lenp; +{ + char *ret; + int i; + + ret = (char *)xmalloc (strlen (fname)); +#if defined (__MSDOS__) + for (i = 1; fname[i] && fname[i] != '/' && fname[i] != '\\'; i++) +#else + for (i = 1; fname[i] && fname[i] != '/'; i++) +#endif + ret[i - 1] = fname[i]; + ret[i - 1] = '\0'; + if (lenp) + *lenp = i; + return ret; +} + +#if 0 +/* Public function to scan a string (FNAME) beginning with a tilde and find + the portion of the string that should be passed to the tilde expansion + function. Right now, it just calls tilde_find_suffix and allocates new + memory, but it can be expanded to do different things later. */ +char * +tilde_find_word (fname, flags, lenp) + const char *fname; + int flags, *lenp; +{ + int x; + char *r; + + x = tilde_find_suffix (fname); + if (x == 0) + { + r = savestring (fname); + if (lenp) + *lenp = 0; + } + else + { + r = (char *)xmalloc (1 + x); + strncpy (r, fname, x); + r[x] = '\0'; + if (lenp) + *lenp = x; + } + + return r; +} +#endif + +/* Return a string that is PREFIX concatenated with SUFFIX starting at + SUFFIND. */ +static char * +glue_prefix_and_suffix (prefix, suffix, suffind) + char *prefix; + const char *suffix; + int suffind; +{ + char *ret; + int plen, slen; + + plen = (prefix && *prefix) ? strlen (prefix) : 0; + slen = strlen (suffix + suffind); + ret = (char *)xmalloc (plen + slen + 1); + if (plen) + strcpy (ret, prefix); + strcpy (ret + plen, suffix + suffind); + return ret; +} + +/* Do the work of tilde expansion on FILENAME. FILENAME starts with a + tilde. If there is no expansion, call tilde_expansion_failure_hook. + This always returns a newly-allocated string, never static storage. */ +char * +tilde_expand_word (filename) + const char *filename; +{ + char *dirname, *expansion, *username; + int user_len; + struct passwd *user_entry; + + if (filename == 0) + return ((char *)NULL); + + if (*filename != '~') + return (savestring (filename)); + + /* A leading `~/' or a bare `~' is *always* translated to the value of + $HOME or the home directory of the current user, regardless of any + preexpansion hook. */ + if (filename[1] == '\0' || filename[1] == '/') + { + /* Prefix $HOME to the rest of the string. */ + expansion = sh_get_env_value ("HOME"); + + /* If there is no HOME variable, look up the directory in + the password database. */ + if (expansion == 0) + expansion = sh_get_home_dir (); + + return (glue_prefix_and_suffix (expansion, filename, 1)); + } + + username = isolate_tilde_prefix (filename, &user_len); + + if (tilde_expansion_preexpansion_hook) + { + expansion = (*tilde_expansion_preexpansion_hook) (username); + if (expansion) + { + dirname = glue_prefix_and_suffix (expansion, filename, user_len); + free (username); + free (expansion); + return (dirname); + } + } + + /* No preexpansion hook, or the preexpansion hook failed. Look in the + password database. */ + dirname = (char *)NULL; +#if defined (HAVE_GETPWNAM) + user_entry = getpwnam (username); +#else + user_entry = 0; +#endif + if (user_entry == 0) + { + /* If the calling program has a special syntax for expanding tildes, + and we couldn't find a standard expansion, then let them try. */ + if (tilde_expansion_failure_hook) + { + expansion = (*tilde_expansion_failure_hook) (username); + if (expansion) + { + dirname = glue_prefix_and_suffix (expansion, filename, user_len); + free (expansion); + } + } + /* If we don't have a failure hook, or if the failure hook did not + expand the tilde, return a copy of what we were passed. */ + if (dirname == 0) + dirname = savestring (filename); + } +#if defined (HAVE_GETPWENT) + else + dirname = glue_prefix_and_suffix (user_entry->pw_dir, filename, user_len); +#endif + + free (username); +#if defined (HAVE_GETPWENT) + endpwent (); +#endif + return (dirname); +} + + +#if defined (TEST) +#undef NULL +#include <stdio.h> + +main (argc, argv) + int argc; + char **argv; +{ + char *result, line[512]; + int done = 0; + + while (!done) + { + printf ("~expand: "); + fflush (stdout); + + if (!gets (line)) + strcpy (line, "done"); + + if ((strcmp (line, "done") == 0) || + (strcmp (line, "quit") == 0) || + (strcmp (line, "exit") == 0)) + { + done = 1; + break; + } + + result = tilde_expand (line); + printf (" --> %s\n", result); + free (result); + } + exit (0); +} + +static void memory_error_and_abort (); + +static void * +xmalloc (bytes) + size_t bytes; +{ + void *temp = (char *)malloc (bytes); + + if (!temp) + memory_error_and_abort (); + return (temp); +} + +static void * +xrealloc (pointer, bytes) + void *pointer; + int bytes; +{ + void *temp; + + if (!pointer) + temp = malloc (bytes); + else + temp = realloc (pointer, bytes); + + if (!temp) + memory_error_and_abort (); + + return (temp); +} + +static void +memory_error_and_abort () +{ + fprintf (stderr, "readline: out of virtual memory\n"); + abort (); +} + +/* + * Local variables: + * compile-command: "gcc -g -DTEST -o tilde tilde.c" + * end: + */ +#endif /* TEST */ diff --git a/extra/readline/tilde.h b/extra/readline/tilde.h new file mode 100644 index 00000000000..6060ec6506d --- /dev/null +++ b/extra/readline/tilde.h @@ -0,0 +1,81 @@ +/* tilde.h: Externally available variables and function in libtilde.a. */ + +/* Copyright (C) 1992 Free Software Foundation, Inc. + + This file contains the Readline Library (the Library), a set of + routines for providing Emacs style line input to programs that ask + for it. + + The Library is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + The 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 + General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#if !defined (_TILDE_H_) +# define _TILDE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* A function can be defined using prototypes and compile on both ANSI C + and traditional C compilers with something like this: + extern char *func PARAMS((char *, char *, int)); */ + +#if !defined (PARAMS) +# if defined (__STDC__) || defined (__GNUC__) || defined (__cplusplus) +# define PARAMS(protos) protos +# else +# define PARAMS(protos) () +# endif +#endif + +typedef char *tilde_hook_func_t PARAMS((char *)); + +/* If non-null, this contains the address of a function that the application + wants called before trying the standard tilde expansions. The function + is called with the text sans tilde, and returns a malloc()'ed string + which is the expansion, or a NULL pointer if the expansion fails. */ +extern tilde_hook_func_t *tilde_expansion_preexpansion_hook; + +/* If non-null, this contains the address of a function to call if the + standard meaning for expanding a tilde fails. The function is called + with the text (sans tilde, as in "foo"), and returns a malloc()'ed string + which is the expansion, or a NULL pointer if there is no expansion. */ +extern tilde_hook_func_t *tilde_expansion_failure_hook; + +/* When non-null, this is a NULL terminated array of strings which + are duplicates for a tilde prefix. Bash uses this to expand + `=~' and `:~'. */ +extern char **tilde_additional_prefixes; + +/* When non-null, this is a NULL terminated array of strings which match + the end of a username, instead of just "/". Bash sets this to + `:' and `=~'. */ +extern char **tilde_additional_suffixes; + +/* Return a new string which is the result of tilde expanding STRING. */ +extern char *tilde_expand PARAMS((const char *)); + +/* Do the work of tilde expansion on FILENAME. FILENAME starts with a + tilde. If there is no expansion, call tilde_expansion_failure_hook. */ +extern char *tilde_expand_word PARAMS((const char *)); + +/* Find the portion of the string beginning with ~ that should be expanded. */ +extern char *tilde_find_word PARAMS((const char *, int, int *)); + +#ifdef __cplusplus +} +#endif + +#endif /* _TILDE_H_ */ diff --git a/extra/readline/undo.c b/extra/readline/undo.c new file mode 100644 index 00000000000..6b217e45e35 --- /dev/null +++ b/extra/readline/undo.c @@ -0,0 +1,330 @@ +/* readline.c -- a general facility for reading lines of input + with emacs style editing and completion. */ + +/* Copyright (C) 1987, 1989, 1992, 2006 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <sys/types.h> + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> /* for _POSIX_VERSION */ +#endif /* HAVE_UNISTD_H */ + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#include <stdio.h> + +/* System-specific feature definitions and include files. */ +#include "rldefs.h" + +/* Some standard library routines. */ +#include "readline.h" +#include "history.h" + +#include "rlprivate.h" +#include "xmalloc.h" + +extern void replace_history_data PARAMS((int, histdata_t *, histdata_t *)); + +/* Non-zero tells rl_delete_text and rl_insert_text to not add to + the undo list. */ +int _rl_doing_an_undo = 0; + +/* How many unclosed undo groups we currently have. */ +int _rl_undo_group_level = 0; + +/* The current undo list for THE_LINE. */ +UNDO_LIST *rl_undo_list = (UNDO_LIST *)NULL; + +/* **************************************************************** */ +/* */ +/* Undo, and Undoing */ +/* */ +/* **************************************************************** */ + +static UNDO_LIST * +alloc_undo_entry (what, start, end, text) + enum undo_code what; + int start, end; + char *text; +{ + UNDO_LIST *temp; + + temp = (UNDO_LIST *)xmalloc (sizeof (UNDO_LIST)); + temp->what = what; + temp->start = start; + temp->end = end; + temp->text = text; + + temp->next = (UNDO_LIST *)NULL; + return temp; +} + +/* Remember how to undo something. Concatenate some undos if that + seems right. */ +void +rl_add_undo (what, start, end, text) + enum undo_code what; + int start, end; + char *text; +{ + UNDO_LIST *temp; + + temp = alloc_undo_entry (what, start, end, text); + temp->next = rl_undo_list; + rl_undo_list = temp; +} + +/* Free the existing undo list. */ +void +rl_free_undo_list () +{ + UNDO_LIST *release, *orig_list; + + orig_list = rl_undo_list; + while (rl_undo_list) + { + release = rl_undo_list; + rl_undo_list = rl_undo_list->next; + + if (release->what == UNDO_DELETE) + free (release->text); + + free (release); + } + rl_undo_list = (UNDO_LIST *)NULL; + replace_history_data (-1, (histdata_t *)orig_list, (histdata_t *)NULL); +} + +UNDO_LIST * +_rl_copy_undo_entry (entry) + UNDO_LIST *entry; +{ + UNDO_LIST *new; + + new = alloc_undo_entry (entry->what, entry->start, entry->end, (char *)NULL); + new->text = entry->text ? savestring (entry->text) : 0; + return new; +} + +UNDO_LIST * +_rl_copy_undo_list (head) + UNDO_LIST *head; +{ + UNDO_LIST *list, *new, *c; + UNDO_LIST *roving= NULL; + + list = head; + new = 0; + while (list) + { + c = _rl_copy_undo_entry (list); + if (new == 0) + roving = new = c; + else + { + roving->next = c; + roving = roving->next; + } + list = list->next; + } + + roving->next = 0; + return new; +} + +/* Undo the next thing in the list. Return 0 if there + is nothing to undo, or non-zero if there was. */ +int +rl_do_undo () +{ + UNDO_LIST *release; + int waiting_for_begin, start, end; + +#define TRANS(i) ((i) == -1 ? rl_point : ((i) == -2 ? rl_end : (i))) + + start = end = waiting_for_begin = 0; + do + { + if (!rl_undo_list) + return (0); + + _rl_doing_an_undo = 1; + RL_SETSTATE(RL_STATE_UNDOING); + + /* To better support vi-mode, a start or end value of -1 means + rl_point, and a value of -2 means rl_end. */ + if (rl_undo_list->what == UNDO_DELETE || rl_undo_list->what == UNDO_INSERT) + { + start = TRANS (rl_undo_list->start); + end = TRANS (rl_undo_list->end); + } + + switch (rl_undo_list->what) + { + /* Undoing deletes means inserting some text. */ + case UNDO_DELETE: + rl_point = start; + rl_insert_text (rl_undo_list->text); + free (rl_undo_list->text); + break; + + /* Undoing inserts means deleting some text. */ + case UNDO_INSERT: + rl_delete_text (start, end); + rl_point = start; + break; + + /* Undoing an END means undoing everything 'til we get to a BEGIN. */ + case UNDO_END: + waiting_for_begin++; + break; + + /* Undoing a BEGIN means that we are done with this group. */ + case UNDO_BEGIN: + if (waiting_for_begin) + waiting_for_begin--; + else + rl_ding (); + break; + } + + _rl_doing_an_undo = 0; + RL_UNSETSTATE(RL_STATE_UNDOING); + + release = rl_undo_list; + rl_undo_list = rl_undo_list->next; + replace_history_data (-1, (histdata_t *)release, (histdata_t *)rl_undo_list); + + free (release); + } + while (waiting_for_begin); + + return (1); +} +#undef TRANS + +int +_rl_fix_last_undo_of_type (type, start, end) + enum undo_code type; + int start, end; +{ + UNDO_LIST *rl; + + for (rl = rl_undo_list; rl; rl = rl->next) + { + if (rl->what == type) + { + rl->start = start; + rl->end = end; + return 0; + } + } + return 1; +} + +/* Begin a group. Subsequent undos are undone as an atomic operation. */ +int +rl_begin_undo_group () +{ + rl_add_undo (UNDO_BEGIN, 0, 0, 0); + _rl_undo_group_level++; + return 0; +} + +/* End an undo group started with rl_begin_undo_group (). */ +int +rl_end_undo_group () +{ + rl_add_undo (UNDO_END, 0, 0, 0); + _rl_undo_group_level--; + return 0; +} + +/* Save an undo entry for the text from START to END. */ +int +rl_modifying (start, end) + int start, end; +{ + if (start > end) + { + SWAP (start, end); + } + + if (start != end) + { + char *temp = rl_copy_text (start, end); + rl_begin_undo_group (); + rl_add_undo (UNDO_DELETE, start, end, temp); + rl_add_undo (UNDO_INSERT, start, end, (char *)NULL); + rl_end_undo_group (); + } + return 0; +} + +/* Revert the current line to its previous state. */ +int +rl_revert_line (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ + if (!rl_undo_list) + rl_ding (); + else + { + while (rl_undo_list) + rl_do_undo (); +#if defined (VI_MODE) + if (rl_editing_mode == vi_mode) + rl_point = rl_mark = 0; /* rl_end should be set correctly */ +#endif + } + + return 0; +} + +/* Do some undoing of things that were done. */ +int +rl_undo_command (count, key) + int count, key __attribute__((unused)); +{ + if (count < 0) + return 0; /* Nothing to do. */ + + while (count) + { + if (rl_do_undo ()) + count--; + else + { + rl_ding (); + break; + } + } + return 0; +} diff --git a/extra/readline/util.c b/extra/readline/util.c new file mode 100644 index 00000000000..7c98bcaaefe --- /dev/null +++ b/extra/readline/util.c @@ -0,0 +1,360 @@ +/* util.c -- readline utility functions */ + +/* Copyright (C) 1987-2005 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <sys/types.h> +#include <fcntl.h> +#include "posixjmp.h" + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> /* for _POSIX_VERSION */ +#endif /* HAVE_UNISTD_H */ + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#include <stdio.h> +#include <ctype.h> + +/* System-specific feature definitions and include files. */ +#include "rldefs.h" +#include "rlmbutil.h" + +#if defined (TIOCSTAT_IN_SYS_IOCTL) +# include <sys/ioctl.h> +#endif /* TIOCSTAT_IN_SYS_IOCTL */ + +/* Some standard library routines. */ +#include "readline.h" + +#include "rlprivate.h" +#include "xmalloc.h" + +/* **************************************************************** */ +/* */ +/* Utility Functions */ +/* */ +/* **************************************************************** */ + +/* Return 0 if C is not a member of the class of characters that belong + in words, or 1 if it is. */ + +int _rl_allow_pathname_alphabetic_chars = 0; +static const char *pathname_alphabetic_chars = "/-_=~.#$"; + +int +rl_alphabetic (c) + int c; +{ + if (ALPHABETIC (c)) + return (1); + + return (_rl_allow_pathname_alphabetic_chars && + strchr (pathname_alphabetic_chars, c) != NULL); +} + +#if defined (HANDLE_MULTIBYTE) +int +/* + Portability issue with VisualAge C++ Professional / C for AIX Compiler, Version 6: + "util.c", line 84.1: 1506-343 (S) Redeclaration of _rl_walphabetic differs + from previous declaration on line 110 of "rlmbutil.h". + So, put type in the function signature here. +*/ +_rl_walphabetic (wchar_t wc) +{ + int c; + + if (iswalnum (wc)) + return (1); + + c = wc & 0177; + return (_rl_allow_pathname_alphabetic_chars && + strchr (pathname_alphabetic_chars, c) != NULL); +} +#endif + +/* How to abort things. */ +int +_rl_abort_internal () +{ + rl_ding (); + rl_clear_message (); + _rl_reset_argument (); + rl_clear_pending_input (); + + RL_UNSETSTATE (RL_STATE_MACRODEF); + while (rl_executing_macro) + _rl_pop_executing_macro (); + + rl_last_func = (rl_command_func_t *)NULL; + longjmp (readline_top_level, 1); + return (0); +} + +int +rl_abort (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ + return (_rl_abort_internal ()); +} + +int +rl_tty_status (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ +#if defined (TIOCSTAT) + ioctl (1, TIOCSTAT, (char *)0); + rl_refresh_line (count, key); +#else + rl_ding (); +#endif + return 0; +} + +/* Return a copy of the string between FROM and TO. + FROM is inclusive, TO is not. */ +char * +rl_copy_text (from, to) + int from, to; +{ + register int length; + char *copy; + + /* Fix it if the caller is confused. */ + if (from > to) + SWAP (from, to); + + length = to - from; + copy = (char *)xmalloc (1 + length); + strncpy (copy, rl_line_buffer + from, length); + copy[length] = '\0'; + return (copy); +} + +/* Increase the size of RL_LINE_BUFFER until it has enough space to hold + LEN characters. */ +void +rl_extend_line_buffer (len) + int len; +{ + while (len >= rl_line_buffer_len) + { + rl_line_buffer_len += DEFAULT_BUFFER_SIZE; + rl_line_buffer = (char *)xrealloc (rl_line_buffer, rl_line_buffer_len); + } + + _rl_set_the_line (); +} + + +/* A function for simple tilde expansion. */ +int +rl_tilde_expand (ignore, key) + int ignore __attribute__((unused)), key __attribute__((unused)); +{ + register int start, end; + char *homedir, *temp; + int len; + + end = rl_point; + start = end - 1; + + if (rl_point == rl_end && rl_line_buffer[rl_point] == '~') + { + homedir = tilde_expand ("~"); + _rl_replace_text (homedir, start, end); + return (0); + } + else if (rl_line_buffer[start] != '~') + { + for (; !whitespace (rl_line_buffer[start]) && start >= 0; start--) + ; + start++; + } + + end = start; + do + end++; + while (whitespace (rl_line_buffer[end]) == 0 && end < rl_end); + + if (whitespace (rl_line_buffer[end]) || end >= rl_end) + end--; + + /* If the first character of the current word is a tilde, perform + tilde expansion and insert the result. If not a tilde, do + nothing. */ + if (rl_line_buffer[start] == '~') + { + len = end - start + 1; + temp = (char *)xmalloc (len + 1); + strncpy (temp, rl_line_buffer + start, len); + temp[len] = '\0'; + homedir = tilde_expand (temp); + free (temp); + + _rl_replace_text (homedir, start, end); + } + + return (0); +} + +/* **************************************************************** */ +/* */ +/* String Utility Functions */ +/* */ +/* **************************************************************** */ + +/* Determine if s2 occurs in s1. If so, return a pointer to the + match in s1. The compare is case insensitive. */ +char * +_rl_strindex (s1, s2) + register const char *s1, *s2; +{ + register int i, l, len; + + for (i = 0, l = strlen (s2), len = strlen (s1); (len - i) >= l; i++) + if (_rl_strnicmp (s1 + i, s2, l) == 0) + return ((char *) (s1 + i)); + return ((char *)NULL); +} + +#ifndef HAVE_STRPBRK +/* Find the first occurrence in STRING1 of any character from STRING2. + Return a pointer to the character in STRING1. */ +char * +_rl_strpbrk (string1, string2) + const char *string1, *string2; +{ + register const char *scan; +#if defined (HANDLE_MULTIBYTE) + mbstate_t ps; + register int i, v; + + memset (&ps, 0, sizeof (mbstate_t)); +#endif + + for (; *string1; string1++) + { + for (scan = string2; *scan; scan++) + { + if (*string1 == *scan) + return ((char *)string1); + } +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + v = _rl_get_char_len (string1, &ps); + if (v > 1) + string1 += v - 1; /* -1 to account for auto-increment in loop */ + } +#endif + } + return ((char *)NULL); +} +#endif + +#if !defined (HAVE_STRCASECMP) +/* Compare at most COUNT characters from string1 to string2. Case + doesn't matter. */ +int +_rl_strnicmp (string1, string2, count) + char *string1, *string2; + int count; +{ + register char ch1, ch2; + + while (count) + { + ch1 = *string1++; + ch2 = *string2++; + if (_rl_to_upper(ch1) == _rl_to_upper(ch2)) + count--; + else + break; + } + return (count); +} + +/* strcmp (), but caseless. */ +int +_rl_stricmp (string1, string2) + char *string1, *string2; +{ + register char ch1, ch2; + + while (*string1 && *string2) + { + ch1 = *string1++; + ch2 = *string2++; + if (_rl_to_upper(ch1) != _rl_to_upper(ch2)) + return (1); + } + return (*string1 - *string2); +} +#endif /* !HAVE_STRCASECMP */ + +/* Stupid comparison routine for qsort () ing strings. */ +int +_rl_qsort_string_compare (s1, s2) + char **s1, **s2; +{ +#if defined (HAVE_STRCOLL) + return (strcoll (*s1, *s2)); +#else + int result; + + result = **s1 - **s2; + if (result == 0) + result = strcmp (*s1, *s2); + + return result; +#endif +} + +/* Function equivalents for the macros defined in chardefs.h. */ +#define FUNCTION_FOR_MACRO(f) int (f) (c) int c; { return f (c); } + +FUNCTION_FOR_MACRO (_rl_digit_p) +FUNCTION_FOR_MACRO (_rl_digit_value) +FUNCTION_FOR_MACRO (_rl_lowercase_p) +FUNCTION_FOR_MACRO (_rl_pure_alphabetic) +FUNCTION_FOR_MACRO (_rl_to_lower) +FUNCTION_FOR_MACRO (_rl_to_upper) +FUNCTION_FOR_MACRO (_rl_uppercase_p) + +/* Backwards compatibility, now that savestring has been removed from + all `public' readline header files. */ +#undef _rl_savestring +char * +_rl_savestring (s) + const char *s; +{ + return (strcpy ((char *)xmalloc (1 + (int)strlen (s)), (s))); +} diff --git a/extra/readline/vi_keymap.c b/extra/readline/vi_keymap.c new file mode 100644 index 00000000000..85a90fe7086 --- /dev/null +++ b/extra/readline/vi_keymap.c @@ -0,0 +1,877 @@ +/* vi_keymap.c -- the keymap for vi_mode in readline (). */ + +/* Copyright (C) 1987, 1989, 1992 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#if !defined (BUFSIZ) +#include <stdio.h> +#endif /* !BUFSIZ */ + +#include "readline.h" + +#if 0 +extern KEYMAP_ENTRY_ARRAY vi_escape_keymap; +#endif + +/* The keymap arrays for handling vi mode. */ +KEYMAP_ENTRY_ARRAY vi_movement_keymap = { + /* The regular control keys come first. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-@ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-a */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-b */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-c */ + { ISFUNC, rl_vi_eof_maybe }, /* Control-d */ + { ISFUNC, rl_emacs_editing_mode }, /* Control-e */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-f */ + { ISFUNC, rl_abort }, /* Control-g */ + { ISFUNC, rl_backward_char }, /* Control-h */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-i */ + { ISFUNC, rl_newline }, /* Control-j */ + { ISFUNC, rl_kill_line }, /* Control-k */ + { ISFUNC, rl_clear_screen }, /* Control-l */ + { ISFUNC, rl_newline }, /* Control-m */ + { ISFUNC, rl_get_next_history }, /* Control-n */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-o */ + { ISFUNC, rl_get_previous_history }, /* Control-p */ + { ISFUNC, rl_quoted_insert }, /* Control-q */ + { ISFUNC, rl_reverse_search_history }, /* Control-r */ + { ISFUNC, rl_forward_search_history }, /* Control-s */ + { ISFUNC, rl_transpose_chars }, /* Control-t */ + { ISFUNC, rl_unix_line_discard }, /* Control-u */ + { ISFUNC, rl_quoted_insert }, /* Control-v */ + { ISFUNC, rl_unix_word_rubout }, /* Control-w */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-x */ + { ISFUNC, rl_yank }, /* Control-y */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-z */ + + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-[ */ /* vi_escape_keymap */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-\ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-] */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-^ */ + { ISFUNC, rl_vi_undo }, /* Control-_ */ + + /* The start of printing characters. */ + { ISFUNC, rl_forward_char }, /* SPACE */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ! */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* " */ + { ISFUNC, rl_insert_comment }, /* # */ + { ISFUNC, rl_end_of_line }, /* $ */ + { ISFUNC, rl_vi_match }, /* % */ + { ISFUNC, rl_vi_tilde_expand }, /* & */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ' */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ( */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ) */ + { ISFUNC, rl_vi_complete }, /* * */ + { ISFUNC, rl_get_next_history}, /* + */ + { ISFUNC, rl_vi_char_search }, /* , */ + { ISFUNC, rl_get_previous_history }, /* - */ + { ISFUNC, rl_vi_redo }, /* . */ + { ISFUNC, rl_vi_search }, /* / */ + + /* Regular digits. */ + { ISFUNC, rl_beg_of_line }, /* 0 */ + { ISFUNC, rl_vi_arg_digit }, /* 1 */ + { ISFUNC, rl_vi_arg_digit }, /* 2 */ + { ISFUNC, rl_vi_arg_digit }, /* 3 */ + { ISFUNC, rl_vi_arg_digit }, /* 4 */ + { ISFUNC, rl_vi_arg_digit }, /* 5 */ + { ISFUNC, rl_vi_arg_digit }, /* 6 */ + { ISFUNC, rl_vi_arg_digit }, /* 7 */ + { ISFUNC, rl_vi_arg_digit }, /* 8 */ + { ISFUNC, rl_vi_arg_digit }, /* 9 */ + + /* A little more punctuation. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* : */ + { ISFUNC, rl_vi_char_search }, /* ; */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* < */ + { ISFUNC, rl_vi_complete }, /* = */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* > */ + { ISFUNC, rl_vi_search }, /* ? */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* @ */ + + /* Uppercase alphabet. */ + { ISFUNC, rl_vi_append_eol }, /* A */ + { ISFUNC, rl_vi_prev_word}, /* B */ + { ISFUNC, rl_vi_change_to }, /* C */ + { ISFUNC, rl_vi_delete_to }, /* D */ + { ISFUNC, rl_vi_end_word }, /* E */ + { ISFUNC, rl_vi_char_search }, /* F */ + { ISFUNC, rl_vi_fetch_history }, /* G */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* H */ + { ISFUNC, rl_vi_insert_beg }, /* I */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* J */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* K */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* L */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* M */ + { ISFUNC, rl_vi_search_again }, /* N */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* O */ + { ISFUNC, rl_vi_put }, /* P */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Q */ + { ISFUNC, rl_vi_replace }, /* R */ + { ISFUNC, rl_vi_subst }, /* S */ + { ISFUNC, rl_vi_char_search }, /* T */ + { ISFUNC, rl_revert_line }, /* U */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* V */ + { ISFUNC, rl_vi_next_word }, /* W */ + { ISFUNC, rl_vi_rubout }, /* X */ + { ISFUNC, rl_vi_yank_to }, /* Y */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Z */ + + /* Some more punctuation. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* [ */ + { ISFUNC, rl_vi_complete }, /* \ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ] */ + { ISFUNC, rl_vi_first_print }, /* ^ */ + { ISFUNC, rl_vi_yank_arg }, /* _ */ + { ISFUNC, rl_vi_goto_mark }, /* ` */ + + /* Lowercase alphabet. */ + { ISFUNC, rl_vi_append_mode }, /* a */ + { ISFUNC, rl_vi_prev_word }, /* b */ + { ISFUNC, rl_vi_change_to }, /* c */ + { ISFUNC, rl_vi_delete_to }, /* d */ + { ISFUNC, rl_vi_end_word }, /* e */ + { ISFUNC, rl_vi_char_search }, /* f */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* g */ + { ISFUNC, rl_backward_char }, /* h */ + { ISFUNC, rl_vi_insertion_mode }, /* i */ + { ISFUNC, rl_get_next_history }, /* j */ + { ISFUNC, rl_get_previous_history }, /* k */ + { ISFUNC, rl_forward_char }, /* l */ + { ISFUNC, rl_vi_set_mark }, /* m */ + { ISFUNC, rl_vi_search_again }, /* n */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* o */ + { ISFUNC, rl_vi_put }, /* p */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* q */ + { ISFUNC, rl_vi_change_char }, /* r */ + { ISFUNC, rl_vi_subst }, /* s */ + { ISFUNC, rl_vi_char_search }, /* t */ + { ISFUNC, rl_vi_undo }, /* u */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* v */ + { ISFUNC, rl_vi_next_word }, /* w */ + { ISFUNC, rl_vi_delete }, /* x */ + { ISFUNC, rl_vi_yank_to }, /* y */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* z */ + + /* Final punctuation. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* { */ + { ISFUNC, rl_vi_column }, /* | */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* } */ + { ISFUNC, rl_vi_change_case }, /* ~ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* RUBOUT */ + +#if KEYMAP_SIZE > 128 + /* Undefined keys. */ + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 } +#endif /* KEYMAP_SIZE > 128 */ +}; + + +KEYMAP_ENTRY_ARRAY vi_insertion_keymap = { + /* The regular control keys come first. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-@ */ + { ISFUNC, rl_insert }, /* Control-a */ + { ISFUNC, rl_insert }, /* Control-b */ + { ISFUNC, rl_insert }, /* Control-c */ + { ISFUNC, rl_vi_eof_maybe }, /* Control-d */ + { ISFUNC, rl_insert }, /* Control-e */ + { ISFUNC, rl_insert }, /* Control-f */ + { ISFUNC, rl_insert }, /* Control-g */ + { ISFUNC, rl_rubout }, /* Control-h */ + { ISFUNC, rl_complete }, /* Control-i */ + { ISFUNC, rl_newline }, /* Control-j */ + { ISFUNC, rl_insert }, /* Control-k */ + { ISFUNC, rl_insert }, /* Control-l */ + { ISFUNC, rl_newline }, /* Control-m */ + { ISFUNC, rl_insert }, /* Control-n */ + { ISFUNC, rl_insert }, /* Control-o */ + { ISFUNC, rl_insert }, /* Control-p */ + { ISFUNC, rl_insert }, /* Control-q */ + { ISFUNC, rl_reverse_search_history }, /* Control-r */ + { ISFUNC, rl_forward_search_history }, /* Control-s */ + { ISFUNC, rl_transpose_chars }, /* Control-t */ + { ISFUNC, rl_unix_line_discard }, /* Control-u */ + { ISFUNC, rl_quoted_insert }, /* Control-v */ + { ISFUNC, rl_unix_word_rubout }, /* Control-w */ + { ISFUNC, rl_insert }, /* Control-x */ + { ISFUNC, rl_yank }, /* Control-y */ + { ISFUNC, rl_insert }, /* Control-z */ + + { ISFUNC, rl_vi_movement_mode }, /* Control-[ */ + { ISFUNC, rl_insert }, /* Control-\ */ + { ISFUNC, rl_insert }, /* Control-] */ + { ISFUNC, rl_insert }, /* Control-^ */ + { ISFUNC, rl_vi_undo }, /* Control-_ */ + + /* The start of printing characters. */ + { ISFUNC, rl_insert }, /* SPACE */ + { ISFUNC, rl_insert }, /* ! */ + { ISFUNC, rl_insert }, /* " */ + { ISFUNC, rl_insert }, /* # */ + { ISFUNC, rl_insert }, /* $ */ + { ISFUNC, rl_insert }, /* % */ + { ISFUNC, rl_insert }, /* & */ + { ISFUNC, rl_insert }, /* ' */ + { ISFUNC, rl_insert }, /* ( */ + { ISFUNC, rl_insert }, /* ) */ + { ISFUNC, rl_insert }, /* * */ + { ISFUNC, rl_insert }, /* + */ + { ISFUNC, rl_insert }, /* , */ + { ISFUNC, rl_insert }, /* - */ + { ISFUNC, rl_insert }, /* . */ + { ISFUNC, rl_insert }, /* / */ + + /* Regular digits. */ + { ISFUNC, rl_insert }, /* 0 */ + { ISFUNC, rl_insert }, /* 1 */ + { ISFUNC, rl_insert }, /* 2 */ + { ISFUNC, rl_insert }, /* 3 */ + { ISFUNC, rl_insert }, /* 4 */ + { ISFUNC, rl_insert }, /* 5 */ + { ISFUNC, rl_insert }, /* 6 */ + { ISFUNC, rl_insert }, /* 7 */ + { ISFUNC, rl_insert }, /* 8 */ + { ISFUNC, rl_insert }, /* 9 */ + + /* A little more punctuation. */ + { ISFUNC, rl_insert }, /* : */ + { ISFUNC, rl_insert }, /* ; */ + { ISFUNC, rl_insert }, /* < */ + { ISFUNC, rl_insert }, /* = */ + { ISFUNC, rl_insert }, /* > */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* @ */ + + /* Uppercase alphabet. */ + { ISFUNC, rl_insert }, /* A */ + { ISFUNC, rl_insert }, /* B */ + { ISFUNC, rl_insert }, /* C */ + { ISFUNC, rl_insert }, /* D */ + { ISFUNC, rl_insert }, /* E */ + { ISFUNC, rl_insert }, /* F */ + { ISFUNC, rl_insert }, /* G */ + { ISFUNC, rl_insert }, /* H */ + { ISFUNC, rl_insert }, /* I */ + { ISFUNC, rl_insert }, /* J */ + { ISFUNC, rl_insert }, /* K */ + { ISFUNC, rl_insert }, /* L */ + { ISFUNC, rl_insert }, /* M */ + { ISFUNC, rl_insert }, /* N */ + { ISFUNC, rl_insert }, /* O */ + { ISFUNC, rl_insert }, /* P */ + { ISFUNC, rl_insert }, /* Q */ + { ISFUNC, rl_insert }, /* R */ + { ISFUNC, rl_insert }, /* S */ + { ISFUNC, rl_insert }, /* T */ + { ISFUNC, rl_insert }, /* U */ + { ISFUNC, rl_insert }, /* V */ + { ISFUNC, rl_insert }, /* W */ + { ISFUNC, rl_insert }, /* X */ + { ISFUNC, rl_insert }, /* Y */ + { ISFUNC, rl_insert }, /* Z */ + + /* Some more punctuation. */ + { ISFUNC, rl_insert }, /* [ */ + { ISFUNC, rl_insert }, /* \ */ + { ISFUNC, rl_insert }, /* ] */ + { ISFUNC, rl_insert }, /* ^ */ + { ISFUNC, rl_insert }, /* _ */ + { ISFUNC, rl_insert }, /* ` */ + + /* Lowercase alphabet. */ + { ISFUNC, rl_insert }, /* a */ + { ISFUNC, rl_insert }, /* b */ + { ISFUNC, rl_insert }, /* c */ + { ISFUNC, rl_insert }, /* d */ + { ISFUNC, rl_insert }, /* e */ + { ISFUNC, rl_insert }, /* f */ + { ISFUNC, rl_insert }, /* g */ + { ISFUNC, rl_insert }, /* h */ + { ISFUNC, rl_insert }, /* i */ + { ISFUNC, rl_insert }, /* j */ + { ISFUNC, rl_insert }, /* k */ + { ISFUNC, rl_insert }, /* l */ + { ISFUNC, rl_insert }, /* m */ + { ISFUNC, rl_insert }, /* n */ + { ISFUNC, rl_insert }, /* o */ + { ISFUNC, rl_insert }, /* p */ + { ISFUNC, rl_insert }, /* q */ + { ISFUNC, rl_insert }, /* r */ + { ISFUNC, rl_insert }, /* s */ + { ISFUNC, rl_insert }, /* t */ + { ISFUNC, rl_insert }, /* u */ + { ISFUNC, rl_insert }, /* v */ + { ISFUNC, rl_insert }, /* w */ + { ISFUNC, rl_insert }, /* x */ + { ISFUNC, rl_insert }, /* y */ + { ISFUNC, rl_insert }, /* z */ + + /* Final punctuation. */ + { ISFUNC, rl_insert }, /* { */ + { ISFUNC, rl_insert }, /* | */ + { ISFUNC, rl_insert }, /* } */ + { ISFUNC, rl_insert }, /* ~ */ + { ISFUNC, rl_rubout }, /* RUBOUT */ + +#if KEYMAP_SIZE > 128 + /* Pure 8-bit characters (128 - 159). + These might be used in some + character sets. */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + { ISFUNC, rl_insert }, /* ? */ + + /* ISO Latin-1 characters (160 - 255) */ + { ISFUNC, rl_insert }, /* No-break space */ + { ISFUNC, rl_insert }, /* Inverted exclamation mark */ + { ISFUNC, rl_insert }, /* Cent sign */ + { ISFUNC, rl_insert }, /* Pound sign */ + { ISFUNC, rl_insert }, /* Currency sign */ + { ISFUNC, rl_insert }, /* Yen sign */ + { ISFUNC, rl_insert }, /* Broken bar */ + { ISFUNC, rl_insert }, /* Section sign */ + { ISFUNC, rl_insert }, /* Diaeresis */ + { ISFUNC, rl_insert }, /* Copyright sign */ + { ISFUNC, rl_insert }, /* Feminine ordinal indicator */ + { ISFUNC, rl_insert }, /* Left pointing double angle quotation mark */ + { ISFUNC, rl_insert }, /* Not sign */ + { ISFUNC, rl_insert }, /* Soft hyphen */ + { ISFUNC, rl_insert }, /* Registered sign */ + { ISFUNC, rl_insert }, /* Macron */ + { ISFUNC, rl_insert }, /* Degree sign */ + { ISFUNC, rl_insert }, /* Plus-minus sign */ + { ISFUNC, rl_insert }, /* Superscript two */ + { ISFUNC, rl_insert }, /* Superscript three */ + { ISFUNC, rl_insert }, /* Acute accent */ + { ISFUNC, rl_insert }, /* Micro sign */ + { ISFUNC, rl_insert }, /* Pilcrow sign */ + { ISFUNC, rl_insert }, /* Middle dot */ + { ISFUNC, rl_insert }, /* Cedilla */ + { ISFUNC, rl_insert }, /* Superscript one */ + { ISFUNC, rl_insert }, /* Masculine ordinal indicator */ + { ISFUNC, rl_insert }, /* Right pointing double angle quotation mark */ + { ISFUNC, rl_insert }, /* Vulgar fraction one quarter */ + { ISFUNC, rl_insert }, /* Vulgar fraction one half */ + { ISFUNC, rl_insert }, /* Vulgar fraction three quarters */ + { ISFUNC, rl_insert }, /* Inverted questionk mark */ + { ISFUNC, rl_insert }, /* Latin capital letter a with grave */ + { ISFUNC, rl_insert }, /* Latin capital letter a with acute */ + { ISFUNC, rl_insert }, /* Latin capital letter a with circumflex */ + { ISFUNC, rl_insert }, /* Latin capital letter a with tilde */ + { ISFUNC, rl_insert }, /* Latin capital letter a with diaeresis */ + { ISFUNC, rl_insert }, /* Latin capital letter a with ring above */ + { ISFUNC, rl_insert }, /* Latin capital letter ae */ + { ISFUNC, rl_insert }, /* Latin capital letter c with cedilla */ + { ISFUNC, rl_insert }, /* Latin capital letter e with grave */ + { ISFUNC, rl_insert }, /* Latin capital letter e with acute */ + { ISFUNC, rl_insert }, /* Latin capital letter e with circumflex */ + { ISFUNC, rl_insert }, /* Latin capital letter e with diaeresis */ + { ISFUNC, rl_insert }, /* Latin capital letter i with grave */ + { ISFUNC, rl_insert }, /* Latin capital letter i with acute */ + { ISFUNC, rl_insert }, /* Latin capital letter i with circumflex */ + { ISFUNC, rl_insert }, /* Latin capital letter i with diaeresis */ + { ISFUNC, rl_insert }, /* Latin capital letter eth (Icelandic) */ + { ISFUNC, rl_insert }, /* Latin capital letter n with tilde */ + { ISFUNC, rl_insert }, /* Latin capital letter o with grave */ + { ISFUNC, rl_insert }, /* Latin capital letter o with acute */ + { ISFUNC, rl_insert }, /* Latin capital letter o with circumflex */ + { ISFUNC, rl_insert }, /* Latin capital letter o with tilde */ + { ISFUNC, rl_insert }, /* Latin capital letter o with diaeresis */ + { ISFUNC, rl_insert }, /* Multiplication sign */ + { ISFUNC, rl_insert }, /* Latin capital letter o with stroke */ + { ISFUNC, rl_insert }, /* Latin capital letter u with grave */ + { ISFUNC, rl_insert }, /* Latin capital letter u with acute */ + { ISFUNC, rl_insert }, /* Latin capital letter u with circumflex */ + { ISFUNC, rl_insert }, /* Latin capital letter u with diaeresis */ + { ISFUNC, rl_insert }, /* Latin capital letter Y with acute */ + { ISFUNC, rl_insert }, /* Latin capital letter thorn (Icelandic) */ + { ISFUNC, rl_insert }, /* Latin small letter sharp s (German) */ + { ISFUNC, rl_insert }, /* Latin small letter a with grave */ + { ISFUNC, rl_insert }, /* Latin small letter a with acute */ + { ISFUNC, rl_insert }, /* Latin small letter a with circumflex */ + { ISFUNC, rl_insert }, /* Latin small letter a with tilde */ + { ISFUNC, rl_insert }, /* Latin small letter a with diaeresis */ + { ISFUNC, rl_insert }, /* Latin small letter a with ring above */ + { ISFUNC, rl_insert }, /* Latin small letter ae */ + { ISFUNC, rl_insert }, /* Latin small letter c with cedilla */ + { ISFUNC, rl_insert }, /* Latin small letter e with grave */ + { ISFUNC, rl_insert }, /* Latin small letter e with acute */ + { ISFUNC, rl_insert }, /* Latin small letter e with circumflex */ + { ISFUNC, rl_insert }, /* Latin small letter e with diaeresis */ + { ISFUNC, rl_insert }, /* Latin small letter i with grave */ + { ISFUNC, rl_insert }, /* Latin small letter i with acute */ + { ISFUNC, rl_insert }, /* Latin small letter i with circumflex */ + { ISFUNC, rl_insert }, /* Latin small letter i with diaeresis */ + { ISFUNC, rl_insert }, /* Latin small letter eth (Icelandic) */ + { ISFUNC, rl_insert }, /* Latin small letter n with tilde */ + { ISFUNC, rl_insert }, /* Latin small letter o with grave */ + { ISFUNC, rl_insert }, /* Latin small letter o with acute */ + { ISFUNC, rl_insert }, /* Latin small letter o with circumflex */ + { ISFUNC, rl_insert }, /* Latin small letter o with tilde */ + { ISFUNC, rl_insert }, /* Latin small letter o with diaeresis */ + { ISFUNC, rl_insert }, /* Division sign */ + { ISFUNC, rl_insert }, /* Latin small letter o with stroke */ + { ISFUNC, rl_insert }, /* Latin small letter u with grave */ + { ISFUNC, rl_insert }, /* Latin small letter u with acute */ + { ISFUNC, rl_insert }, /* Latin small letter u with circumflex */ + { ISFUNC, rl_insert }, /* Latin small letter u with diaeresis */ + { ISFUNC, rl_insert }, /* Latin small letter y with acute */ + { ISFUNC, rl_insert }, /* Latin small letter thorn (Icelandic) */ + { ISFUNC, rl_insert } /* Latin small letter y with diaeresis */ +#endif /* KEYMAP_SIZE > 128 */ +}; + +/* Unused for the time being. */ +#if 0 +KEYMAP_ENTRY_ARRAY vi_escape_keymap = { + /* The regular control keys come first. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-@ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-a */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-b */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-c */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-d */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-e */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-f */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-g */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-h */ + { ISFUNC, rl_tab_insert}, /* Control-i */ + { ISFUNC, rl_emacs_editing_mode}, /* Control-j */ + { ISFUNC, rl_kill_line }, /* Control-k */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-l */ + { ISFUNC, rl_emacs_editing_mode}, /* Control-m */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-n */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-o */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-p */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-q */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-r */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-s */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-t */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-u */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-v */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-w */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-x */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-y */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-z */ + + { ISFUNC, rl_vi_movement_mode }, /* Control-[ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-\ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-] */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* Control-^ */ + { ISFUNC, rl_vi_undo }, /* Control-_ */ + + /* The start of printing characters. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* SPACE */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ! */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* " */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* # */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* $ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* % */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* & */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ' */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ( */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ) */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* * */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* + */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* , */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* - */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* . */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* / */ + + /* Regular digits. */ + { ISFUNC, rl_vi_arg_digit }, /* 0 */ + { ISFUNC, rl_vi_arg_digit }, /* 1 */ + { ISFUNC, rl_vi_arg_digit }, /* 2 */ + { ISFUNC, rl_vi_arg_digit }, /* 3 */ + { ISFUNC, rl_vi_arg_digit }, /* 4 */ + { ISFUNC, rl_vi_arg_digit }, /* 5 */ + { ISFUNC, rl_vi_arg_digit }, /* 6 */ + { ISFUNC, rl_vi_arg_digit }, /* 7 */ + { ISFUNC, rl_vi_arg_digit }, /* 8 */ + { ISFUNC, rl_vi_arg_digit }, /* 9 */ + + /* A little more punctuation. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* : */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ; */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* < */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* = */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* > */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ? */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* @ */ + + /* Uppercase alphabet. */ + { ISFUNC, rl_do_lowercase_version }, /* A */ + { ISFUNC, rl_do_lowercase_version }, /* B */ + { ISFUNC, rl_do_lowercase_version }, /* C */ + { ISFUNC, rl_do_lowercase_version }, /* D */ + { ISFUNC, rl_do_lowercase_version }, /* E */ + { ISFUNC, rl_do_lowercase_version }, /* F */ + { ISFUNC, rl_do_lowercase_version }, /* G */ + { ISFUNC, rl_do_lowercase_version }, /* H */ + { ISFUNC, rl_do_lowercase_version }, /* I */ + { ISFUNC, rl_do_lowercase_version }, /* J */ + { ISFUNC, rl_do_lowercase_version }, /* K */ + { ISFUNC, rl_do_lowercase_version }, /* L */ + { ISFUNC, rl_do_lowercase_version }, /* M */ + { ISFUNC, rl_do_lowercase_version }, /* N */ + { ISFUNC, rl_do_lowercase_version }, /* O */ + { ISFUNC, rl_do_lowercase_version }, /* P */ + { ISFUNC, rl_do_lowercase_version }, /* Q */ + { ISFUNC, rl_do_lowercase_version }, /* R */ + { ISFUNC, rl_do_lowercase_version }, /* S */ + { ISFUNC, rl_do_lowercase_version }, /* T */ + { ISFUNC, rl_do_lowercase_version }, /* U */ + { ISFUNC, rl_do_lowercase_version }, /* V */ + { ISFUNC, rl_do_lowercase_version }, /* W */ + { ISFUNC, rl_do_lowercase_version }, /* X */ + { ISFUNC, rl_do_lowercase_version }, /* Y */ + { ISFUNC, rl_do_lowercase_version }, /* Z */ + + /* Some more punctuation. */ + { ISFUNC, rl_arrow_keys }, /* [ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* \ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ] */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ^ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* _ */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ` */ + + /* Lowercase alphabet. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* a */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* b */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* c */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* d */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* e */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* f */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* g */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* h */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* i */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* j */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* k */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* l */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* m */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* n */ + { ISFUNC, rl_arrow_keys }, /* o */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* p */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* q */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* r */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* s */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* t */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* u */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* v */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* w */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* x */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* y */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* z */ + + /* Final punctuation. */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* { */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* | */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* } */ + { ISFUNC, (rl_command_func_t *)0x0 }, /* ~ */ + { ISFUNC, rl_backward_kill_word }, /* RUBOUT */ + +#if KEYMAP_SIZE > 128 + /* Undefined keys. */ + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 }, + { ISFUNC, (rl_command_func_t *)0x0 } +#endif /* KEYMAP_SIZE > 128 */ +}; +#endif diff --git a/extra/readline/vi_mode.c b/extra/readline/vi_mode.c new file mode 100644 index 00000000000..7a7dda760df --- /dev/null +++ b/extra/readline/vi_mode.c @@ -0,0 +1,1748 @@ +/* vi_mode.c -- A vi emulation mode for Bash. + Derived from code written by Jeff Sparkes (jsparkes@bnr.ca). */ + +/* Copyright (C) 1987-2005 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +/* **************************************************************** */ +/* */ +/* VI Emulation Mode */ +/* */ +/* **************************************************************** */ +#include "rlconf.h" + +#if defined (VI_MODE) + +#if defined (HAVE_CONFIG_H) +# include "config_readline.h" +#endif + +#include <sys/types.h> + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif + +#include <stdio.h> + +/* Some standard library routines. */ +#include "rldefs.h" +#include "rlmbutil.h" + +#include "readline.h" +#include "history.h" + +#include "rlprivate.h" +#include "xmalloc.h" + +#ifndef member +#define member(c, s) ((c) ? (char *)strchr ((s), (c)) != (char *)NULL : 0) +#endif + +int _rl_vi_last_command = 'i'; /* default `.' puts you in insert mode */ + +/* Non-zero means enter insertion mode. */ +static int _rl_vi_doing_insert; + +/* Command keys which do movement for xxx_to commands. */ +static const char *vi_motion = " hl^$0ftFT;,%wbeWBE|"; + +/* Keymap used for vi replace characters. Created dynamically since + rarely used. */ +static Keymap vi_replace_map; + +/* The number of characters inserted in the last replace operation. */ +static int vi_replace_count; + +/* If non-zero, we have text inserted after a c[motion] command that put + us implicitly into insert mode. Some people want this text to be + attached to the command so that it is `redoable' with `.'. */ +static int vi_continued_command; +static char *vi_insert_buffer; +static int vi_insert_buffer_size; + +static int _rl_vi_last_repeat = 1; +static int _rl_vi_last_arg_sign = 1; +static int _rl_vi_last_motion; +#if defined (HANDLE_MULTIBYTE) +static char _rl_vi_last_search_mbchar[MB_LEN_MAX]; +static int _rl_vi_last_search_mblen; +#else +static int _rl_vi_last_search_char; +#endif +static int _rl_vi_last_replacement; + +static int _rl_vi_last_key_before_insert; + +static int vi_redoing; + +/* Text modification commands. These are the `redoable' commands. */ +static const char *vi_textmod = "_*\\AaIiCcDdPpYyRrSsXx~"; + +/* Arrays for the saved marks. */ +static int vi_mark_chars['z' - 'a' + 1]; + +static void _rl_vi_stuff_insert PARAMS((int)); +static void _rl_vi_save_insert PARAMS((UNDO_LIST *)); + +static void _rl_vi_backup PARAMS((void)); + +static int _rl_vi_arg_dispatch PARAMS((int)); +static int rl_digit_loop1 PARAMS((void)); + +static int _rl_vi_set_mark PARAMS((void)); +static int _rl_vi_goto_mark PARAMS((void)); + +static void _rl_vi_append_forward PARAMS((int)); + +static int _rl_vi_callback_getchar PARAMS((char *, int)); + +#if defined (READLINE_CALLBACKS) +static int _rl_vi_callback_set_mark PARAMS((_rl_callback_generic_arg *)); +static int _rl_vi_callback_goto_mark PARAMS((_rl_callback_generic_arg *)); +static int _rl_vi_callback_change_char PARAMS((_rl_callback_generic_arg *)); +static int _rl_vi_callback_char_search PARAMS((_rl_callback_generic_arg *)); +#endif + +void +_rl_vi_initialize_line () +{ + register size_t i; + + for (i = 0; i < sizeof (vi_mark_chars) / sizeof (int); i++) + vi_mark_chars[i] = -1; + + RL_UNSETSTATE(RL_STATE_VICMDONCE); +} + +void +_rl_vi_reset_last () +{ + _rl_vi_last_command = 'i'; + _rl_vi_last_repeat = 1; + _rl_vi_last_arg_sign = 1; + _rl_vi_last_motion = 0; +} + +void +_rl_vi_set_last (key, repeat, sign) + int key, repeat, sign; +{ + _rl_vi_last_command = key; + _rl_vi_last_repeat = repeat; + _rl_vi_last_arg_sign = sign; +} + +/* A convenience function that calls _rl_vi_set_last to save the last command + information and enters insertion mode. */ +void +rl_vi_start_inserting (key, repeat, sign) + int key, repeat, sign; +{ + _rl_vi_set_last (key, repeat, sign); + rl_vi_insertion_mode (1, key); +} + +/* Is the command C a VI mode text modification command? */ +int +_rl_vi_textmod_command (c) + int c; +{ + return (member (c, vi_textmod)); +} + +static void +_rl_vi_stuff_insert (count) + int count; +{ + rl_begin_undo_group (); + while (count--) + rl_insert_text (vi_insert_buffer); + rl_end_undo_group (); +} + +/* Bound to `.'. Called from command mode, so we know that we have to + redo a text modification command. The default for _rl_vi_last_command + puts you back into insert mode. */ +int +rl_vi_redo (count, c) + int count, c __attribute__((unused)); +{ + int r; + + if (!rl_explicit_arg) + { + rl_numeric_arg = _rl_vi_last_repeat; + rl_arg_sign = _rl_vi_last_arg_sign; + } + + r = 0; + vi_redoing = 1; + /* If we're redoing an insert with `i', stuff in the inserted text + and do not go into insertion mode. */ + if (_rl_vi_last_command == 'i' && vi_insert_buffer && *vi_insert_buffer) + { + _rl_vi_stuff_insert (count); + /* And back up point over the last character inserted. */ + if (rl_point > 0) + _rl_vi_backup (); + } + /* Ditto for redoing an insert with `a', but move forward a character first + like the `a' command does. */ + else if (_rl_vi_last_command == 'a' && vi_insert_buffer && *vi_insert_buffer) + { + _rl_vi_append_forward ('a'); + _rl_vi_stuff_insert (count); + if (rl_point > 0) + _rl_vi_backup (); + } + else + r = _rl_dispatch (_rl_vi_last_command, _rl_keymap); + vi_redoing = 0; + + return (r); +} + +/* A placeholder for further expansion. */ +int +rl_vi_undo (count, key) + int count, key; +{ + return (rl_undo_command (count, key)); +} + +/* Yank the nth arg from the previous line into this line at point. */ +int +rl_vi_yank_arg (count, key) + int count, key __attribute__((unused)); +{ + /* Readline thinks that the first word on a line is the 0th, while vi + thinks the first word on a line is the 1st. Compensate. */ + if (rl_explicit_arg) + rl_yank_nth_arg (count - 1, 0); + else + rl_yank_nth_arg ('$', 0); + + return (0); +} + +/* With an argument, move back that many history lines, else move to the + beginning of history. */ +int +rl_vi_fetch_history (count, c) + int count, c; +{ + int wanted; + + /* Giving an argument of n means we want the nth command in the history + file. The command number is interpreted the same way that the bash + `history' command does it -- that is, giving an argument count of 450 + to this command would get the command listed as number 450 in the + output of `history'. */ + if (rl_explicit_arg) + { + wanted = history_base + where_history () - count; + if (wanted <= 0) + rl_beginning_of_history (0, 0); + else + rl_get_previous_history (wanted, c); + } + else + rl_beginning_of_history (count, 0); + return (0); +} + +/* Search again for the last thing searched for. */ +int +rl_vi_search_again (count, key) + int count, key; +{ + switch (key) + { + case 'n': + rl_noninc_reverse_search_again (count, key); + break; + + case 'N': + rl_noninc_forward_search_again (count, key); + break; + } + return (0); +} + +/* Do a vi style search. */ +int +rl_vi_search (count, key) + int count, key; +{ + switch (key) + { + case '?': + _rl_free_saved_history_line (); + rl_noninc_forward_search (count, key); + break; + + case '/': + _rl_free_saved_history_line (); + rl_noninc_reverse_search (count, key); + break; + + default: + rl_ding (); + break; + } + return (0); +} + +/* Completion, from vi's point of view. */ +int +rl_vi_complete (ignore, key) + int ignore __attribute__((unused)), key; +{ + if ((rl_point < rl_end) && (!whitespace (rl_line_buffer[rl_point]))) + { + if (!whitespace (rl_line_buffer[rl_point + 1])) + rl_vi_end_word (1, 'E'); + rl_point++; + } + + if (key == '*') + rl_complete_internal ('*'); /* Expansion and replacement. */ + else if (key == '=') + rl_complete_internal ('?'); /* List possible completions. */ + else if (key == '\\') + rl_complete_internal (TAB); /* Standard Readline completion. */ + else + rl_complete (0, key); + + if (key == '*' || key == '\\') + rl_vi_start_inserting (key, 1, rl_arg_sign); + + return (0); +} + +/* Tilde expansion for vi mode. */ +int +rl_vi_tilde_expand (ignore, key) + int ignore __attribute__((unused)), key; +{ + rl_tilde_expand (0, key); + rl_vi_start_inserting (key, 1, rl_arg_sign); + return (0); +} + +/* Previous word in vi mode. */ +int +rl_vi_prev_word (count, key) + int count, key; +{ + if (count < 0) + return (rl_vi_next_word (-count, key)); + + if (rl_point == 0) + { + rl_ding (); + return (0); + } + + if (_rl_uppercase_p (key)) + rl_vi_bWord (count, key); + else + rl_vi_bword (count, key); + + return (0); +} + +/* Next word in vi mode. */ +int +rl_vi_next_word (count, key) + int count, key; +{ + if (count < 0) + return (rl_vi_prev_word (-count, key)); + + if (rl_point >= (rl_end - 1)) + { + rl_ding (); + return (0); + } + + if (_rl_uppercase_p (key)) + rl_vi_fWord (count, key); + else + rl_vi_fword (count, key); + return (0); +} + +/* Move to the end of the ?next? word. */ +int +rl_vi_end_word (count, key) + int count, key; +{ + if (count < 0) + { + rl_ding (); + return -1; + } + + if (_rl_uppercase_p (key)) + rl_vi_eWord (count, key); + else + rl_vi_eword (count, key); + return (0); +} + +/* Move forward a word the way that 'W' does. */ +int +rl_vi_fWord (count, ignore) + int count, ignore __attribute__((unused)); +{ + while (count-- && rl_point < (rl_end - 1)) + { + /* Skip until whitespace. */ + while (!whitespace (rl_line_buffer[rl_point]) && rl_point < rl_end) + rl_point++; + + /* Now skip whitespace. */ + while (whitespace (rl_line_buffer[rl_point]) && rl_point < rl_end) + rl_point++; + } + return (0); +} + +int +rl_vi_bWord (count, ignore) + int count, ignore __attribute__((unused)); +{ + while (count-- && rl_point > 0) + { + /* If we are at the start of a word, move back to whitespace so + we will go back to the start of the previous word. */ + if (!whitespace (rl_line_buffer[rl_point]) && + whitespace (rl_line_buffer[rl_point - 1])) + rl_point--; + + while (rl_point > 0 && whitespace (rl_line_buffer[rl_point])) + rl_point--; + + if (rl_point > 0) + { + while (--rl_point >= 0 && !whitespace (rl_line_buffer[rl_point])); + rl_point++; + } + } + return (0); +} + +int +rl_vi_eWord (count, ignore) + int count, ignore __attribute__((unused)); +{ + while (count-- && rl_point < (rl_end - 1)) + { + if (!whitespace (rl_line_buffer[rl_point])) + rl_point++; + + /* Move to the next non-whitespace character (to the start of the + next word). */ + while (rl_point < rl_end && whitespace (rl_line_buffer[rl_point])) + rl_point++; + + if (rl_point && rl_point < rl_end) + { + /* Skip whitespace. */ + while (rl_point < rl_end && whitespace (rl_line_buffer[rl_point])) + rl_point++; + + /* Skip until whitespace. */ + while (rl_point < rl_end && !whitespace (rl_line_buffer[rl_point])) + rl_point++; + + /* Move back to the last character of the word. */ + rl_point--; + } + } + return (0); +} + +int +rl_vi_fword (count, ignore) + int count, ignore __attribute__((unused)); +{ + while (count-- && rl_point < (rl_end - 1)) + { + /* Move to white space (really non-identifer). */ + if (_rl_isident (rl_line_buffer[rl_point])) + { + while (_rl_isident (rl_line_buffer[rl_point]) && rl_point < rl_end) + rl_point++; + } + else /* if (!whitespace (rl_line_buffer[rl_point])) */ + { + while (!_rl_isident (rl_line_buffer[rl_point]) && + !whitespace (rl_line_buffer[rl_point]) && rl_point < rl_end) + rl_point++; + } + + /* Move past whitespace. */ + while (whitespace (rl_line_buffer[rl_point]) && rl_point < rl_end) + rl_point++; + } + return (0); +} + +int +rl_vi_bword (count, ignore) + int count, ignore __attribute__((unused)); +{ + while (count-- && rl_point > 0) + { + int last_is_ident; + + /* If we are at the start of a word, move back to whitespace + so we will go back to the start of the previous word. */ + if (!whitespace (rl_line_buffer[rl_point]) && + whitespace (rl_line_buffer[rl_point - 1])) + rl_point--; + + /* If this character and the previous character are `opposite', move + back so we don't get messed up by the rl_point++ down there in + the while loop. Without this code, words like `l;' screw up the + function. */ + last_is_ident = _rl_isident (rl_line_buffer[rl_point - 1]); + if ((_rl_isident (rl_line_buffer[rl_point]) && !last_is_ident) || + (!_rl_isident (rl_line_buffer[rl_point]) && last_is_ident)) + rl_point--; + + while (rl_point > 0 && whitespace (rl_line_buffer[rl_point])) + rl_point--; + + if (rl_point > 0) + { + if (_rl_isident (rl_line_buffer[rl_point])) + while (--rl_point >= 0 && _rl_isident (rl_line_buffer[rl_point])); + else + while (--rl_point >= 0 && !_rl_isident (rl_line_buffer[rl_point]) && + !whitespace (rl_line_buffer[rl_point])); + rl_point++; + } + } + return (0); +} + +int +rl_vi_eword (count, ignore) + int count, ignore __attribute__((unused)); +{ + while (count-- && rl_point < rl_end - 1) + { + if (!whitespace (rl_line_buffer[rl_point])) + rl_point++; + + while (rl_point < rl_end && whitespace (rl_line_buffer[rl_point])) + rl_point++; + + if (rl_point < rl_end) + { + if (_rl_isident (rl_line_buffer[rl_point])) + while (++rl_point < rl_end && _rl_isident (rl_line_buffer[rl_point])); + else + while (++rl_point < rl_end && !_rl_isident (rl_line_buffer[rl_point]) + && !whitespace (rl_line_buffer[rl_point])); + } + rl_point--; + } + return (0); +} + +int +rl_vi_insert_beg (count, key) + int count __attribute__((unused)), key; +{ + rl_beg_of_line (1, key); + rl_vi_insertion_mode (1, key); + return (0); +} + +static void +_rl_vi_append_forward (key) + int key; +{ + int point; + + if (rl_point < rl_end) + { + if (MB_CUR_MAX == 1 || rl_byte_oriented) + rl_point++; + else + { + point = rl_point; + rl_forward_char (1, key); + if (point == rl_point) + rl_point = rl_end; + } + } +} + +int +rl_vi_append_mode (count, key) + int count __attribute__((unused)), key; +{ + _rl_vi_append_forward (key); + rl_vi_start_inserting (key, 1, rl_arg_sign); + return (0); +} + +int +rl_vi_append_eol (count, key) + int count __attribute__((unused)), key; +{ + rl_end_of_line (1, key); + rl_vi_append_mode (1, key); + return (0); +} + +/* What to do in the case of C-d. */ +int +rl_vi_eof_maybe (count, c) + int count __attribute__((unused)), c __attribute__((unused)); +{ + return (rl_newline (1, '\n')); +} + +/* Insertion mode stuff. */ + +/* Switching from one mode to the other really just involves + switching keymaps. */ +int +rl_vi_insertion_mode (count, key) + int count __attribute__((unused)), key; +{ + _rl_keymap = vi_insertion_keymap; + _rl_vi_last_key_before_insert = key; + return (0); +} + +static void +_rl_vi_save_insert (up) + UNDO_LIST *up; +{ + int len, start, end; + + if (up == 0 || up->what != UNDO_INSERT) + { + if (vi_insert_buffer_size >= 1) + vi_insert_buffer[0] = '\0'; + return; + } + + start = up->start; + end = up->end; + len = end - start + 1; + if (len >= vi_insert_buffer_size) + { + vi_insert_buffer_size += (len + 32) - (len % 32); + vi_insert_buffer = (char *)xrealloc (vi_insert_buffer, vi_insert_buffer_size); + } + strncpy (vi_insert_buffer, rl_line_buffer + start, len - 1); + vi_insert_buffer[len-1] = '\0'; +} + +void +_rl_vi_done_inserting () +{ + if (_rl_vi_doing_insert) + { + /* The `C', `s', and `S' commands set this. */ + rl_end_undo_group (); + /* Now, the text between rl_undo_list->next->start and + rl_undo_list->next->end is what was inserted while in insert + mode. It gets copied to VI_INSERT_BUFFER because it depends + on absolute indices into the line which may change (though they + probably will not). */ + _rl_vi_doing_insert = 0; + _rl_vi_save_insert (rl_undo_list->next); + vi_continued_command = 1; + } + else + { + if ((_rl_vi_last_key_before_insert == 'i' || _rl_vi_last_key_before_insert == 'a') && rl_undo_list) + _rl_vi_save_insert (rl_undo_list); + /* XXX - Other keys probably need to be checked. */ + else if (_rl_vi_last_key_before_insert == 'C') + rl_end_undo_group (); + while (_rl_undo_group_level > 0) + rl_end_undo_group (); + vi_continued_command = 0; + } +} + +int +rl_vi_movement_mode (count, key) + int count __attribute__((unused)), key; +{ + if (rl_point > 0) + rl_backward_char (1, key); + + _rl_keymap = vi_movement_keymap; + _rl_vi_done_inserting (); + + /* This is how POSIX.2 says `U' should behave -- everything up until the + first time you go into command mode should not be undone. */ + if (RL_ISSTATE (RL_STATE_VICMDONCE) == 0) + rl_free_undo_list (); + + RL_SETSTATE (RL_STATE_VICMDONCE); + return (0); +} + +int +rl_vi_arg_digit (count, c) + int count, c; +{ + if (c == '0' && rl_numeric_arg == 1 && !rl_explicit_arg) + return (rl_beg_of_line (1, c)); + else + return (rl_digit_argument (count, c)); +} + +/* Change the case of the next COUNT characters. */ +#if defined (HANDLE_MULTIBYTE) +static int +_rl_vi_change_mbchar_case (count) + int count; +{ + wchar_t wc; + char mb[MB_LEN_MAX+1]; + int mlen, p; + mbstate_t ps; + + memset (&ps, 0, sizeof (mbstate_t)); + if (_rl_adjust_point (rl_line_buffer, rl_point, &ps) > 0) + count--; + while (count-- && rl_point < rl_end) + { + mbrtowc (&wc, rl_line_buffer + rl_point, rl_end - rl_point, &ps); + if (iswupper (wc)) + wc = towlower (wc); + else if (iswlower (wc)) + wc = towupper (wc); + else + { + /* Just skip over chars neither upper nor lower case */ + rl_forward_char (1, 0); + continue; + } + + /* Vi is kind of strange here. */ + if (wc) + { + p = rl_point; + mlen = wcrtomb (mb, wc, &ps); + if (mlen >= 0) + mb[mlen] = '\0'; + rl_begin_undo_group (); + rl_vi_delete (1, 0); + if (rl_point < p) /* Did we retreat at EOL? */ + rl_point++; /* XXX - should we advance more than 1 for mbchar? */ + rl_insert_text (mb); + rl_end_undo_group (); + rl_vi_check (); + } + else + rl_forward_char (1, 0); + } + + return 0; +} +#endif + +int +rl_vi_change_case (count, ignore) + int count, ignore __attribute__((unused)); +{ + int c, p; + + /* Don't try this on an empty line. */ + if (rl_point >= rl_end) + return (0); + + c = 0; +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + return (_rl_vi_change_mbchar_case (count)); +#endif + + while (count-- && rl_point < rl_end) + { + if (_rl_uppercase_p (rl_line_buffer[rl_point])) + c = _rl_to_lower (rl_line_buffer[rl_point]); + else if (_rl_lowercase_p (rl_line_buffer[rl_point])) + c = _rl_to_upper (rl_line_buffer[rl_point]); + else + { + /* Just skip over characters neither upper nor lower case. */ + rl_forward_char (1, c); + continue; + } + + /* Vi is kind of strange here. */ + if (c) + { + p = rl_point; + rl_begin_undo_group (); + rl_vi_delete (1, c); + if (rl_point < p) /* Did we retreat at EOL? */ + rl_point++; + _rl_insert_char (1, c); + rl_end_undo_group (); + rl_vi_check (); + } + else + rl_forward_char (1, c); + } + return (0); +} + +int +rl_vi_put (count, key) + int count, key; +{ + if (!_rl_uppercase_p (key) && (rl_point + 1 <= rl_end)) + rl_point = _rl_find_next_mbchar (rl_line_buffer, rl_point, 1, MB_FIND_NONZERO); + + while (count--) + rl_yank (1, key); + + rl_backward_char (1, key); + return (0); +} + +static void +_rl_vi_backup () +{ + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + rl_point = _rl_find_prev_mbchar (rl_line_buffer, rl_point, MB_FIND_NONZERO); + else + rl_point--; +} + +int +rl_vi_check () +{ + if (rl_point && rl_point == rl_end) + { + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + rl_point = _rl_find_prev_mbchar (rl_line_buffer, rl_point, MB_FIND_NONZERO); + else + rl_point--; + } + return (0); +} + +int +rl_vi_column (count, key) + int count, key; +{ + if (count > rl_end) + rl_end_of_line (1, key); + else + rl_point = count - 1; + return (0); +} + +int +rl_vi_domove (key, nextkey) + int key, *nextkey; +{ + int c, save; + int old_end; + + rl_mark = rl_point; + RL_SETSTATE(RL_STATE_MOREINPUT); + c = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); + *nextkey = c; + + if (!member (c, vi_motion)) + { + if (_rl_digit_p (c)) + { + save = rl_numeric_arg; + rl_numeric_arg = _rl_digit_value (c); + rl_explicit_arg = 1; + RL_SETSTATE (RL_STATE_NUMERICARG|RL_STATE_VIMOTION); + rl_digit_loop1 (); + RL_UNSETSTATE (RL_STATE_VIMOTION); + rl_numeric_arg *= save; + RL_SETSTATE(RL_STATE_MOREINPUT); + c = rl_read_key (); /* real command */ + RL_UNSETSTATE(RL_STATE_MOREINPUT); + *nextkey = c; + } + else if (key == c && (key == 'd' || key == 'y' || key == 'c')) + { + rl_mark = rl_end; + rl_beg_of_line (1, c); + _rl_vi_last_motion = c; + return (0); + } + else + return (-1); + } + + _rl_vi_last_motion = c; + + /* Append a blank character temporarily so that the motion routines + work right at the end of the line. */ + old_end = rl_end; + rl_line_buffer[rl_end++] = ' '; + rl_line_buffer[rl_end] = '\0'; + + _rl_dispatch (c, _rl_keymap); + + /* Remove the blank that we added. */ + rl_end = old_end; + rl_line_buffer[rl_end] = '\0'; + if (rl_point > rl_end) + rl_point = rl_end; + + /* No change in position means the command failed. */ + if (rl_mark == rl_point) + return (-1); + + /* rl_vi_f[wW]ord () leaves the cursor on the first character of the next + word. If we are not at the end of the line, and we are on a + non-whitespace character, move back one (presumably to whitespace). */ + if ((_rl_to_upper (c) == 'W') && rl_point < rl_end && rl_point > rl_mark && + !whitespace (rl_line_buffer[rl_point])) + rl_point--; + + /* If cw or cW, back up to the end of a word, so the behaviour of ce + or cE is the actual result. Brute-force, no subtlety. */ + if (key == 'c' && rl_point >= rl_mark && (_rl_to_upper (c) == 'W')) + { + /* Don't move farther back than where we started. */ + while (rl_point > rl_mark && whitespace (rl_line_buffer[rl_point])) + rl_point--; + + /* Posix.2 says that if cw or cW moves the cursor towards the end of + the line, the character under the cursor should be deleted. */ + if (rl_point == rl_mark) + rl_point++; + else + { + /* Move past the end of the word so that the kill doesn't + remove the last letter of the previous word. Only do this + if we are not at the end of the line. */ + if (rl_point >= 0 && rl_point < (rl_end - 1) && !whitespace (rl_line_buffer[rl_point])) + rl_point++; + } + } + + if (rl_mark < rl_point) + SWAP (rl_point, rl_mark); + + return (0); +} + +/* Process C as part of the current numeric argument. Return -1 if the + argument should be aborted, 0 if we should not read any more chars, and + 1 if we should continue to read chars. */ +static int +_rl_vi_arg_dispatch (c) + int c; +{ + int key; + + key = c; + if (c >= 0 && _rl_keymap[c].type == ISFUNC && _rl_keymap[c].function == rl_universal_argument) + { + rl_numeric_arg *= 4; + return 1; + } + + c = UNMETA (c); + + if (_rl_digit_p (c)) + { + if (rl_explicit_arg) + rl_numeric_arg = (rl_numeric_arg * 10) + _rl_digit_value (c); + else + rl_numeric_arg = _rl_digit_value (c); + rl_explicit_arg = 1; + return 1; + } + else + { + rl_clear_message (); + rl_stuff_char (key); + return 0; + } +} + +/* A simplified loop for vi. Don't dispatch key at end. + Don't recognize minus sign? + Should this do rl_save_prompt/rl_restore_prompt? */ +static int +rl_digit_loop1 () +{ + int c, r; + + while (1) + { + if (_rl_arg_overflow ()) + return 1; + + c = _rl_arg_getchar (); + + r = _rl_vi_arg_dispatch (c); + if (r <= 0) + break; + } + + RL_UNSETSTATE(RL_STATE_NUMERICARG); + return (0); +} + +int +rl_vi_delete_to (count, key) + int count __attribute__((unused)), key; +{ + int c; + + if (_rl_uppercase_p (key)) + rl_stuff_char ('$'); + else if (vi_redoing) + rl_stuff_char (_rl_vi_last_motion); + + if (rl_vi_domove (key, &c)) + { + rl_ding (); + return -1; + } + + /* These are the motion commands that do not require adjusting the + mark. */ + if ((strchr (" l|h^0bB", c) == 0) && (rl_mark < rl_end)) + rl_mark++; + + rl_kill_text (rl_point, rl_mark); + return (0); +} + +int +rl_vi_change_to (count, key) + int count __attribute__((unused)), key; +{ + int c, start_pos; + + if (_rl_uppercase_p (key)) + rl_stuff_char ('$'); + else if (vi_redoing) + rl_stuff_char (_rl_vi_last_motion); + + start_pos = rl_point; + + if (rl_vi_domove (key, &c)) + { + rl_ding (); + return -1; + } + + /* These are the motion commands that do not require adjusting the + mark. c[wW] are handled by special-case code in rl_vi_domove(), + and already leave the mark at the correct location. */ + if ((strchr (" l|hwW^0bB", c) == 0) && (rl_mark < rl_end)) + rl_mark++; + + /* The cursor never moves with c[wW]. */ + if ((_rl_to_upper (c) == 'W') && rl_point < start_pos) + rl_point = start_pos; + + if (vi_redoing) + { + if (vi_insert_buffer && *vi_insert_buffer) + rl_begin_undo_group (); + rl_delete_text (rl_point, rl_mark); + if (vi_insert_buffer && *vi_insert_buffer) + { + rl_insert_text (vi_insert_buffer); + rl_end_undo_group (); + } + } + else + { + rl_begin_undo_group (); /* to make the `u' command work */ + rl_kill_text (rl_point, rl_mark); + /* `C' does not save the text inserted for undoing or redoing. */ + if (_rl_uppercase_p (key) == 0) + _rl_vi_doing_insert = 1; + rl_vi_start_inserting (key, rl_numeric_arg, rl_arg_sign); + } + + return (0); +} + +int +rl_vi_yank_to (count, key) + int count __attribute__((unused)), key; +{ + int c, save; + + save = rl_point; + if (_rl_uppercase_p (key)) + rl_stuff_char ('$'); + + if (rl_vi_domove (key, &c)) + { + rl_ding (); + return -1; + } + + /* These are the motion commands that do not require adjusting the + mark. */ + if ((strchr (" l|h^0%bB", c) == 0) && (rl_mark < rl_end)) + rl_mark++; + + rl_begin_undo_group (); + rl_kill_text (rl_point, rl_mark); + rl_end_undo_group (); + rl_do_undo (); + rl_point = save; + + return (0); +} + +int +rl_vi_rubout (count, key) + int count, key; +{ + int opoint; + + if (count < 0) + return (rl_vi_delete (-count, key)); + + if (rl_point == 0) + { + rl_ding (); + return -1; + } + + opoint = rl_point; + if (count > 1 && MB_CUR_MAX > 1 && rl_byte_oriented == 0) + rl_backward_char (count, key); + else if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + rl_point = _rl_find_prev_mbchar (rl_line_buffer, rl_point, MB_FIND_NONZERO); + else + rl_point -= count; + + if (rl_point < 0) + rl_point = 0; + + rl_kill_text (rl_point, opoint); + + return (0); +} + +int +rl_vi_delete (count, key) + int count, key; +{ + int end; + + if (count < 0) + return (rl_vi_rubout (-count, key)); + + if (rl_end == 0) + { + rl_ding (); + return -1; + } + + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + end = _rl_find_next_mbchar (rl_line_buffer, rl_point, count, MB_FIND_NONZERO); + else + end = rl_point + count; + + if (end >= rl_end) + end = rl_end; + + rl_kill_text (rl_point, end); + + if (rl_point > 0 && rl_point == rl_end) + rl_backward_char (1, key); + + return (0); +} + +int +rl_vi_back_to_indent (count, key) + int count __attribute__((unused)), key; +{ + rl_beg_of_line (1, key); + while (rl_point < rl_end && whitespace (rl_line_buffer[rl_point])) + rl_point++; + return (0); +} + +int +rl_vi_first_print (count, key) + int count __attribute__((unused)), key; +{ + return (rl_vi_back_to_indent (1, key)); +} + +static int _rl_cs_dir, _rl_cs_orig_dir; + +#if defined (READLINE_CALLBACKS) +static int +_rl_vi_callback_char_search (data) + _rl_callback_generic_arg *data; +{ +#if defined (HANDLE_MULTIBYTE) + _rl_vi_last_search_mblen = _rl_read_mbchar (_rl_vi_last_search_mbchar, MB_LEN_MAX); +#else + RL_SETSTATE(RL_STATE_MOREINPUT); + _rl_vi_last_search_char = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); +#endif + + _rl_callback_func = 0; + _rl_want_redisplay = 1; + +#if defined (HANDLE_MULTIBYTE) + return (_rl_char_search_internal (data->count, _rl_cs_dir, _rl_vi_last_search_mbchar, _rl_vi_last_search_mblen)); +#else + return (_rl_char_search_internal (data->count, _rl_cs_dir, _rl_vi_last_search_char)); +#endif +} +#endif + +int +rl_vi_char_search (count, key) + int count, key; +{ +#if defined (HANDLE_MULTIBYTE) + static char *target; + static int tlen; +#else + static char target; +#endif + + if (key == ';' || key == ',') + _rl_cs_dir = (key == ';') ? _rl_cs_orig_dir : -_rl_cs_orig_dir; + else + { + switch (key) + { + case 't': + _rl_cs_orig_dir = _rl_cs_dir = FTO; + break; + + case 'T': + _rl_cs_orig_dir = _rl_cs_dir = BTO; + break; + + case 'f': + _rl_cs_orig_dir = _rl_cs_dir = FFIND; + break; + + case 'F': + _rl_cs_orig_dir = _rl_cs_dir = BFIND; + break; + } + + if (vi_redoing) + { + /* set target and tlen below */ + } +#if defined (READLINE_CALLBACKS) + else if (RL_ISSTATE (RL_STATE_CALLBACK)) + { + _rl_callback_data = _rl_callback_data_alloc (count); + _rl_callback_data->i1 = _rl_cs_dir; + _rl_callback_func = _rl_vi_callback_char_search; + return (0); + } +#endif + else + { +#if defined (HANDLE_MULTIBYTE) + _rl_vi_last_search_mblen = _rl_read_mbchar (_rl_vi_last_search_mbchar, MB_LEN_MAX); +#else + RL_SETSTATE(RL_STATE_MOREINPUT); + _rl_vi_last_search_char = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); +#endif + } + } + +#if defined (HANDLE_MULTIBYTE) + target = _rl_vi_last_search_mbchar; + tlen = _rl_vi_last_search_mblen; +#else + target = _rl_vi_last_search_char; +#endif + +#if defined (HANDLE_MULTIBYTE) + return (_rl_char_search_internal (count, _rl_cs_dir, target, tlen)); +#else + return (_rl_char_search_internal (count, _rl_cs_dir, target)); +#endif +} + +/* Match brackets */ +int +rl_vi_match (ignore, key) + int ignore __attribute__((unused)), key; +{ + int count = 1, brack, pos, tmp, pre; + + pos = rl_point; + if ((brack = rl_vi_bracktype (rl_line_buffer[rl_point])) == 0) + { + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + { + while ((brack = rl_vi_bracktype (rl_line_buffer[rl_point])) == 0) + { + pre = rl_point; + rl_forward_char (1, key); + if (pre == rl_point) + break; + } + } + else + while ((brack = rl_vi_bracktype (rl_line_buffer[rl_point])) == 0 && + rl_point < rl_end - 1) + rl_forward_char (1, key); + + if (brack <= 0) + { + rl_point = pos; + rl_ding (); + return -1; + } + } + + pos = rl_point; + + if (brack < 0) + { + while (count) + { + tmp = pos; + if (MB_CUR_MAX == 1 || rl_byte_oriented) + pos--; + else + { + pos = _rl_find_prev_mbchar (rl_line_buffer, pos, MB_FIND_ANY); + if (tmp == pos) + pos--; + } + if (pos >= 0) + { + int b = rl_vi_bracktype (rl_line_buffer[pos]); + if (b == -brack) + count--; + else if (b == brack) + count++; + } + else + { + rl_ding (); + return -1; + } + } + } + else + { /* brack > 0 */ + while (count) + { + if (MB_CUR_MAX == 1 || rl_byte_oriented) + pos++; + else + pos = _rl_find_next_mbchar (rl_line_buffer, pos, 1, MB_FIND_ANY); + + if (pos < rl_end) + { + int b = rl_vi_bracktype (rl_line_buffer[pos]); + if (b == -brack) + count--; + else if (b == brack) + count++; + } + else + { + rl_ding (); + return -1; + } + } + } + rl_point = pos; + return (0); +} + +int +rl_vi_bracktype (c) + int c; +{ + switch (c) + { + case '(': return 1; + case ')': return -1; + case '[': return 2; + case ']': return -2; + case '{': return 3; + case '}': return -3; + default: return 0; + } +} + +static int +_rl_vi_change_char (count, c, mb) + int count, c; + char *mb __attribute__((unused)); +{ + int p; + + if (c == '\033' || c == CTRL ('C')) + return -1; + + rl_begin_undo_group (); + while (count-- && rl_point < rl_end) + { + p = rl_point; + rl_vi_delete (1, c); + if (rl_point < p) /* Did we retreat at EOL? */ + rl_point++; +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + rl_insert_text (mb); + else +#endif + _rl_insert_char (1, c); + } + + /* The cursor shall be left on the last character changed. */ + rl_backward_char (1, c); + + rl_end_undo_group (); + + return (0); +} + +static int +_rl_vi_callback_getchar (mb, mlen) + char *mb __attribute__((unused)); + int mlen __attribute__((unused)); +{ + int c; + + RL_SETSTATE(RL_STATE_MOREINPUT); + c = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); + +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + c = _rl_read_mbstring (c, mb, mlen); +#endif + + return c; +} + +#if defined (READLINE_CALLBACKS) +static int +_rl_vi_callback_change_char (data) + _rl_callback_generic_arg *data; +{ + int c; + char mb[MB_LEN_MAX]; + + _rl_vi_last_replacement = c = _rl_vi_callback_getchar (mb, MB_LEN_MAX); + + _rl_callback_func = 0; + _rl_want_redisplay = 1; + + return (_rl_vi_change_char (data->count, c, mb)); +} +#endif + +int +rl_vi_change_char (count, key) + int count, key __attribute__((unused)); +{ + int c; + char mb[MB_LEN_MAX]; + + if (vi_redoing) + { + c = _rl_vi_last_replacement; + mb[0] = c; + mb[1] = '\0'; + } +#if defined (READLINE_CALLBACKS) + else if (RL_ISSTATE (RL_STATE_CALLBACK)) + { + _rl_callback_data = _rl_callback_data_alloc (count); + _rl_callback_func = _rl_vi_callback_change_char; + return (0); + } +#endif + else + _rl_vi_last_replacement = c = _rl_vi_callback_getchar (mb, MB_LEN_MAX); + + return (_rl_vi_change_char (count, c, mb)); +} + +int +rl_vi_subst (count, key) + int count, key; +{ + /* If we are redoing, rl_vi_change_to will stuff the last motion char */ + if (vi_redoing == 0) + rl_stuff_char ((key == 'S') ? 'c' : 'l'); /* `S' == `cc', `s' == `cl' */ + + return (rl_vi_change_to (count, 'c')); +} + +int +rl_vi_overstrike (count, key) + int count, key; +{ + if (_rl_vi_doing_insert == 0) + { + _rl_vi_doing_insert = 1; + rl_begin_undo_group (); + } + + if (count > 0) + { + _rl_overwrite_char (count, key); + vi_replace_count += count; + } + + return (0); +} + +int +rl_vi_overstrike_delete (count, key) + int count, key; +{ + int i, s; + + for (i = 0; i < count; i++) + { + if (vi_replace_count == 0) + { + rl_ding (); + break; + } + s = rl_point; + + if (rl_do_undo ()) + vi_replace_count--; + + if (rl_point == s) + rl_backward_char (1, key); + } + + if (vi_replace_count == 0 && _rl_vi_doing_insert) + { + rl_end_undo_group (); + rl_do_undo (); + _rl_vi_doing_insert = 0; + } + return (0); +} + +int +rl_vi_replace (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ + int i; + + vi_replace_count = 0; + + if (!vi_replace_map) + { + vi_replace_map = rl_make_bare_keymap (); + + for (i = ' '; i < KEYMAP_SIZE; i++) + vi_replace_map[i].function = rl_vi_overstrike; + + vi_replace_map[RUBOUT].function = rl_vi_overstrike_delete; + vi_replace_map[ESC].function = rl_vi_movement_mode; + vi_replace_map[RETURN].function = rl_newline; + vi_replace_map[NEWLINE].function = rl_newline; + + /* If the normal vi insertion keymap has ^H bound to erase, do the + same here. Probably should remove the assignment to RUBOUT up + there, but I don't think it will make a difference in real life. */ + if (vi_insertion_keymap[CTRL ('H')].type == ISFUNC && + vi_insertion_keymap[CTRL ('H')].function == rl_rubout) + vi_replace_map[CTRL ('H')].function = rl_vi_overstrike_delete; + + } + _rl_keymap = vi_replace_map; + return (0); +} + +#if 0 +/* Try to complete the word we are standing on or the word that ends with + the previous character. A space matches everything. Word delimiters are + space and ;. */ +int +rl_vi_possible_completions() +{ + int save_pos = rl_point; + + if (rl_line_buffer[rl_point] != ' ' && rl_line_buffer[rl_point] != ';') + { + while (rl_point < rl_end && rl_line_buffer[rl_point] != ' ' && + rl_line_buffer[rl_point] != ';') + rl_point++; + } + else if (rl_line_buffer[rl_point - 1] == ';') + { + rl_ding (); + return (0); + } + + rl_possible_completions (); + rl_point = save_pos; + + return (0); +} +#endif + +/* Functions to save and restore marks. */ +static int +_rl_vi_set_mark () +{ + int ch; + + RL_SETSTATE(RL_STATE_MOREINPUT); + ch = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); + + if (ch < 'a' || ch > 'z') + { + rl_ding (); + return -1; + } + ch -= 'a'; + vi_mark_chars[ch] = rl_point; + return 0; +} + +#if defined (READLINE_CALLBACKS) +static int +_rl_vi_callback_set_mark (data) + _rl_callback_generic_arg *data __attribute__((unused)); +{ + _rl_callback_func = 0; + _rl_want_redisplay = 1; + + return (_rl_vi_set_mark ()); +} +#endif + +int +rl_vi_set_mark (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ +#if defined (READLINE_CALLBACKS) + if (RL_ISSTATE (RL_STATE_CALLBACK)) + { + _rl_callback_data = 0; + _rl_callback_func = _rl_vi_callback_set_mark; + return (0); + } +#endif + + return (_rl_vi_set_mark ()); +} + +static int +_rl_vi_goto_mark () +{ + int ch; + + RL_SETSTATE(RL_STATE_MOREINPUT); + ch = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); + + if (ch == '`') + { + rl_point = rl_mark; + return 0; + } + else if (ch < 'a' || ch > 'z') + { + rl_ding (); + return -1; + } + + ch -= 'a'; + if (vi_mark_chars[ch] == -1) + { + rl_ding (); + return -1; + } + rl_point = vi_mark_chars[ch]; + return 0; +} + +#if defined (READLINE_CALLBACKS) +static int +_rl_vi_callback_goto_mark (data) + _rl_callback_generic_arg *data __attribute__((unused)); +{ + _rl_callback_func = 0; + _rl_want_redisplay = 1; + + return (_rl_vi_goto_mark ()); +} +#endif + +int +rl_vi_goto_mark (count, key) + int count __attribute__((unused)), key __attribute__((unused)); +{ +#if defined (READLINE_CALLBACKS) + if (RL_ISSTATE (RL_STATE_CALLBACK)) + { + _rl_callback_data = 0; + _rl_callback_func = _rl_vi_callback_goto_mark; + return (0); + } +#endif + + return (_rl_vi_goto_mark ()); +} +#endif /* VI_MODE */ diff --git a/extra/readline/xmalloc.c b/extra/readline/xmalloc.c new file mode 100644 index 00000000000..60a142ec1c3 --- /dev/null +++ b/extra/readline/xmalloc.c @@ -0,0 +1,88 @@ +/* xmalloc.c -- safe versions of malloc and realloc */ + +/* Copyright (C) 1991 Free Software Foundation, Inc. + + This file is part of GNU Readline, a library for reading lines + of text with interactive input and history editing. + + Readline is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any + later version. + + Readline 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Readline; see the file COPYING. If not, write to the Free + Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +#include "config_readline.h" +#endif + +#include <stdio.h> + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#include "xmalloc.h" + +/* **************************************************************** */ +/* */ +/* Memory Allocation and Deallocation. */ +/* */ +/* **************************************************************** */ + +static void +memory_error_and_abort (fname) + const char *fname; +{ + fprintf (stderr, "%s: out of virtual memory\n", fname); + exit (2); +} + +/* Return a pointer to free()able block of memory large enough + to hold BYTES number of bytes. If the memory cannot be allocated, + print an error message and abort. */ +PTR_T +xmalloc (bytes) + size_t bytes; +{ + PTR_T temp; + + temp = malloc (bytes); + if (temp == 0) + memory_error_and_abort ("xmalloc"); + return (temp); +} + +PTR_T +xrealloc (pointer, bytes) + PTR_T pointer; + size_t bytes; +{ + PTR_T temp; + + temp = pointer ? realloc (pointer, bytes) : malloc (bytes); + + if (temp == 0) + memory_error_and_abort ("xrealloc"); + return (temp); +} + +/* Use this as the function to call when adding unwind protects so we + don't need to know what free() returns. */ +void +xfree (string) + PTR_T string; +{ + if (string) + free (string); +} diff --git a/extra/readline/xmalloc.h b/extra/readline/xmalloc.h new file mode 100644 index 00000000000..58b17f39f3d --- /dev/null +++ b/extra/readline/xmalloc.h @@ -0,0 +1,46 @@ +/* xmalloc.h -- memory allocation that aborts on errors. */ + +/* Copyright (C) 1999 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline 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 General Public License for more details. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#if !defined (_XMALLOC_H_) +#define _XMALLOC_H_ + +#if defined (READLINE_LIBRARY) +# include "rlstdc.h" +#else +# include <rlstdc.h> +#endif + +#ifndef PTR_T + +#ifdef __STDC__ +# define PTR_T void * +#else +# define PTR_T char * +#endif + +#endif /* !PTR_T */ + +extern PTR_T xmalloc PARAMS((size_t)); +extern PTR_T xrealloc PARAMS((void *, size_t)); +extern void xfree PARAMS((void *)); + +#endif /* _XMALLOC_H_ */ diff --git a/extra/replace.c b/extra/replace.c index 465c0c48f6e..b8c328f2902 100644 --- a/extra/replace.c +++ b/extra/replace.c @@ -36,7 +36,7 @@ The programs make a DFA-state-machine of the strings and the speed isn't dependent on the count of replace-strings (only of the number of replaces). A line is assumed ending with \n or \0. - There are no limit exept memory on length of strings. + There are no limit except memory on length of strings. Written by Monty. fill_buffer_retaining() is taken from gnu-grep and modified. diff --git a/extra/yassl/src/ssl.cpp b/extra/yassl/src/ssl.cpp index 982d462e3b8..28927c41c5c 100644 --- a/extra/yassl/src/ssl.cpp +++ b/extra/yassl/src/ssl.cpp @@ -1006,7 +1006,7 @@ void OpenSSL_add_all_algorithms() // compatibility only {} -int SSL_library_init() // compatiblity only +int SSL_library_init() // compatibility only { return 1; } |