summaryrefslogtreecommitdiff
path: root/extra
diff options
context:
space:
mode:
Diffstat (limited to 'extra')
-rw-r--r--extra/CMakeLists.txt48
-rw-r--r--extra/charset2html.c2
-rw-r--r--extra/innochecksum.cc656
-rw-r--r--extra/mariabackup/CMakeLists.txt214
-rw-r--r--extra/mariabackup/backup_copy.cc2075
-rw-r--r--extra/mariabackup/backup_copy.h49
-rw-r--r--extra/mariabackup/backup_mysql.cc1648
-rw-r--r--extra/mariabackup/backup_mysql.h92
-rw-r--r--extra/mariabackup/changed_page_bitmap.cc1018
-rw-r--r--extra/mariabackup/changed_page_bitmap.h85
-rw-r--r--extra/mariabackup/common.h174
-rw-r--r--extra/mariabackup/crc/CMakeLists.txt33
-rw-r--r--extra/mariabackup/crc/config.h.cmake21
-rw-r--r--extra/mariabackup/crc/crc-intel-pclmul.c511
-rw-r--r--extra/mariabackup/crc/crc-intel-pclmul.h25
-rw-r--r--extra/mariabackup/crc/crc_glue.c72
-rw-r--r--extra/mariabackup/crc/crc_glue.h31
-rw-r--r--extra/mariabackup/datasink.c137
-rw-r--r--extra/mariabackup/datasink.h100
-rw-r--r--extra/mariabackup/ds_archive.c280
-rw-r--r--extra/mariabackup/ds_archive.h28
-rw-r--r--extra/mariabackup/ds_buffer.c189
-rw-r--r--extra/mariabackup/ds_buffer.h39
-rw-r--r--extra/mariabackup/ds_compress.c462
-rw-r--r--extra/mariabackup/ds_compress.h28
-rw-r--r--extra/mariabackup/ds_decrypt.c665
-rw-r--r--extra/mariabackup/ds_decrypt.h30
-rw-r--r--extra/mariabackup/ds_encrypt.c446
-rw-r--r--extra/mariabackup/ds_encrypt.h33
-rw-r--r--extra/mariabackup/ds_local.c151
-rw-r--r--extra/mariabackup/ds_local.h28
-rw-r--r--extra/mariabackup/ds_stdout.c121
-rw-r--r--extra/mariabackup/ds_stdout.h28
-rw-r--r--extra/mariabackup/ds_tmpfile.c247
-rw-r--r--extra/mariabackup/ds_tmpfile.h30
-rw-r--r--extra/mariabackup/ds_xbstream.c223
-rw-r--r--extra/mariabackup/ds_xbstream.h28
-rw-r--r--extra/mariabackup/encryption_plugin.cc157
-rw-r--r--extra/mariabackup/encryption_plugin.h7
-rw-r--r--extra/mariabackup/fil_cur.cc409
-rw-r--r--extra/mariabackup/fil_cur.h123
-rw-r--r--extra/mariabackup/innobackupex.cc1132
-rw-r--r--extra/mariabackup/innobackupex.h45
-rw-r--r--extra/mariabackup/quicklz/quicklz.c848
-rw-r--r--extra/mariabackup/quicklz/quicklz.h144
-rw-r--r--extra/mariabackup/read_filt.cc206
-rw-r--r--extra/mariabackup/read_filt.h62
-rw-r--r--extra/mariabackup/write_filt.cc219
-rw-r--r--extra/mariabackup/write_filt.h58
-rw-r--r--extra/mariabackup/wsrep.cc220
-rw-r--r--extra/mariabackup/wsrep.h32
-rw-r--r--extra/mariabackup/xb0xb.h78
-rw-r--r--extra/mariabackup/xb_regex.h48
-rw-r--r--extra/mariabackup/xbcloud.cc2721
-rw-r--r--extra/mariabackup/xbcrypt.c696
-rw-r--r--extra/mariabackup/xbcrypt.h79
-rw-r--r--extra/mariabackup/xbcrypt_common.c328
-rw-r--r--extra/mariabackup/xbcrypt_common.h64
-rw-r--r--extra/mariabackup/xbcrypt_read.c252
-rw-r--r--extra/mariabackup/xbcrypt_write.c105
-rw-r--r--extra/mariabackup/xbstream.c613
-rw-r--r--extra/mariabackup/xbstream.h107
-rw-r--r--extra/mariabackup/xbstream_read.c228
-rw-r--r--extra/mariabackup/xbstream_write.c294
-rw-r--r--extra/mariabackup/xtrabackup.cc7501
-rw-r--r--extra/mariabackup/xtrabackup.h247
-rw-r--r--extra/my_print_defaults.c11
-rw-r--r--extra/perror.c58
-rw-r--r--extra/readline/CMakeLists.txt59
-rw-r--r--extra/readline/COPYING339
-rw-r--r--extra/readline/INSTALL287
-rw-r--r--extra/readline/README186
-rw-r--r--extra/readline/ansi_stdlib.h54
-rw-r--r--extra/readline/bind.c2311
-rw-r--r--extra/readline/callback.c258
-rw-r--r--extra/readline/chardefs.h166
-rw-r--r--extra/readline/compat.c113
-rw-r--r--extra/readline/complete.c2223
-rw-r--r--extra/readline/config_readline.h37
-rw-r--r--extra/readline/configure.in296
-rw-r--r--extra/readline/display.c2452
-rw-r--r--extra/readline/emacs_keymap.c873
-rw-r--r--extra/readline/funmap.c255
-rw-r--r--extra/readline/histexpand.c1592
-rw-r--r--extra/readline/histfile.c550
-rw-r--r--extra/readline/histlib.h82
-rw-r--r--extra/readline/history.c518
-rw-r--r--extra/readline/history.h266
-rw-r--r--extra/readline/histsearch.c195
-rw-r--r--extra/readline/input.c585
-rw-r--r--extra/readline/isearch.c666
-rw-r--r--extra/readline/keymaps.c149
-rw-r--r--extra/readline/keymaps.h103
-rw-r--r--extra/readline/kill.c694
-rw-r--r--extra/readline/macro.c271
-rw-r--r--extra/readline/mbutil.c373
-rw-r--r--extra/readline/misc.c603
-rw-r--r--extra/readline/nls.c254
-rw-r--r--extra/readline/parens.c183
-rw-r--r--extra/readline/posixdir.h61
-rw-r--r--extra/readline/posixjmp.h40
-rw-r--r--extra/readline/posixstat.h142
-rw-r--r--extra/readline/readline.c1200
-rw-r--r--extra/readline/readline.h853
-rw-r--r--extra/readline/rlconf.h63
-rw-r--r--extra/readline/rldefs.h160
-rw-r--r--extra/readline/rlmbutil.h155
-rw-r--r--extra/readline/rlprivate.h425
-rw-r--r--extra/readline/rlshell.h34
-rw-r--r--extra/readline/rlstdc.h45
-rw-r--r--extra/readline/rltty.c1033
-rw-r--r--extra/readline/rltty.h82
-rw-r--r--extra/readline/rltypedefs.h94
-rw-r--r--extra/readline/rlwinsize.h57
-rw-r--r--extra/readline/savestring.c37
-rw-r--r--extra/readline/search.c571
-rw-r--r--extra/readline/shell.c208
-rw-r--r--extra/readline/signals.c466
-rw-r--r--extra/readline/tcap.h60
-rw-r--r--extra/readline/terminal.c735
-rw-r--r--extra/readline/text.c1639
-rw-r--r--extra/readline/tilde.c502
-rw-r--r--extra/readline/tilde.h81
-rw-r--r--extra/readline/undo.c330
-rw-r--r--extra/readline/util.c360
-rw-r--r--extra/readline/vi_keymap.c877
-rw-r--r--extra/readline/vi_mode.c1748
-rw-r--r--extra/readline/xmalloc.c88
-rw-r--r--extra/readline/xmalloc.h46
-rw-r--r--extra/replace.c2
-rw-r--r--extra/yassl/src/ssl.cpp2
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", &gtid_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", &gtid_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", &gtid_executed},
+ {NULL, NULL}
+ };
+
+ mysql_variable variables[] = {
+ {"gtid_slave_pos", &gtid_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", &gtid_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", &gtid_executed},
+ {NULL, NULL}
+ };
+
+ mysql_variable vars[] = {
+ {"gtid_mode", &gtid_mode},
+ {"gtid_current_pos", &gtid_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,
+ &current_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(&regex, 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", &regex_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", &regex_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(&regex_include_list);
+ xb_regex_list_free(&regex_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, &macro_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, &quote_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, &quote_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, &quote_char);
+ }
+ else
+ {
+ insert_match (matches[match_list_index], orig_start, SINGLE_MATCH, &quote_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;
}