mariadb/extra/replace.c
Michael Widenius a260b15554 MDEV-4011 Added per thread memory counting and usage
Base code and idea from a patch from by plinux at Taobao.

The idea is that we mark all memory that are thread specific with MY_THREAD_SPECIFIC.
Memory counting is done per thread in the my_malloc_size_cb_func callback function from my_malloc().
There are plenty of new asserts to ensure that for a debug server the counting is correct.

Information_schema.processlist gets two new columns: MEMORY_USED and EXAMINED_ROWS.
- The later is there mainly to show how query is progressing.

The following changes in interfaces was needed to get this to work:
- init_alloc_root() amd init_sql_alloc() has extra option so that one can mark memory with MY_THREAD_SPECIFIC
- One now have to use alloc_root_set_min_malloc() to set min memory to be allocated by alloc_root()
- my_init_dynamic_array()  has extra option so that one can mark memory with MY_THREAD_SPECIFIC
- my_net_init() has extra option so that one can mark memory with MY_THREAD_SPECIFIC
- Added flag for hash_init() so that one can mark hash table to be thread specific.
- Added flags to init_tree() so that one can mark tree to be thread specific.
- Removed with_delete option to init_tree(). Now one should instead use MY_TREE_WITH_DELETE_FLAG.
- Added flag to Warning_info::Warning_info() if the structure should be fully initialized.
- String elements can now be marked as thread specific.
- Internal HEAP tables are now marking it's memory as MY_THREAD_SPECIFIC.
- Changed type of myf from int to ulong, as this is always a set of bit flags.

Other things:
- Removed calls to net_end() and thd->cleanup() as these are now done in ~THD()
- We now also show EXAMINED_ROWS in SHOW PROCESSLIST
- Added new variable 'memory_used'
- Fixed bug where kill_threads_for_user() was using the wrong mem_root to allocate memory.
- Removed calls to the obsoleted function init_dynamic_array()
- Use set_current_thd() instead of my_pthread_setspecific_ptr(THR_THD,...)


client/completion_hash.cc:
  Updated call to init_alloc_root()
client/mysql.cc:
  Updated call to init_alloc_root()
client/mysqlbinlog.cc:
  init_dynamic_array() -> my_init_dynamic_array()
  Updated call to init_alloc_root()
client/mysqlcheck.c:
  Updated call to my_init_dynamic_array()
client/mysqldump.c:
  Updated call to init_alloc_root()
client/mysqltest.cc:
  Updated call to init_alloc_root()
  Updated call to my_init_dynamic_array()
  Fixed compiler warnings
extra/comp_err.c:
  Updated call to my_init_dynamic_array()
extra/resolve_stack_dump.c:
  Updated call to my_init_dynamic_array()
include/hash.h:
  Added HASH_THREAD_SPECIFIC
include/heap.h:
  Added flag is internal temporary table.
include/my_dir.h:
  Safety fix: Ensure that MY_DONT_SORT and MY_WANT_STAT don't interfer with other mysys flags
include/my_global.h:
  Changed type of myf from int to ulong, as this is always a set of bit flags.
include/my_sys.h:
  Added MY_THREAD_SPECIFIC and MY_THREAD_MOVE
  Added malloc_flags to DYNAMIC_ARRAY
  Added extra mysys flag argument to my_init_dynamic_array()
  Removed deprecated functions init_dynamic_array() and my_init_dynamic_array.._ci
  Updated paramaters for init_alloc_root()
include/my_tree.h:
  Added my_flags to allow one to use MY_THREAD_SPECIFIC with hash tables.
  Removed with_delete. One should now instead use MY_TREE_WITH_DELETE_FLAG
  Updated parameters to init_tree()
include/myisamchk.h:
  Added malloc_flags to allow one to use MY_THREAD_SPECIFIC for checks.
include/mysql.h:
  Added MYSQL_THREAD_SPECIFIC_MALLOC
  Used 'unused1' to mark memory as thread specific.
include/mysql.h.pp:
  Updated file
include/mysql_com.h:
  Used 'unused1' to mark memory as thread specific.
  Updated parameters for my_net_init()
libmysql/libmysql.c:
  Updated call to init_alloc_root() to mark memory thread specific.
libmysqld/emb_qcache.cc:
  Updated call to init_alloc_root()
libmysqld/lib_sql.cc:
  Updated call to init_alloc_root()
mysql-test/r/create.result:
  Updated results
mysql-test/r/user_var.result:
  Updated results
mysql-test/suite/funcs_1/datadict/processlist_priv.inc:
  Update to handle new format of SHOW PROCESSLIST
mysql-test/suite/funcs_1/datadict/processlist_val.inc:
  Update to handle new format of SHOW PROCESSLIST
mysql-test/suite/funcs_1/r/is_columns_is.result:
  Update to handle new format of SHOW PROCESSLIST
mysql-test/suite/funcs_1/r/processlist_priv_no_prot.result:
  Updated results
mysql-test/suite/funcs_1/r/processlist_val_no_prot.result:
  Updated results
mysql-test/t/show_explain.test:
  Fixed usage of debug variable so that one can run test with --debug
mysql-test/t/user_var.test:
  Added test of memory_usage variable.
mysys/array.c:
  Added extra my_flags option to init_dynamic_array() and init_dynamic_array2() so that one can mark memory with MY_THREAD_SPECIFIC
  All allocated memory is marked with the given my_flags.
  Removed obsolete function init_dynamic_array()
mysys/default.c:
  Updated call to init_alloc_root()
  Updated call to my_init_dynamic_array()
mysys/hash.c:
  Updated call to my_init_dynamic_array_ci().
  Allocated memory is marked with MY_THREAD_SPECIFIC if HASH_THREAD_SPECIFIC is used.
mysys/ma_dyncol.c:
  init_dynamic_array() -> my_init_dynamic_array()
  Added #if to get rid of compiler warnings
mysys/mf_tempdir.c:
  Updated call to my_init_dynamic_array()
mysys/my_alloc.c:
  Added extra parameter to init_alloc_root() so that one can mark memory with MY_THREAD_SPECIFIC
  Extend MEM_ROOT with a flag if memory is thread specific.
  This is stored in block_size, to keep the size of the MEM_ROOT object identical as before.
  Allocated memory is marked with MY_THREAD_SPECIFIC if used with init_alloc_root()
mysys/my_chmod.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_chsize.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_copy.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_create.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_delete.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_error.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_fopen.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_fstream.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_getwd.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_lib.c:
  Updated call to init_alloc_root()
  Updated call to my_init_dynamic_array()
  Updated DBUG_PRINT because of change of myf type
mysys/my_lock.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_malloc.c:
  Store at start of each allocated memory block the size of the block and if the block is thread specific.
  Call malloc_size_cb_func, if set, with the memory allocated/freed.
  Updated DBUG_PRINT because of change of myf type
mysys/my_open.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_pread.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_read.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_redel.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_rename.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_seek.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_sync.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_thr_init.c:
  Ensure that one can call my_thread_dbug_id() even if thread is not properly initialized.
mysys/my_write.c:
  Updated DBUG_PRINT because of change of myf type
mysys/mysys_priv.h:
  Updated parameters to sf_malloc and sf_realloc()
mysys/safemalloc.c:
  Added checking that for memory marked with MY_THREAD_SPECIFIC that it's the same thread that is allocation and freeing the memory.
  Added sf_malloc_dbug_id() to allow MariaDB to specify which THD is handling the memory.
  Added my_flags arguments to sf_malloc() and sf_realloc() to be able to mark memory with MY_THREAD_SPECIFIC.
  Added sf_report_leaked_memory() to get list of memory not freed by a thread.
mysys/tree.c:
  Added flags to init_tree() so that one can mark tree to be thread specific.
  Removed with_delete option to init_tree(). Now one should instead use MY_TREE_WITH_DELETE_FLAG.
  Updated call to init_alloc_root()
  All allocated memory is marked with the given malloc flags
mysys/waiting_threads.c:
  Updated call to my_init_dynamic_array()
sql-common/client.c:
  Updated call to init_alloc_root() and my_net_init() to mark memory thread specific.
  Updated call to my_init_dynamic_array().
  Added MYSQL_THREAD_SPECIFIC_MALLOC so that client can mark memory as MY_THREAD_SPECIFIC.
sql-common/client_plugin.c:
  Updated call to init_alloc_root()
sql/debug_sync.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/event_scheduler.cc:
  Removed calls to net_end() as this is now done in ~THD()
  Call set_current_thd() to ensure that memory is assigned to right thread.
sql/events.cc:
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
sql/filesort.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/filesort_utils.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/ha_ndbcluster.cc:
  Updated call to init_alloc_root()
  Updated call to my_net_init()
  Removed calls to net_end() and thd->cleanup() as these are now done in ~THD()
sql/ha_ndbcluster_binlog.cc:
  Updated call to my_net_init()
  Updated call to init_sql_alloc()
  Removed calls to net_end() and thd->cleanup() as these are now done in ~THD()
sql/ha_partition.cc:
  Updated call to init_alloc_root()
sql/handler.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
  Added missing call to my_dir_end()
sql/item_func.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/item_subselect.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/item_sum.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/log.cc:
  More DBUG
  Updated call to init_alloc_root()
sql/mdl.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/mysqld.cc:
  Added total_memory_used
  Updated call to init_alloc_root()
  Move mysql_cond_broadcast() before my_thread_end()
  Added mariadb_dbug_id() to count memory per THD instead of per thread.
  Added my_malloc_size_cb_func() callback function for my_malloc() to count memory.
  Move initialization of mysqld_server_started and mysqld_server_initialized earlier.
  Updated call to my_init_dynamic_array().
  Updated call to my_net_init().
  Call my_pthread_setspecific_ptr(THR_THD,...) to ensure that memory is assigned to right thread.
  Added status variable 'memory_used'.
  Updated call to init_alloc_root()
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
sql/mysqld.h:
  Added set_current_thd()
sql/net_serv.cc:
  Added new parameter to my_net_init() so that one can mark memory with MY_THREAD_SPECIFIC.
  Store in net->thread_specific_malloc if memory is thread specific.
  Mark memory to be thread specific if requested.
sql/opt_range.cc:
  Updated call to my_init_dynamic_array()
  Updated call to init_sql_alloc()
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/opt_subselect.cc:
  Updated call to init_sql_alloc() to mark memory thread specific.
sql/protocol.cc:
  Fixed compiler warning
sql/records.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/rpl_filter.cc:
  Updated call to my_init_dynamic_array()
sql/rpl_handler.cc:
  Updated call to my_init_dynamic_array2()
sql/rpl_handler.h:
  Updated call to init_sql_alloc()
sql/rpl_mi.cc:
  Updated call to my_init_dynamic_array()
sql/rpl_tblmap.cc:
  Updated call to init_alloc_root()
sql/rpl_utility.cc:
  Updated call to my_init_dynamic_array()
sql/slave.cc:
  Initialize things properly before calling functions that allocate memory.
  Removed calls to net_end() as this is now done in ~THD()
sql/sp_head.cc:
  Updated call to init_sql_alloc()
  Updated call to my_init_dynamic_array()
  Added parameter to warning_info() that it should be fully initialized.
sql/sp_pcontext.cc:
  Updated call to my_init_dynamic_array()
sql/sql_acl.cc:
  Updated call to init_sql_alloc()
  Updated call to my_init_dynamic_array()
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
sql/sql_admin.cc:
  Added parameter to warning_info() that it should be fully initialized.
sql/sql_analyse.h:
  Updated call to init_tree() to mark memory thread specific.
sql/sql_array.h:
  Updated call to my_init_dynamic_array() to mark memory thread specific.
sql/sql_audit.cc:
  Updated call to my_init_dynamic_array()
sql/sql_base.cc:
  Updated call to init_sql_alloc()
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
sql/sql_cache.cc:
  Updated comment
sql/sql_class.cc:
  Added parameter to warning_info() that not initialize it until THD is fully created.
  Updated call to init_sql_alloc()
  Mark THD::user_vars has to be thread specific.
  Updated call to my_init_dynamic_array()
  Ensure that memory allocated by THD is assigned to the THD.
  More DBUG
  Always acll net_end() in ~THD()
  Assert that all memory signed to this THD is really deleted at ~THD.
  Fixed set_status_var_init() to not reset memory_used.
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
sql/sql_class.h:
  Added MY_THREAD_SPECIFIC to allocated memory.
  Added malloc_size to THD to record allocated memory per THD.
sql/sql_delete.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/sql_error.cc:
  Added 'initialize' parameter to Warning_info() to say if should allocate memory for it's structures.
  This is used by THD::THD() to not allocate memory until THD is ready.
  Added Warning_info::free_memory()
sql/sql_error.h:
  Updated Warning_info() class.
sql/sql_handler.cc:
  Updated call to init_alloc_root() to mark memory thread specific.
sql/sql_insert.cc:
  More DBUG
sql/sql_join_cache.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/sql_lex.cc:
  Updated call to my_init_dynamic_array()
sql/sql_lex.h:
  Updated call to my_init_dynamic_array()
sql/sql_load.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/sql_parse.cc:
  Removed calls to net_end() and thd->cleanup() as these are now done in ~THD()
  Ensure that examined_row_count() is reset before query.
  Fixed bug where kill_threads_for_user() was using the wrong mem_root to allocate memory.
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
  Don't restore thd->status_var.memory_used when restoring thd->status_var
sql/sql_plugin.cc:
  Updated call to init_alloc_root()
  Updated call to my_init_dynamic_array()
  Don't allocate THD on the stack, as this causes problems with valgrind when doing thd memory counting.
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
sql/sql_prepare.cc:
  Added parameter to warning_info() that it should be fully initialized.
  Updated call to init_sql_alloc() to mark memory thread specific.
sql/sql_reload.cc:
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
sql/sql_select.cc:
  Updated call to my_init_dynamic_array() and init_sql_alloc() to mark memory thread specific.
  Added MY_THREAD_SPECIFIC to allocated memory.
  More DBUG
sql/sql_servers.cc:
  Updated call to init_sql_alloc() to mark memory some memory thread specific.
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
sql/sql_show.cc:
  Updated call to my_init_dynamic_array()
  Mark my_dir() memory thread specific.
  Use my_pthread_setspecific_ptr(THR_THD,...) to mark that allocated memory should be allocated to calling thread.
  More DBUG.
  Added malloc_size and examined_row_count to SHOW PROCESSLIST.
  Added MY_THREAD_SPECIFIC to allocated memory.
  Updated call to init_sql_alloc()
  Added parameter to warning_info() that it should be fully initialized.
sql/sql_statistics.cc:
  Fixed compiler warning
sql/sql_string.cc:
  String elements can now be marked as thread specific.
sql/sql_string.h:
  String elements can now be marked as thread specific.
sql/sql_table.cc:
  Updated call to init_sql_alloc() and my_malloc() to mark memory thread specific
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
  Fixed compiler warning
sql/sql_test.cc:
  Updated call to my_init_dynamic_array() to mark memory thread specific.
sql/sql_trigger.cc:
  Updated call to init_sql_alloc()
sql/sql_udf.cc:
  Updated call to init_sql_alloc()
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
sql/sql_update.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/table.cc:
  Updated call to init_sql_alloc().
  Mark memory used by temporary tables, that are not for slave threads, as MY_THREAD_SPECIFIC
  Updated call to init_sql_alloc()
sql/thr_malloc.cc:
  Added my_flags argument to init_sql_alloc() to be able to mark memory as MY_THREAD_SPECIFIC.
sql/thr_malloc.h:
  Updated prototype for init_sql_alloc()
sql/tztime.cc:
  Updated call to init_sql_alloc()
  Updated call to init_alloc_root() to mark memory thread specific.
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
sql/uniques.cc:
  Updated calls to init_tree(), my_init_dynamic_array() and my_malloc() to mark memory thread specific.
sql/unireg.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
storage/csv/ha_tina.cc:
  Updated call to init_alloc_root()
storage/federated/ha_federated.cc:
  Updated call to init_alloc_root()
  Updated call to my_init_dynamic_array()
  Ensure that memory allocated by fedarated is registered for the system, not for the thread.
storage/federatedx/federatedx_io_mysql.cc:
  Updated call to my_init_dynamic_array()
storage/federatedx/ha_federatedx.cc:
  Updated call to init_alloc_root()
  Updated call to my_init_dynamic_array()
storage/heap/ha_heap.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
storage/heap/heapdef.h:
  Added parameter to hp_get_new_block() to be able to do thread specific memory tagging.
storage/heap/hp_block.c:
  Added parameter to hp_get_new_block() to be able to do thread specific memory tagging.
storage/heap/hp_create.c:
  - Internal HEAP tables are now marking it's memory as MY_THREAD_SPECIFIC.
  - Use MY_TREE_WITH_DELETE instead of removed option 'with_delete'.
storage/heap/hp_open.c:
  Internal HEAP tables are now marking it's memory as MY_THREAD_SPECIFIC.
storage/heap/hp_write.c:
  Added new parameter to hp_get_new_block()
storage/maria/ma_bitmap.c:
  Updated call to my_init_dynamic_array()
storage/maria/ma_blockrec.c:
  Updated call to my_init_dynamic_array()
storage/maria/ma_check.c:
  Updated call to init_alloc_root()
storage/maria/ma_ft_boolean_search.c:
  Updated calls to init_tree() and init_alloc_root()
storage/maria/ma_ft_nlq_search.c:
  Updated call to init_tree()
storage/maria/ma_ft_parser.c:
  Updated call to init_tree()
  Updated call to init_alloc_root()
storage/maria/ma_loghandler.c:
  Updated call to my_init_dynamic_array()
storage/maria/ma_open.c:
  Updated call to my_init_dynamic_array()
storage/maria/ma_sort.c:
  Updated call to my_init_dynamic_array()
storage/maria/ma_write.c:
  Updated calls to my_init_dynamic_array() and init_tree()
storage/maria/maria_pack.c:
  Updated call to init_tree()
storage/maria/unittest/sequence_storage.c:
  Updated call to my_init_dynamic_array()
storage/myisam/ft_boolean_search.c:
  Updated call to init_tree()
  Updated call to init_alloc_root()
storage/myisam/ft_nlq_search.c:
  Updated call to init_tree()
storage/myisam/ft_parser.c:
  Updated call to init_tree()
  Updated call to init_alloc_root()
storage/myisam/ft_stopwords.c:
  Updated call to init_tree()
storage/myisam/mi_check.c:
  Updated call to init_alloc_root()
storage/myisam/mi_write.c:
  Updated call to my_init_dynamic_array()
  Updated call to init_tree()
storage/myisam/myisamlog.c:
  Updated call to init_tree()
storage/myisam/myisampack.c:
  Updated call to init_tree()
storage/myisam/sort.c:
  Updated call to my_init_dynamic_array()
storage/myisammrg/ha_myisammrg.cc:
  Updated call to init_sql_alloc()
storage/perfschema/pfs_check.cc:
  Rest current_thd
storage/perfschema/pfs_instr.cc:
  Removed DBUG_ENTER/DBUG_VOID_RETURN as at this point my_thread_var is not allocated anymore, which can cause problems.
support-files/compiler_warnings.supp:
  Disable compiler warning from offsetof macro.
2013-01-23 16:16:14 +01:00

1101 lines
29 KiB
C

/*
Copyright (c) 2000, 2011, Oracle 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 St, Fifth Floor, Boston, MA
02110-1301 USA */
/*
Replace strings in textfile
This program replaces strings in files or from stdin to stdout.
It accepts a list of from-string/to-string pairs and replaces
each occurrence of a from-string with the corresponding to-string.
The first occurrence of a found string is matched. If there is more
than one possibility for the string to replace, longer matches
are preferred before shorter matches.
Special characters in from string:
\^ Match start of line.
\$ Match end of line.
\b Match space-character, start of line or end of line.
For end \b the next replace starts locking at the end space-character.
An \b alone or in a string matches only a space-character.
\r, \t, \v as in C.
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.
Written by Monty.
fill_buffer_retaining() is taken from gnu-grep and modified.
*/
#include <my_global.h>
#include <m_ctype.h>
#include <my_sys.h>
#include <m_string.h>
#include <errno.h>
#define PC_MALLOC 256 /* Bytes for pointers */
#define PS_MALLOC 512 /* Bytes for data */
typedef struct st_pointer_array { /* when using array-strings */
TYPELIB typelib; /* Pointer to strings */
uchar *str; /* Strings is here */
uint8 *flag; /* Flag about each var. */
uint array_allocs,max_count,length,max_length;
} POINTER_ARRAY;
#define SPACE_CHAR 256
#define START_OF_LINE 257
#define END_OF_LINE 258
#define LAST_CHAR_CODE 259
typedef struct st_replace {
my_bool found;
struct st_replace *next[256];
} REPLACE;
typedef struct st_replace_found {
my_bool found;
char *replace_string;
uint to_offset;
int from_offset;
} REPLACE_STRING;
#ifndef WORD_BIT
#define WORD_BIT (8*sizeof(uint))
#endif
/* functions defined in this file */
static int static_get_options(int *argc,char * * *argv);
static int get_replace_strings(int *argc,char * * *argv,
POINTER_ARRAY *from_array,
POINTER_ARRAY *to_array);
static int insert_pointer_name(POINTER_ARRAY *pa, char * name);
static void free_pointer_array(POINTER_ARRAY *pa);
static int convert_pipe(REPLACE *,FILE *,FILE *);
static int convert_file(REPLACE *, char *);
static REPLACE *init_replace(char * *from, char * *to,uint count,
char * word_end_chars);
static uint replace_strings(REPLACE *rep, char * *start,uint *max_length,
char * from);
static int initialize_buffer(void);
static void reset_buffer(void);
static void free_buffer(void);
static int silent=0,verbose=0,updated=0;
/* The main program */
int main(int argc, char *argv[])
{
int i,error;
char word_end_chars[256],*pos;
POINTER_ARRAY from,to;
REPLACE *replace;
MY_INIT(argv[0]);
if (static_get_options(&argc,&argv))
exit(1);
if (get_replace_strings(&argc,&argv,&from,&to))
exit(1);
for (i=1,pos=word_end_chars ; i < 256 ; i++)
if (my_isspace(&my_charset_latin1,i))
*pos++= (char) i;
*pos=0;
if (!(replace=init_replace((char**) from.typelib.type_names,
(char**) to.typelib.type_names,
(uint) from.typelib.count,word_end_chars)))
exit(1);
free_pointer_array(&from);
free_pointer_array(&to);
if (initialize_buffer())
return 1;
error=0;
if (argc == 0)
error=convert_pipe(replace,stdin,stdout);
else
{
while (argc--)
{
error=convert_file(replace,*(argv++));
}
}
free_buffer();
my_free(replace);
my_end(verbose ? MY_CHECK_ERROR | MY_GIVE_INFO : MY_CHECK_ERROR);
exit(error ? 2 : 0);
return 0; /* No compiler warning */
} /* main */
/* reads options */
/* Initiates DEBUG - but no debugging here ! */
static int static_get_options(argc,argv)
register int *argc;
register char **argv[];
{
int help,version;
char *pos;
silent=verbose=help=0;
while (--*argc > 0 && *(pos = *(++*argv)) == '-' && pos[1] != '-') {
while (*++pos)
{
version=0;
switch((*pos)) {
case 's':
silent=1;
break;
case 'v':
verbose=1;
break;
case '#':
DBUG_PUSH (++pos);
pos= (char*) " "; /* Skip rest of arguments */
break;
case 'V':
version=1;
case 'I':
case '?':
help=1; /* Help text written */
printf("%s Ver 1.4 for %s at %s\n",my_progname,SYSTEM_TYPE,
MACHINE_TYPE);
if (version)
break;
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("This program replaces strings in files or from stdin to stdout.\n"
"It accepts a list of from-string/to-string pairs and replaces\n"
"each occurrence of a from-string with the corresponding to-string.\n"
"The first occurrence of a found string is matched. If there is\n"
"more than one possibility for the string to replace, longer\n"
"matches are preferred before shorter matches.\n\n"
"A from-string can contain these special characters:\n"
" \\^ Match start of line.\n"
" \\$ Match end of line.\n"
" \\b Match space-character, start of line or end of line.\n"
" For a end \\b the next replace starts locking at the end\n"
" space-character. A \\b alone in a string matches only a\n"
" space-character.\n");
printf("Usage: %s [-?svIV] from to from to ... -- [files]\n", my_progname);
puts("or");
printf("Usage: %s [-?svIV] from to from to ... < fromfile > tofile\n", my_progname);
puts("");
puts("Options: -? or -I \"Info\" -s \"silent\" -v \"verbose\"");
break;
default:
fprintf(stderr,"illegal option: -%c\n",*pos);
break;
}
}
}
if (*argc == 0)
{
if (!help)
my_message(0,"No replace options given",MYF(ME_BELL));
exit(0); /* Don't use as pipe */
}
return(0);
} /* static_get_options */
static int get_replace_strings(argc,argv,from_array,to_array)
register int *argc;
register char **argv[];
POINTER_ARRAY *from_array,*to_array;
{
char *pos;
bzero((char*) from_array,sizeof(from_array[0]));
bzero((char*) to_array,sizeof(to_array[0]));
while (*argc > 0 && (*(pos = *(*argv)) != '-' || pos[1] != '-' || pos[2]))
{
insert_pointer_name(from_array,pos);
(*argc)--;
(*argv)++;
if (!*argc || !strcmp(**argv,"--"))
{
my_message(0,"No to-string for last from-string",MYF(ME_BELL));
return 1;
}
insert_pointer_name(to_array,**argv);
(*argc)--;
(*argv)++;
}
if (*argc)
{ /* Skip "--" argument */
(*argc)--;
(*argv)++;
}
return 0;
}
static int insert_pointer_name(reg1 POINTER_ARRAY *pa,char * name)
{
uint i,length,old_count;
uchar *new_pos;
const char **new_array;
DBUG_ENTER("insert_pointer_name");
if (! pa->typelib.count)
{
if (!(pa->typelib.type_names=(const char **)
my_malloc(((PC_MALLOC-MALLOC_OVERHEAD)/
(sizeof(char *)+sizeof(*pa->flag))*
(sizeof(char *)+sizeof(*pa->flag))),MYF(MY_WME))))
DBUG_RETURN(-1);
if (!(pa->str= (uchar*) my_malloc((uint) (PS_MALLOC-MALLOC_OVERHEAD),
MYF(MY_WME))))
{
my_free((char*) pa->typelib.type_names);
DBUG_RETURN (-1);
}
pa->max_count=(PC_MALLOC-MALLOC_OVERHEAD)/(sizeof(uchar*)+
sizeof(*pa->flag));
pa->flag= (uint8*) (pa->typelib.type_names+pa->max_count);
pa->length=0;
pa->max_length=PS_MALLOC-MALLOC_OVERHEAD;
pa->array_allocs=1;
}
length=(uint) strlen(name)+1;
if (pa->length+length >= pa->max_length)
{
pa->max_length=(pa->length+length+MALLOC_OVERHEAD+PS_MALLOC-1)/PS_MALLOC;
pa->max_length=pa->max_length*PS_MALLOC-MALLOC_OVERHEAD;
if (!(new_pos= (uchar*) my_realloc((uchar*) pa->str,
(uint) pa->max_length,
MYF(MY_WME))))
DBUG_RETURN(1);
if (new_pos != pa->str)
{
my_ptrdiff_t diff=PTR_BYTE_DIFF(new_pos,pa->str);
for (i=0 ; i < pa->typelib.count ; i++)
pa->typelib.type_names[i]= ADD_TO_PTR(pa->typelib.type_names[i],diff,
char*);
pa->str=new_pos;
}
}
if (pa->typelib.count >= pa->max_count-1)
{
int len;
pa->array_allocs++;
len=(PC_MALLOC*pa->array_allocs - MALLOC_OVERHEAD);
if (!(new_array=(const char **) my_realloc((uchar*) pa->typelib.type_names,
(uint) len/
(sizeof(uchar*)+sizeof(*pa->flag))*
(sizeof(uchar*)+sizeof(*pa->flag)),
MYF(MY_WME))))
DBUG_RETURN(1);
pa->typelib.type_names=new_array;
old_count=pa->max_count;
pa->max_count=len/(sizeof(uchar*) + sizeof(*pa->flag));
pa->flag= (uint8*) (pa->typelib.type_names+pa->max_count);
memcpy((uchar*) pa->flag,(char *) (pa->typelib.type_names+old_count),
old_count*sizeof(*pa->flag));
}
pa->flag[pa->typelib.count]=0; /* Reset flag */
pa->typelib.type_names[pa->typelib.count++]= (char*) (pa->str+pa->length);
pa->typelib.type_names[pa->typelib.count]= NullS; /* Put end-mark */
(void) strmov((char*) pa->str + pa->length, name);
pa->length+=length;
DBUG_RETURN(0);
} /* insert_pointer_name */
/* free pointer array */
static void free_pointer_array(reg1 POINTER_ARRAY *pa)
{
if (pa->typelib.count)
{
pa->typelib.count=0;
my_free((char*) pa->typelib.type_names);
pa->typelib.type_names=0;
my_free(pa->str);
}
return;
} /* free_pointer_array */
/* Code for replace rutines */
#define SET_MALLOC_HUNC 64
typedef struct st_rep_set {
uint *bits; /* Pointer to used sets */
short next[LAST_CHAR_CODE]; /* Pointer to next sets */
uint found_len; /* Best match to date */
int found_offset;
uint table_offset;
uint size_of_bits; /* For convinience */
} REP_SET;
typedef struct st_rep_sets {
uint count; /* Number of sets */
uint extra; /* Extra sets in buffer */
uint invisible; /* Sets not chown */
uint size_of_bits;
REP_SET *set,*set_buffer;
uint *bit_buffer;
} REP_SETS;
typedef struct st_found_set {
uint table_offset;
int found_offset;
} FOUND_SET;
typedef struct st_follow {
int chr;
uint table_offset;
uint len;
} FOLLOWS;
static int init_sets(REP_SETS *sets,uint states);
static REP_SET *make_new_set(REP_SETS *sets);
static void make_sets_invisible(REP_SETS *sets);
static void free_last_set(REP_SETS *sets);
static void free_sets(REP_SETS *sets);
static void internal_set_bit(REP_SET *set, uint bit);
static void internal_clear_bit(REP_SET *set, uint bit);
static void or_bits(REP_SET *to,REP_SET *from);
static void copy_bits(REP_SET *to,REP_SET *from);
static int cmp_bits(REP_SET *set1,REP_SET *set2);
static int get_next_bit(REP_SET *set,uint lastpos);
static short find_set(REP_SETS *sets,REP_SET *find);
static short find_found(FOUND_SET *found_set,uint table_offset,
int found_offset);
static uint start_at_word(char * pos);
static uint end_of_word(char * pos);
static uint replace_len(char * pos);
static uint found_sets=0;
/* Init a replace structure for further calls */
static REPLACE *init_replace(char * *from, char * *to,uint count,
char * word_end_chars)
{
uint i,j,states,set_nr,len,result_len,max_length,found_end,bits_set,bit_nr;
int used_sets,chr;
short default_state;
char used_chars[LAST_CHAR_CODE],is_word_end[256];
char * pos, *to_pos, **to_array;
REP_SETS sets;
REP_SET *set,*start_states,*word_states,*new_set;
FOLLOWS *follow,*follow_ptr;
REPLACE *replace;
FOUND_SET *found_set;
REPLACE_STRING *rep_str;
DBUG_ENTER("init_replace");
/* Count number of states */
for (i=result_len=max_length=0 , states=2 ; i < count ; i++)
{
len=replace_len(from[i]);
if (!len)
{
errno=EINVAL;
my_message(0,"No to-string for last from-string",MYF(ME_BELL));
DBUG_RETURN(0);
}
states+=len+1;
result_len+=(uint) strlen(to[i])+1;
if (len > max_length)
max_length=len;
}
bzero((char*) is_word_end,sizeof(is_word_end));
for (i=0 ; word_end_chars[i] ; i++)
is_word_end[(uchar) word_end_chars[i]]=1;
if (init_sets(&sets,states))
DBUG_RETURN(0);
found_sets=0;
if (!(found_set= (FOUND_SET*) my_malloc(sizeof(FOUND_SET)*max_length*count,
MYF(MY_WME))))
{
free_sets(&sets);
DBUG_RETURN(0);
}
(void) make_new_set(&sets); /* Set starting set */
make_sets_invisible(&sets); /* Hide previus sets */
used_sets=-1;
word_states=make_new_set(&sets); /* Start of new word */
start_states=make_new_set(&sets); /* This is first state */
if (!(follow=(FOLLOWS*) my_malloc((states+2)*sizeof(FOLLOWS),MYF(MY_WME))))
{
free_sets(&sets);
my_free(found_set);
DBUG_RETURN(0);
}
/* Init follow_ptr[] */
for (i=0, states=1, follow_ptr=follow+1 ; i < count ; i++)
{
if (from[i][0] == '\\' && from[i][1] == '^')
{
internal_set_bit(start_states,states+1);
if (!from[i][2])
{
start_states->table_offset=i;
start_states->found_offset=1;
}
}
else if (from[i][0] == '\\' && from[i][1] == '$')
{
internal_set_bit(start_states,states);
internal_set_bit(word_states,states);
if (!from[i][2] && start_states->table_offset == (uint) ~0)
{
start_states->table_offset=i;
start_states->found_offset=0;
}
}
else
{
internal_set_bit(word_states,states);
if (from[i][0] == '\\' && (from[i][1] == 'b' && from[i][2]))
internal_set_bit(start_states,states+1);
else
internal_set_bit(start_states,states);
}
for (pos=from[i], len=0; *pos ; pos++)
{
if (*pos == '\\' && *(pos+1))
{
pos++;
switch (*pos) {
case 'b':
follow_ptr->chr = SPACE_CHAR;
break;
case '^':
follow_ptr->chr = START_OF_LINE;
break;
case '$':
follow_ptr->chr = END_OF_LINE;
break;
case 'r':
follow_ptr->chr = '\r';
break;
case 't':
follow_ptr->chr = '\t';
break;
case 'v':
follow_ptr->chr = '\v';
break;
default:
follow_ptr->chr = (uchar) *pos;
break;
}
}
else
follow_ptr->chr= (uchar) *pos;
follow_ptr->table_offset=i;
follow_ptr->len= ++len;
follow_ptr++;
}
follow_ptr->chr=0;
follow_ptr->table_offset=i;
follow_ptr->len=len;
follow_ptr++;
states+=(uint) len+1;
}
for (set_nr=0,pos=0 ; set_nr < sets.count ; set_nr++)
{
set=sets.set+set_nr;
default_state= 0; /* Start from beginning */
/* If end of found-string not found or start-set with current set */
for (i= (uint) ~0; (i=get_next_bit(set,i)) ;)
{
if (!follow[i].chr)
{
if (! default_state)
default_state= find_found(found_set,set->table_offset,
set->found_offset+1);
}
}
copy_bits(sets.set+used_sets,set); /* Save set for changes */
if (!default_state)
or_bits(sets.set+used_sets,sets.set); /* Can restart from start */
/* Find all chars that follows current sets */
bzero((char*) used_chars,sizeof(used_chars));
for (i= (uint) ~0; (i=get_next_bit(sets.set+used_sets,i)) ;)
{
used_chars[follow[i].chr]=1;
if ((follow[i].chr == SPACE_CHAR && !follow[i+1].chr &&
follow[i].len > 1) || follow[i].chr == END_OF_LINE)
used_chars[0]=1;
}
/* Mark word_chars used if \b is in state */
if (used_chars[SPACE_CHAR])
for (pos= word_end_chars ; *pos ; pos++)
used_chars[(int) (uchar) *pos] = 1;
/* Handle other used characters */
for (chr= 0 ; chr < 256 ; chr++)
{
if (! used_chars[chr])
set->next[chr]= (short) (chr ? default_state : -1);
else
{
new_set=make_new_set(&sets);
set=sets.set+set_nr; /* if realloc */
new_set->table_offset=set->table_offset;
new_set->found_len=set->found_len;
new_set->found_offset=set->found_offset+1;
found_end=0;
for (i= (uint) ~0 ; (i=get_next_bit(sets.set+used_sets,i)) ; )
{
if (!follow[i].chr || follow[i].chr == chr ||
(follow[i].chr == SPACE_CHAR &&
(is_word_end[chr] ||
(!chr && follow[i].len > 1 && ! follow[i+1].chr))) ||
(follow[i].chr == END_OF_LINE && ! chr))
{
if ((! chr || (follow[i].chr && !follow[i+1].chr)) &&
follow[i].len > found_end)
found_end=follow[i].len;
if (chr && follow[i].chr)
internal_set_bit(new_set,i+1); /* To next set */
else
internal_set_bit(new_set,i);
}
}
if (found_end)
{
new_set->found_len=0; /* Set for testing if first */
bits_set=0;
for (i= (uint) ~0; (i=get_next_bit(new_set,i)) ;)
{
if ((follow[i].chr == SPACE_CHAR ||
follow[i].chr == END_OF_LINE) && ! chr)
bit_nr=i+1;
else
bit_nr=i;
if (follow[bit_nr-1].len < found_end ||
(new_set->found_len &&
(chr == 0 || !follow[bit_nr].chr)))
internal_clear_bit(new_set,i);
else
{
if (chr == 0 || !follow[bit_nr].chr)
{ /* best match */
new_set->table_offset=follow[bit_nr].table_offset;
if (chr || (follow[i].chr == SPACE_CHAR ||
follow[i].chr == END_OF_LINE))
new_set->found_offset=found_end; /* New match */
new_set->found_len=found_end;
}
bits_set++;
}
}
if (bits_set == 1)
{
set->next[chr] = find_found(found_set,
new_set->table_offset,
new_set->found_offset);
free_last_set(&sets);
}
else
set->next[chr] = find_set(&sets,new_set);
}
else
set->next[chr] = find_set(&sets,new_set);
}
}
}
/* Alloc replace structure for the replace-state-machine */
if ((replace=(REPLACE*) my_malloc(sizeof(REPLACE)*(sets.count)+
sizeof(REPLACE_STRING)*(found_sets+1)+
sizeof(char *)*count+result_len,
MYF(MY_WME | MY_ZEROFILL))))
{
rep_str=(REPLACE_STRING*) (replace+sets.count);
to_array=(char **) (rep_str+found_sets+1);
to_pos=(char *) (to_array+count);
for (i=0 ; i < count ; i++)
{
to_array[i]=to_pos;
to_pos=strmov(to_pos,to[i])+1;
}
rep_str[0].found=1;
rep_str[0].replace_string=0;
for (i=1 ; i <= found_sets ; i++)
{
pos=from[found_set[i-1].table_offset];
rep_str[i].found= (my_bool) (!memcmp(pos,"\\^",3) ? 2 : 1);
rep_str[i].replace_string=to_array[found_set[i-1].table_offset];
rep_str[i].to_offset=found_set[i-1].found_offset-start_at_word(pos);
rep_str[i].from_offset=found_set[i-1].found_offset-replace_len(pos)+
end_of_word(pos);
}
for (i=0 ; i < sets.count ; i++)
{
for (j=0 ; j < 256 ; j++)
if (sets.set[i].next[j] >= 0)
replace[i].next[j]=replace+sets.set[i].next[j];
else
replace[i].next[j]=(REPLACE*) (rep_str+(-sets.set[i].next[j]-1));
}
}
my_free(follow);
free_sets(&sets);
my_free(found_set);
DBUG_PRINT("exit",("Replace table has %d states",sets.count));
DBUG_RETURN(replace);
}
static int init_sets(REP_SETS *sets,uint states)
{
bzero((char*) sets,sizeof(*sets));
sets->size_of_bits=((states+7)/8);
if (!(sets->set_buffer=(REP_SET*) my_malloc(sizeof(REP_SET)*SET_MALLOC_HUNC,
MYF(MY_WME))))
return 1;
if (!(sets->bit_buffer=(uint*) my_malloc(sizeof(uint)*sets->size_of_bits*
SET_MALLOC_HUNC,MYF(MY_WME))))
{
my_free(sets->set);
return 1;
}
return 0;
}
/* Make help sets invisible for nicer codeing */
static void make_sets_invisible(REP_SETS *sets)
{
sets->invisible=sets->count;
sets->set+=sets->count;
sets->count=0;
}
static REP_SET *make_new_set(REP_SETS *sets)
{
uint i,count,*bit_buffer;
REP_SET *set;
if (sets->extra)
{
sets->extra--;
set=sets->set+ sets->count++;
bzero((char*) set->bits,sizeof(uint)*sets->size_of_bits);
bzero((char*) &set->next[0],sizeof(set->next[0])*LAST_CHAR_CODE);
set->found_offset=0;
set->found_len=0;
set->table_offset= (uint) ~0;
set->size_of_bits=sets->size_of_bits;
return set;
}
count=sets->count+sets->invisible+SET_MALLOC_HUNC;
if (!(set=(REP_SET*) my_realloc((uchar*) sets->set_buffer,
sizeof(REP_SET)*count,
MYF(MY_WME))))
return 0;
sets->set_buffer=set;
sets->set=set+sets->invisible;
if (!(bit_buffer=(uint*) my_realloc((uchar*) sets->bit_buffer,
(sizeof(uint)*sets->size_of_bits)*count,
MYF(MY_WME))))
return 0;
sets->bit_buffer=bit_buffer;
for (i=0 ; i < count ; i++)
{
sets->set_buffer[i].bits=bit_buffer;
bit_buffer+=sets->size_of_bits;
}
sets->extra=SET_MALLOC_HUNC;
return make_new_set(sets);
}
static void free_last_set(REP_SETS *sets)
{
sets->count--;
sets->extra++;
return;
}
static void free_sets(REP_SETS *sets)
{
my_free(sets->set_buffer);
my_free(sets->bit_buffer);
return;
}
static void internal_set_bit(REP_SET *set, uint bit)
{
set->bits[bit / WORD_BIT] |= 1 << (bit % WORD_BIT);
return;
}
static void internal_clear_bit(REP_SET *set, uint bit)
{
set->bits[bit / WORD_BIT] &= ~ (1 << (bit % WORD_BIT));
return;
}
static void or_bits(REP_SET *to,REP_SET *from)
{
reg1 uint i;
for (i=0 ; i < to->size_of_bits ; i++)
to->bits[i]|=from->bits[i];
return;
}
static void copy_bits(REP_SET *to,REP_SET *from)
{
memcpy((uchar*) to->bits,(uchar*) from->bits,
(size_t) (sizeof(uint) * to->size_of_bits));
}
static int cmp_bits(REP_SET *set1,REP_SET *set2)
{
return memcmp(set1->bits, set2->bits,
sizeof(uint) * set1->size_of_bits);
}
/* Get next set bit from set. */
static int get_next_bit(REP_SET *set,uint lastpos)
{
uint pos,*start,*end,bits;
start=set->bits+ ((lastpos+1) / WORD_BIT);
end=set->bits + set->size_of_bits;
bits=start[0] & ~((1 << ((lastpos+1) % WORD_BIT)) -1);
while (! bits && ++start < end)
bits=start[0];
if (!bits)
return 0;
pos=(uint) (start-set->bits)*WORD_BIT;
while (! (bits & 1))
{
bits>>=1;
pos++;
}
return pos;
}
/* find if there is a same set in sets. If there is, use it and
free given set, else put in given set in sets and return it's
position */
static short find_set(REP_SETS *sets,REP_SET *find)
{
uint i;
for (i=0 ; i < sets->count-1 ; i++)
{
if (!cmp_bits(sets->set+i,find))
{
free_last_set(sets);
return (short) i;
}
}
return (short) i; /* return new position */
}
/*
find if there is a found_set with same table_offset & found_offset
If there is return offset to it, else add new offset and return pos.
Pos returned is -offset-2 in found_set_structure because it's is
saved in set->next and set->next[] >= 0 points to next set and
set->next[] == -1 is reserved for end without replaces.
*/
static short find_found(FOUND_SET *found_set,uint table_offset,
int found_offset)
{
int i;
for (i=0 ; (uint) i < found_sets ; i++)
if (found_set[i].table_offset == table_offset &&
found_set[i].found_offset == found_offset)
return (short) (-i-2);
found_set[i].table_offset=table_offset;
found_set[i].found_offset=found_offset;
found_sets++;
return (short) (-i-2); /* return new position */
}
/* Return 1 if regexp starts with \b or ends with \b*/
static uint start_at_word(char * pos)
{
return (((!memcmp(pos,"\\b",2) && pos[2]) || !memcmp(pos,"\\^",2)) ? 1 : 0);
}
static uint end_of_word(char * pos)
{
char * end=strend(pos);
return ((end > pos+2 && !memcmp(end-2,"\\b",2)) ||
(end >= pos+2 && !memcmp(end-2,"\\$",2))) ?
1 : 0;
}
static uint replace_len(char * str)
{
uint len=0;
while (*str)
{
if (str[0] == '\\' && str[1])
str++;
str++;
len++;
}
return len;
}
/* The actual loop */
static uint replace_strings(REPLACE *rep, char **start, uint *max_length,
char *from)
{
reg1 REPLACE *rep_pos;
reg2 REPLACE_STRING *rep_str;
char *to, *end, *pos, *new;
end=(to= *start) + *max_length-1;
rep_pos=rep+1;
for(;;)
{
while (!rep_pos->found)
{
rep_pos= rep_pos->next[(uchar) *from];
if (to == end)
{
(*max_length)+=8192;
if (!(new=my_realloc(*start,*max_length,MYF(MY_WME))))
return (uint) -1;
to=new+(to - *start);
end=(*start=new)+ *max_length-1;
}
*to++= *from++;
}
if (!(rep_str = ((REPLACE_STRING*) rep_pos))->replace_string)
return (uint) (to - *start)-1;
updated=1; /* Some char * is replaced */
to-=rep_str->to_offset;
for (pos=rep_str->replace_string; *pos ; pos++)
{
if (to == end)
{
(*max_length)*=2;
if (!(new=my_realloc(*start,*max_length,MYF(MY_WME))))
return (uint) -1;
to=new+(to - *start);
end=(*start=new)+ *max_length-1;
}
*to++= *pos;
}
if (!*(from-=rep_str->from_offset) && rep_pos->found != 2)
return (uint) (to - *start);
rep_pos=rep;
}
}
static char *buffer; /* The buffer itself, grown as needed. */
static int bufbytes; /* Number of bytes in the buffer. */
static int bufread,my_eof; /* Number of bytes to get with each read(). */
static uint bufalloc;
static char *out_buff;
static uint out_length;
static int initialize_buffer()
{
bufread = 8192;
bufalloc = bufread + bufread / 2;
if (!(buffer = my_malloc(bufalloc+1,MYF(MY_WME))))
return 1;
bufbytes=my_eof=0;
out_length=bufread;
if (!(out_buff=my_malloc(out_length,MYF(MY_WME))))
return(1);
return 0;
}
static void reset_buffer()
{
bufbytes=my_eof=0;
}
static void free_buffer()
{
my_free(buffer);
my_free(out_buff);
}
/*
Fill the buffer retaining the last n bytes at the beginning of the
newly filled buffer (for backward context). Returns the number of new
bytes read from disk.
*/
static int fill_buffer_retaining(fd,n)
File fd;
int n;
{
int i;
/* See if we need to grow the buffer. */
if ((int) bufalloc - n <= bufread)
{
while ((int) bufalloc - n <= bufread)
{
bufalloc *= 2;
bufread *= 2;
}
buffer = my_realloc(buffer, bufalloc+1, MYF(MY_WME));
if (! buffer)
return(-1);
}
/* Shift stuff down. */
bmove(buffer,buffer+bufbytes-n,(uint) n);
bufbytes = n;
if (my_eof)
return 0;
/* Read in new stuff. */
if ((i=(int) my_read(fd, (uchar*) buffer + bufbytes,
(size_t) bufread, MYF(MY_WME))) < 0)
return -1;
/* Kludge to pretend every nonempty file ends with a newline. */
if (i == 0 && bufbytes > 0 && buffer[bufbytes - 1] != '\n')
{
my_eof = i = 1;
buffer[bufbytes] = '\n';
}
bufbytes += i;
return i;
}
/* Return 0 if convert is ok */
/* Global variable update is set if something was changed */
static int convert_pipe(rep,in,out)
REPLACE *rep;
FILE *in,*out;
{
int retain,error;
uint length;
char save_char,*end_of_line,*start_of_line;
DBUG_ENTER("convert_pipe");
updated=retain=0;
reset_buffer();
while ((error=fill_buffer_retaining(fileno(in),retain)) > 0)
{
end_of_line=buffer ;
buffer[bufbytes]=0; /* Sentinel */
for (;;)
{
start_of_line=end_of_line;
while (end_of_line[0] != '\n' && end_of_line[0])
end_of_line++;
if (end_of_line == buffer+bufbytes)
{
retain= (int) (end_of_line - start_of_line);
break; /* No end of line, read more */
}
save_char=end_of_line[0];
end_of_line[0]=0;
end_of_line++;
if ((length=replace_strings(rep,&out_buff,&out_length,start_of_line)) ==
(uint) -1)
return 1;
if (!my_eof)
out_buff[length++]=save_char; /* Don't write added newline */
if (my_fwrite(out, (uchar*) out_buff, length, MYF(MY_WME | MY_NABP)))
DBUG_RETURN(1);
}
}
DBUG_RETURN(error);
}
static int convert_file(REPLACE *rep, char * name)
{
int error;
FILE *in,*out;
char dir_buff[FN_REFLEN], tempname[FN_REFLEN], *org_name = name;
#ifdef HAVE_READLINK
char link_name[FN_REFLEN];
#endif
File temp_file;
size_t dir_buff_length;
DBUG_ENTER("convert_file");
/* check if name is a symlink */
#ifdef HAVE_READLINK
org_name= (!my_disable_symlinks &&
!my_readlink(link_name, name, MYF(0))) ? link_name : name;
#endif
if (!(in= my_fopen(org_name,O_RDONLY,MYF(MY_WME))))
DBUG_RETURN(1);
dirname_part(dir_buff, org_name, &dir_buff_length);
if ((temp_file= create_temp_file(tempname, dir_buff, "PR", O_WRONLY,
MYF(MY_WME))) < 0)
{
my_fclose(in,MYF(0));
DBUG_RETURN(1);
}
if (!(out= my_fdopen(temp_file, tempname, O_WRONLY, MYF(MY_WME))))
{
my_fclose(in,MYF(0));
DBUG_RETURN(1);
}
error=convert_pipe(rep,in,out);
my_fclose(in,MYF(0)); my_fclose(out,MYF(0));
if (updated && ! error)
my_redel(org_name, tempname, 0, MYF(MY_WME | MY_LINK_WARNING));
else
my_delete(tempname,MYF(MY_WME));
if (!silent && ! error)
{
if (updated)
printf("%s converted\n",name);
else if (verbose)
printf("%s left unchanged\n",name);
}
DBUG_RETURN(error);
}