sled

Једноставни уређивач текста
git clone https://git.sr.ht/~strahinja/sled
Дневник | Датотеке | Референце | ПРОЧИТАЈМЕ | ЛИЦЕНЦА

чување 4884068e586b9ebd5825af5ed3d13d0f47cd6b92
родитељ fd02c742e936c73af8ef729d32d01201b999245c
Аутор: Страхиња Радић <contact@strahinja.org>
Датум:   Fri,  7 Jun 2024 22:24:35 +0200

merge refs/heads/dynsize into refs/heads/master

Diffstat:
MMakefile | 6+++---
MTODO | 7+++++++
Mconfig.mk | 6+++---
Mdraw.c | 30+++++++++++++++++++++---------
Mdraw.h | 6+++---
Alimit.c | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alimit.h | 21+++++++++++++++++++++
Msled.c | 586++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Atests/003-replace-text-100000-lines.test | 34++++++++++++++++++++++++++++++++++
Atests/replace-ext.expect | 21+++++++++++++++++++++
Mutil.c | 15+++++++--------
Mutil.h | 57++++++++++++++++++++++++++++++++++++++++++++++++++++-----
измењених датотека: 12, додавања: 621(+), брисања: 228(-)

diff --git a/Makefile b/Makefile @@ -29,15 +29,15 @@ makedate: makeversion: sh lib/makeversion -l -sled.o: sled.c config.h draw.h termbox.h util.h version.h +sled.o: sled.c config.h draw.h limit.h termbox.h util.h version.h $(MANPAGE): $(MANPAGE).in date version version.h: version.h.in date version -draw.o: draw.c config.h draw.h termbox.h util.h version.h +draw.o: draw.c config.h draw.h limit.h termbox.h util.h version.h -util.o: util.c termbox.h util.h +util.o: util.c limit.h termbox.h util.h $(PROG): $(OBJS) $(SRC) $(HEADERS) $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) diff --git a/TODO b/TODO @@ -1,6 +1,13 @@ TODO ==== +[ ] *str*cpy -> memccpy? + +[ ] Bugfix: cursor after paste when on last line + +< > Make allocation errors prompt to save the file before exiting? + - Would this make sense? + [~] Code optimization (struct coord...) diff --git a/config.mk b/config.mk @@ -7,9 +7,9 @@ CPPFLAGS = -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L \ # -D_XOPEN_SOURCE=700 -D_BSD_SOURCE INSTALL = install LIBS = -SRC = draw.c sled.c strlcat.c strlcpy.c util.c -HEADERS = config.h draw.h termbox.h util.h version.h -OBJS = draw.o sled.o strlcat.o strlcpy.o util.o +SRC = draw.c limit.c sled.c strlcat.c strlcpy.c util.c +HEADERS = config.h draw.h limit.h termbox.h util.h version.h +OBJS = draw.o limit.o sled.o strlcat.o strlcpy.o util.o PREFIX = /usr/local PROG = sled diff --git a/draw.c b/draw.c @@ -8,6 +8,7 @@ #include <string.h> #include "version.h" +#include "limit.h" #include "util.h" #include "draw.h" #include "config.h" @@ -77,6 +78,7 @@ init_bufferline(struct BufferLine* bl) return; bl->text = NULL; bl->length = 0; + bl->size = 0; bl->fg = 0; bl->bg = 0; } @@ -136,11 +138,12 @@ init_drawstate(struct DrawState* state, char* error, char* action, } void -free_bufferline(struct BufferLine* bl) +free_bufferline(struct BufferLine* bl, const int free_text) { if (!bl) return; - free(bl->text); + if (free_text) + free(bl->text); bl->text = NULL; bl->length = 0; bl->fg = 0; @@ -152,14 +155,14 @@ free_drawstate(struct DrawState* state, int skip_paste_free) { size_t row = 0; for (row = 0; row < state->buffer_size; row++) - free_bufferline(state->buffer + row); + free_bufferline(state->buffer + row, 1); free(state->backup_filename); free(state->real_filename); free(state->buffer); if (!skip_paste_free) { for (row = 0; row < state->paste_size; row++) - free_bufferline(state->paste + row); + free_bufferline(state->paste + row, 1); free(state->paste); } free(state->input); @@ -730,7 +733,7 @@ draw_screen(const struct DrawState* state) const struct DrawState* draw_status(const struct DrawState* state) { - char buf[MAXBUFLINE]; + char* buf = NULL; int seg_size; int current_start = 0; const struct StatusSegment* pseg; @@ -769,13 +772,18 @@ draw_status(const struct DrawState* state) { int last = pseg - status_segments == LEN(status_segments) - 1; + + buf = calloc(limits[L_LINE_MAX], 1); + if (!buf) + return NULL; + buf[0] = 0; if (pseg->callback) - pseg->callback(buf, MAXBUFLINE, pseg->format, - seg_size, state); + pseg->callback(buf, limits[L_LINE_MAX], + pseg->format, seg_size, state); else - strncpy(buf, pseg->format, MAXBUFLINE - 1); - buf[MAXBUFLINE - 1] = 0; + strncpy(buf, pseg->format, limits[L_LINE_MAX]); + buf[limits[L_LINE_MAX] - 1] = 0; if (pseg == status_segments + LEN(status_segments) - 1) seg_size = state->maxx - 1 - current_start; if (!draw_string(current_start, state->maxy - 1, @@ -784,7 +792,11 @@ draw_status(const struct DrawState* state) last ? seg_size + 1 : seg_size, 1, 1, pseg->alignment, 0, 1, NULL, 0, 0, 0, 0, 0, 0)) + { + free(buf); return NULL; + } + free(buf); pseg++; current_start += seg_size; } diff --git a/draw.h b/draw.h @@ -21,7 +21,6 @@ #define INPUT_DLG_WIDTH 40 #define INPUT_DLG_HEIGHT 4 #define TAB_SIZE 8 -#define MAXINPUT (512 + 1) #define M_NONE 0 #define M_COMMAND 1 @@ -48,7 +47,8 @@ struct Coord { struct BufferLine { uint32_t* text; - size_t length; + size_t length; /* both in uint32_t units */ + size_t size; uint16_t fg; uint16_t bg; }; @@ -113,7 +113,7 @@ void init_bufferline(struct BufferLine* bl); void init_coord(struct Coord* c, const int col, const int row); void init_drawstate(struct DrawState* state, char* error, char* action, char* filename, char* prompt, int skip_paste_and_mode_init); -void free_bufferline(struct BufferLine* bl); +void free_bufferline(struct BufferLine* bl, const int free_text); void free_drawstate(struct DrawState* state, int skip_paste_free); void assign_coord(struct Coord* dest, const struct Coord src); size_t display_length(const struct DrawState* state, uint32_t* buffer, diff --git a/limit.c b/limit.c @@ -0,0 +1,60 @@ +/* This program is licensed under the terms of GNU GPL v3 or (at your option) + * any later version. Copyright (C) 2022-2024 Страхиња Радић. + * See the file LICENSE for exact copyright and license details. */ + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdlib.h> +#include <unistd.h> + +#include "limit.h" + +long limits[L_LIMIT_COUNT]; + +#ifndef LINE_MAX +#define LINE_MAX _POSIX_LINK_MAX +#endif + +#ifndef PATH_MAX +#define PATH_MAX _POSIX_PATH_MAX +#endif + +int +init_limits(const char* dir) +{ + int cwdfd; + int result = 0; + + errno = 0; + + if ((cwdfd = open(dir, O_RDONLY)) < 0) + return -1; + + limits[L_PATH_MAX] = fpathconf(cwdfd, _PC_PATH_MAX); + if (limits[L_PATH_MAX] == -1) + { + if (errno) + { + result = -1; + goto init_limits_cleanup; + } + limits[L_PATH_MAX] = PATH_MAX; + } + + limits[L_LINE_MAX] = sysconf(_SC_LINE_MAX); + if (limits[L_LINE_MAX] == -1) + { + if (errno) + { + result = -1; + goto init_limits_cleanup; + } + limits[L_LINE_MAX] = LINE_MAX; + } + +init_limits_cleanup: + close(cwdfd); + + return result; +} diff --git a/limit.h b/limit.h @@ -0,0 +1,21 @@ +/* This program is licensed under the terms of GNU GPL v3 or (at your option) + * any later version. Copyright (C) 2022-2024 Страхиња Радић. + * See the file LICENSE for exact copyright and license details. */ + +/* Limits */ +/* clang-format off */ +enum { + L_PATH_MAX = 0, + L_LINE_MAX = 1, + L_LIMIT_COUNT = 2 +}; +/* clang-format on */ + +extern long limits[]; + +#define ALLOC_DELTA 50 +#define LINE_ALLOC_DELTA 85 +#define LINE_DEFAULT 85 +#define MAXINPUT (512 + 1) + +int init_limits(const char* dir); diff --git a/sled.c b/sled.c @@ -31,6 +31,7 @@ #define ONLY_DEFS #include "config.h" #include "version.h" +#include "limit.h" #include "util.h" #include "draw.h" #undef ONLY_DEFS @@ -72,7 +73,7 @@ extern const char* program_name; int clear_selection(struct DrawState* state); int cp(const char* from, const char* to); int create_file(struct DrawState* dstate); -char* dirname(char* path); +char* dirname(char* dir, char* path, const size_t path_size); int do_search_replace(struct DrawState* state); int erase_selection(struct DrawState* state); void get_selection(int* ssr, int* ssc, int* ser, int* sec, @@ -181,12 +182,13 @@ create_file(struct DrawState* state) for (size_t row = 0; row < ALLOC_DELTA; row++) { init_bufferline(newchunk + row); - newtext = calloc(MAXBUFLINE, sizeof(uint32_t)); + newtext = calloc(LINE_DEFAULT, sizeof(uint32_t)); if (!newtext) { state->running = 0; return LOAD_ERR_CANT_ALLOC; } + newchunk[row].size = LINE_DEFAULT; newchunk[row].text = newtext; } state->buffer = newchunk; @@ -199,59 +201,77 @@ create_file(struct DrawState* state) } char* -dirname(char* path) +dirname(char* dir, char* path, const size_t dir_size) { - static char* result = NULL; - static const char* dot = "."; - char* p = NULL; - char* slash = NULL; + char* p = NULL; - if (!path || !*path) + if (!dir || !path || !*path) return NULL; - free(result); - result = malloc(MAXPATH); - memset(result, 0, MAXPATH); + MEMCCPY(path, dir, dir_size, p); - STRLCPY(path, result, MAXPATH); - p = result + strlen(result) - 1; - if (*p == '/') + if (p > dir + 2 && *(p - 2) == '/') *p = 0; - slash = strrchr(result, '/'); - if (!slash) - return (char*)dot; - - *slash = 0; - - return result; + while (p != dir && *p != '/') + { + p--; + if (*p == '/') + { + *p = 0; + return dir; + } + } + dir[0] = '.'; + dir[1] = 0; + return dir; } int do_search_replace(struct DrawState* state) { - uint32_t buf[MAXBUFLINE]; + uint32_t* buf = NULL; + uint32_t* temp = NULL; + /* minimum size is LINE_DEFAULT to avoid 0-allocation */ + size_t bufsize = LINE_DEFAULT; /* size in uint32_t units */ + size_t buflen = 0; size_t search_len = u32_strlen(state->search); int start_row = 0; int end_row = state->rows_count; + uint32_t* before = NULL; + uint32_t* found = NULL; + uint32_t* after = NULL; if (SEL_NOT_EMPTY(state)) get_selection(&start_row, NULL, &end_row, NULL, state); for (int row = start_row; row < end_row; row++) + if (state->buffer[row].size > bufsize) + bufsize = state->buffer[row].size; + + buf = malloc(bufsize * sizeof(uint32_t)); + if (!buf) + goto do_search_replace_error; + + for (int row = start_row; row < end_row; row++) { - const uint32_t* before = state->buffer[row].text; - uint32_t* found = (uint32_t*)u32_strstr(before, state->search); - const uint32_t* after = before; + before = state->buffer[row].text; + found = (uint32_t*)u32_strstr(before, state->search); + after = before; *buf = 0; + buflen = 0; while (found) { state->dirty = 1; *found = 0; - u32_strncat(buf, before, MAXBUFLINE); - u32_strncat(buf, state->input, MAXBUFLINE); + buflen += u32_strlen(before) + u32_strlen(state->input); + + ENSURE_SIZE(buf, temp, bufsize, buflen + 1, buflen + 1, + do_search_replace_error, uint32_t); + u32_strncat(buf, before, bufsize); + u32_strncat(buf, state->input, bufsize); before = found + search_len; found = (uint32_t*)u32_strstr(before, state->search); if (found) @@ -260,12 +280,25 @@ do_search_replace(struct DrawState* state) after = before; } if (state->buffer[row].text + state->buffer[row].length > after) - u32_strncat(buf, after, MAXBUFLINE); + { + buflen += u32_strlen(after); + ENSURE_SIZE(buf, temp, bufsize, buflen + 1, buflen + 1, + do_search_replace_error, uint32_t); + u32_strncat(buf, after, bufsize); + } - u32_strncpy(state->buffer[row].text, buf, MAXBUFLINE); + U32_SAFE_COPY(state->buffer[row].text, temp, + state->buffer[row].size, buf, bufsize, + do_search_replace_error); state->buffer[row].length = u32_strlen(buf); } + free(buf); return 0; + +do_search_replace_error: + exit(print_error(ENOMEM, "Memory allocation failed")); + /* Unreachable */ + return -1; } int @@ -277,20 +310,21 @@ erase_selection(struct DrawState* state) int ssr = 0; int ssc = 0; /* Selection end row/col */ - int ser = 0; - int sec = 0; - uint32_t* buf = NULL; + int ser = 0; + int sec = 0; + uint32_t* buf = NULL; + size_t bufsize = state->buffer[ser].size; - buf = calloc(MAXBUFLINE, sizeof(uint32_t)); + buf = calloc(bufsize, sizeof(uint32_t)); if (!buf) exit(print_error(ENOMEM, "Memory allocation failed")); get_selection(&ssr, &ssc, &ser, &sec, state); - u32_strncpy(buf, state->buffer[ser].text + sec, MAXBUFLINE); + u32_strncpy(buf, state->buffer[ser].text + sec, bufsize); *(state->buffer[ssr].text + ssc) = 0; - u32_strncat(state->buffer[ssr].text, buf, MAXBUFLINE); + u32_strncat(state->buffer[ssr].text, buf, bufsize); state->buffer[ssr].length = u32_strlen(state->buffer[ssr].text); if (ssr != ser) @@ -300,7 +334,7 @@ erase_selection(struct DrawState* state) { u32_strncpy(state->buffer[ssr + row + 1].text, state->buffer[ser + row + 1].text, - MAXBUFLINE); + bufsize); state->buffer[ssr + row + 1].length = state->buffer[ser + row + 1].length; } @@ -385,7 +419,7 @@ go_to_line_callback(struct DrawState* state) if (!u8_input) { STRLCPY(errors[ERR_CANT_ALLOC], state->error, - MAXBUFLINE); + LINE_DEFAULT); state->running = 0; return; } @@ -394,7 +428,7 @@ go_to_line_callback(struct DrawState* state) lineno = strtoul(u8_input, NULL, 10); if (errno) STRLCPY(errors[ERR_INVALID_NUM], state->error, - MAXBUFLINE); + LINE_DEFAULT); if (!*state->error) { if (lineno > state->rows_count - 1) @@ -462,7 +496,7 @@ handle_key_event(struct tb_event* ev, struct DrawState* state) input_insert_char(state, ev->ch); else STRLCPY(errors[ERR_INPUT_NONNUM], state->error, - MAXBUFLINE); + LINE_DEFAULT); return; } } @@ -479,7 +513,7 @@ handle_key_event(struct tb_event* ev, struct DrawState* state) if (IN(state->mode, M_HELP)) { - STRLCPY(errors[ERR_DLG_OPEN], state->error, MAXBUFLINE); + STRLCPY(errors[ERR_DLG_OPEN], state->error, LINE_DEFAULT); return; } @@ -527,7 +561,7 @@ handle_key_event(struct tb_event* ev, struct DrawState* state) } } - STRLCPY(errors[ERR_UNKNOWN_KEY], state->error, MAXBUFLINE); + STRLCPY(errors[ERR_UNKNOWN_KEY], state->error, LINE_DEFAULT); } int @@ -577,10 +611,14 @@ insert_char(struct DrawState* state, uint32_t ch) struct BufferLine* buffer_line = NULL; size_t i; int add_null; - int len; + size_t len; + size_t size; + uint32_t* ptext = NULL; - if (state->coord.col >= MAXBUFLINE - 1) - return 1; + ENSURE_SIZE(state->buffer[state->coord.row].text, ptext, + state->buffer[state->coord.row].size, state->coord.col + 1, + state->coord.col + LINE_ALLOC_DELTA, insert_char_error, + uint32_t); if (SEL_NOT_EMPTY(state)) { @@ -592,10 +630,11 @@ insert_char(struct DrawState* state, uint32_t ch) buffer_line = &state->buffer[state->coord.row]; add_null = buffer_line->text[state->coord.col] == 0; len = buffer_line->length; + size = buffer_line->size; i = len + 1; if (!IN(state->mode, M_OVERWRITE)) - while (i < MAXBUFLINE - 2 && i > state->coord.col) + while (i < size - 1 && i > state->coord.col) { buffer_line->text[i] = buffer_line->text[i - 1]; i--; @@ -634,6 +673,9 @@ insert_char(struct DrawState* state, uint32_t ch) state->dirty = 1; return 0; + +insert_char_error: + return -1; } int @@ -648,6 +690,7 @@ join_lines(struct DrawState* state) int saved_row = state->coord.row; struct BufferLine* current = NULL; struct BufferLine* next = NULL; + uint32_t* ptext = NULL; if (SEL_NOT_EMPTY(state)) { @@ -670,25 +713,26 @@ join_lines(struct DrawState* state) { current = state->buffer + ssr; next = state->buffer + ssr + 1; - if (current->length + 2 < MAXBUFLINE) + ENSURE_SIZE(current->text, ptext, current->size, + current->length + 3, current->length + LINE_ALLOC_DELTA, + join_lines_error, uint32_t); + state->coord.row = ssr; + if (current->length > 0 + && !u32_is_word_boundary(current->text[current->length + - 1], + 1) + && !u32_is_word_boundary(next->text[0], 1)) { - state->coord.row = ssr; - if (current->length > 0 - && !u32_is_word_boundary( - current->text[current->length - 1], 1) - && !u32_is_word_boundary(next->text[0], 1)) - { - current->text[current->length] = ' '; - current->text[current->length + 1] = 0; - current->length++; - state->coord.col = current->length - 1; - } - else - state->coord.col = current->length; - state->display_column = display_length(state, - current->text, state->coord.col); - simple_join_lines(state); + current->text[current->length] = ' '; + current->text[current->length + 1] = 0; + current->length++; + state->coord.col = current->length - 1; } + else + state->coord.col = current->length; + state->display_column = display_length(state, current->text, + state->coord.col); + simple_join_lines(state); } state->coord.row = saved_row; @@ -696,6 +740,9 @@ join_lines(struct DrawState* state) position_to(state, ssr, ssc); return 0; + +join_lines_error: + return -1; } int @@ -704,28 +751,38 @@ load_file(struct DrawState* state, long* lineno, long* col) struct BufferLine* newchunk = NULL; struct BufferLine* line = NULL; FILE* input = NULL; - char input_line[MAXBUFLINE]; - uint32_t* newtext = NULL; + char* input_line = NULL; + size_t input_size = limits[L_LINE_MAX]; + size_t input_len = 0; + uint32_t* newtext = NULL; + uint32_t* ptext = NULL; + input_line = calloc(input_size, 1); + if (!input_line) + { + state->running = 0; + goto load_file_alloc_error; + } newchunk = calloc(ALLOC_DELTA, sizeof(struct BufferLine)); if (!newchunk) { state->running = 0; - return LOAD_ERR_CANT_ALLOC; + goto load_file_alloc_error; } for (size_t row = 0; row < ALLOC_DELTA; row++) { init_bufferline(newchunk + row); - newtext = calloc(MAXBUFLINE, sizeof(uint32_t)); + newtext = calloc(LINE_DEFAULT, sizeof(uint32_t)); if (!newtext) { state->running = 0; - return LOAD_ERR_CANT_ALLOC; + goto load_file_alloc_error; } + newchunk[row].size = LINE_DEFAULT; newchunk[row].text = newtext; } - STRLCPY(actions[ACT_READING], state->action, MAXBUFLINE); + STRLCPY(actions[ACT_READING], state->action, LINE_DEFAULT); redraw_status(state); @@ -740,7 +797,7 @@ load_file(struct DrawState* state, long* lineno, long* col) { char* eol = NULL; - if (!fgets(input_line, MAXBUFLINE, input)) + if (!fgets(input_line, input_size, input)) continue; eol = strchr(input_line, '\n'); if (eol) @@ -754,25 +811,31 @@ load_file(struct DrawState* state, long* lineno, long* col) if (!newchunk) { state->running = 0; - return LOAD_ERR_CANT_ALLOC; + goto load_file_alloc_error; } state->buffer = newchunk; for (size_t row = state->buffer_size - ALLOC_DELTA; row < state->buffer_size; row++) { init_bufferline(state->buffer + row); - newtext = calloc(MAXBUFLINE, sizeof(uint32_t)); + newtext = calloc(LINE_DEFAULT, sizeof(uint32_t)); if (!newtext) { state->running = 0; - return LOAD_ERR_CANT_ALLOC; + goto load_file_alloc_error; } + state->buffer[row].size = LINE_DEFAULT; state->buffer[row].text = newtext; } } - line = state->buffer + state->rows_count; + line = state->buffer + state->rows_count; + input_len = strlen(input_line); + ENSURE_SIZE(line->text, ptext, line->size, input_len + 1, + input_len + LINE_ALLOC_DELTA, load_file_alloc_error, + uint32_t); + line->length - = u32_u8_strncpy(line->text, input_line, MAXBUFLINE); + = u32_u8_strncpy(line->text, input_line, line->size); state->rows_count++; } fclose(input); @@ -781,7 +844,12 @@ load_file(struct DrawState* state, long* lineno, long* col) if (state->rows_count == 0) state->rows_count++; + free(input_line); return LOAD_ERR_NONE; + +load_file_alloc_error: + free(input_line); + return LOAD_ERR_CANT_ALLOC; } /* @@ -887,10 +955,10 @@ position_to_selection_start(struct DrawState* state) int print_error(const int code, const char* msg, ...) { - char buf[MAXBUFLINE]; + char buf[LINE_DEFAULT]; va_list args; va_start(args, msg); - vsnprintf(buf, MAXBUFLINE, msg, args); + vsnprintf(buf, LINE_DEFAULT, msg, args); va_end(args); fprintf(stderr, "%s: %s\n", program_name, buf); return code; @@ -922,11 +990,11 @@ reread_file(struct DrawState* state) switch (result) { case LOAD_ERR_CANT_ALLOC: - STRLCPY(errors[ERR_CANT_ALLOC], state->error, MAXBUFLINE); + STRLCPY(errors[ERR_CANT_ALLOC], state->error, LINE_DEFAULT); state->running = 0; break; case LOAD_ERR_CANT_OPEN_FILE: - STRLCPY(errors[ERR_CANT_OPEN], state->error, MAXBUFLINE); + STRLCPY(errors[ERR_CANT_OPEN], state->error, LINE_DEFAULT); state->running = 0; break; default:; @@ -948,17 +1016,19 @@ quit_callback(struct DrawState* state, struct tb_event* ev) int save_file(struct DrawState* state) { - FILE* output = NULL; - char* buf = NULL; + FILE* output = NULL; + char* buf = NULL; + char* pbuf = NULL; + size_t bufsize = LINE_DEFAULT; #ifdef CREATE_BACKUPS struct stat st; #endif int result = SAVE_ERR_NONE; - if (!(buf = calloc(MAXBUFLINE, 1))) + if (!(buf = calloc(bufsize, 1))) return SAVE_ERR_CANT_ALLOC; - STRLCPY(actions[ACT_WRITING], state->action, MAXBUFLINE); + STRLCPY(actions[ACT_WRITING], state->action, LINE_DEFAULT); redraw_status(state); errno = 0; @@ -970,7 +1040,10 @@ save_file(struct DrawState* state) if (state->backup_possible) { if (unlink(state->backup_filename) < 0 && errno != ENOENT) - return SAVE_ERR_CANT_COPY_FILE; + { + result = SAVE_ERR_CANT_COPY_FILE; + goto save_file_cleanup; + } errno = 0; cp(state->rfn_len > 0 ? state->real_filename : state->filename, state->backup_filename); @@ -1058,11 +1131,18 @@ save_skip_backup_symlink: for (size_t row = 0; row < state->rows_count; row++) { - unicode_string_to_u8(buf, state->buffer[row].text, MAXBUFLINE); + ENSURE_SIZE(buf, pbuf, bufsize, state->buffer[row].length + 1, + state->buffer[row].length + LINE_ALLOC_DELTA, + save_file_alloc_error, char); + unicode_string_to_u8(buf, state->buffer[row].text, bufsize); fprintf(output, "%s\n", buf); } state->new_file = 0; + goto save_file_cleanup; + +save_file_alloc_error: + result = SAVE_ERR_CANT_ALLOC; save_file_cleanup: fclose(output); @@ -1104,7 +1184,7 @@ search_replace_replace_dialog_callback(struct DrawState* state) void search_update_callback(struct DrawState* state) { - u32_strncpy(state->search, state->input, MAXBUFLINE); + u32_strncpy(state->search, state->input, MAXINPUT); } int @@ -1116,7 +1196,8 @@ cancel_close(struct DrawState* state) if (SEL_NOT_EMPTY(state)) clear_selection(state); else - STRLCPY(errors[ERR_EXIT_KEY], state->error, MAXBUFLINE); + STRLCPY(errors[ERR_EXIT_KEY], state->error, + LINE_DEFAULT); } else { @@ -1207,7 +1288,7 @@ erase_forward(struct DrawState* state) if (buffer_line->text[state->coord.col]) { size_t i = state->coord.col; - while (i < MAXBUFLINE - 2 && buffer_line->text[i + 1]) + while (i + 2 < buffer_line->size && buffer_line->text[i + 1]) { buffer_line->text[i] = buffer_line->text[i + 1]; i++; @@ -1218,11 +1299,8 @@ erase_forward(struct DrawState* state) } else if (state->coord.row < state->rows_count - 1) { - struct BufferLine* current = state->buffer + state->coord.row; - if (state->coord.row == state->rows_count - 1 - || current->length - + state->buffer[state->coord.row + 1].length - >= MAXBUFLINE) + // struct BufferLine* current = state->buffer + state->coord.row; + if (state->coord.row + 1 == state->rows_count) goto skip_join_lines; simple_join_lines(state); skip_join_lines:; @@ -1240,14 +1318,15 @@ erase_line(struct DrawState* state) state->buffer[0].length = 0; return 0; } + free(state->buffer[state->coord.row].text); for (size_t row = state->coord.row; row < state->rows_count - 1; row++) { current = state->buffer + row; - current->length = u32_strncpy(current->text, - state->buffer[row + 1].text, MAXBUFLINE); + current->text = state->buffer[row + 1].text; + current->size = state->buffer[row + 1].size; + current->length = state->buffer[row + 1].length; } - *state->buffer[state->rows_count - 1].text = 0; - state->buffer[state->rows_count - 1].length = 0; + free_bufferline(&state->buffer[state->rows_count - 1], 0); if (state->coord.row == state->rows_count - 1) state->coord.row--; position_to(state, state->coord.row, 0); @@ -1340,7 +1419,7 @@ exit_program(struct DrawState* state) if (state->dirty) { SET(state->mode, M_PROMPT); - STRLCPY(prompt_dirty, state->prompt, MAXBUFLINE); + STRLCPY(prompt_dirty, state->prompt, LINE_DEFAULT); state->prompt_callback = quit_callback; } else @@ -1416,7 +1495,7 @@ input_erase_forward(struct DrawState* state) if (state->input) { size_t i = state->input_column; - while (i < MAXBUFLINE - 2 && state->input[i + 1]) + while (i + 2 < MAXINPUT && state->input[i + 1]) { state->input[i] = state->input[i + 1]; i++; @@ -1433,7 +1512,7 @@ input_erase_to_end(struct DrawState* state) { state->input[state->input_column] = 0; input_move_end(state); - u32_strncpy(state->search, state->input, MAXBUFLINE); + u32_strncpy(state->search, state->input, MAXINPUT); if (state->input_update_callback) state->input_update_callback(state); return 0; @@ -1772,9 +1851,12 @@ int paste_from_paste(struct DrawState* state) { uint32_t* newtext = NULL; + uint32_t* ptext = NULL; + size_t newsize = 0; struct BufferLine* line = NULL; struct BufferLine* newbuf = NULL; uint32_t* rest = NULL; + size_t rest_size = 0; if (!state->paste_count) return 0; @@ -1800,7 +1882,8 @@ paste_from_paste(struct DrawState* state) row++) { init_bufferline(newbuf + row); - newtext = calloc(MAXBUFLINE, sizeof(uint32_t)); + newtext = calloc(LINE_DEFAULT, sizeof(uint32_t)); + newbuf[row].size = LINE_DEFAULT; newbuf[row].text = newtext; } state->buffer = newbuf; @@ -1808,59 +1891,77 @@ paste_from_paste(struct DrawState* state) } line = state->buffer + state->coord.row; - newtext = calloc(MAXBUFLINE, sizeof(uint32_t)); + newsize = line->size + state->paste[0].length + 1; + newtext = calloc(newsize, sizeof(uint32_t)); if (!newtext) { state->running = 0; - STRLCPY(errors[ERR_CANT_ALLOC], state->error, MAXBUFLINE); + STRLCPY(errors[ERR_CANT_ALLOC], state->error, LINE_DEFAULT); goto paste_from_paste_cleanup; } u32_strncpy(newtext, line->text, state->coord.col + 1); - u32_strncat(newtext, state->paste[0].text, MAXBUFLINE); + u32_strncat(newtext, state->paste[0].text, newsize); if (state->paste_count == 1) { - u32_strncat(newtext, line->text + state->coord.col, MAXBUFLINE); - line->length = u32_strncpy(line->text, newtext, MAXBUFLINE); + u32_strncat(newtext, line->text + state->coord.col, newsize); + line->length = u32_strncpy(line->text, newtext, newsize); + line->size = newsize; state->coord.col += state->paste[state->paste_count - 1].length; } else { state->rows_count += state->paste_count - 1; - rest = calloc(MAXBUFLINE, sizeof(uint32_t)); + rest_size = line->size - state->coord.col; + rest = calloc(rest_size, sizeof(uint32_t)); if (!rest) { state->running = 0; STRLCPY(errors[ERR_CANT_ALLOC], state->error, - MAXBUFLINE); + LINE_DEFAULT); goto paste_from_paste_cleanup; } - u32_strncpy(rest, line->text + state->coord.col, MAXBUFLINE); - line->length = u32_strncpy(line->text, newtext, MAXBUFLINE); + u32_strncpy(rest, line->text + state->coord.col, rest_size); + U32_SAFE_COPY(line->text, ptext, line->size, newtext, newsize, + paste_from_paste_cleanup); + line->length = u32_strlen(line->text); for (size_t row = state->rows_count - state->coord.row - (state->paste_count - 2) - 1; row > 0; row--) { + struct BufferLine* copied_line + = &state->buffer[state->coord.row + row - 1]; + line = state->buffer + state->coord.row + state->paste_count - 2 + row; - line->length = u32_strncpy(line->text, - state->buffer[state->coord.row + row - 1].text, - MAXBUFLINE); + U32_SAFE_COPY(line->text, ptext, line->size, + copied_line->text, copied_line->size, + paste_from_paste_cleanup); + line->length = u32_strlen(line->text); } - line = state->buffer + state->coord.row; - line->length = u32_strncpy(line->text, newtext, MAXBUFLINE); + line = state->buffer + state->coord.row; + U32_SAFE_COPY(line->text, ptext, line->size, newtext, newsize, + paste_from_paste_cleanup); + line->length = u32_strlen(line->text); for (size_t row = 1; row < state->paste_count - 1; row++) { - line = state->buffer + state->coord.row + row; - line->length = u32_strncpy(line->text, - state->paste[row].text, MAXBUFLINE); + line = state->buffer + state->coord.row + row; + U32_SAFE_COPY(line->text, ptext, line->size, + state->paste[row].text, state->paste[row].size, + paste_from_paste_cleanup); + line->length = u32_strlen(line->text); } line = state->buffer + state->coord.row + state->paste_count - 1; - u32_strncpy(newtext, state->paste[state->paste_count - 1].text, - MAXBUFLINE); - u32_strncat(newtext, rest, MAXBUFLINE); - line->length = u32_strncpy(line->text, newtext, MAXBUFLINE); + U32_SAFE_COPY(newtext, ptext, newsize, + state->paste[state->paste_count - 1].text, + state->paste[state->paste_count - 1].size + rest_size + + 1, + paste_from_paste_cleanup); + u32_strncat(newtext, rest, newsize); + U32_SAFE_COPY(line->text, ptext, line->size, newtext, newsize, + paste_from_paste_cleanup); + line->length = u32_strlen(line->text); state->coord.col = state->paste[state->paste_count - 1].length; state->coord.row += state->paste_count - 1; } @@ -1928,7 +2029,7 @@ reread_or_callback(struct DrawState* state) if (state->dirty) { SET(state->mode, M_PROMPT); - STRLCPY(prompt_reread_dirty, state->prompt, MAXBUFLINE); + STRLCPY(prompt_reread_dirty, state->prompt, LINE_DEFAULT); state->prompt_callback = reread_callback; } else @@ -1947,7 +2048,7 @@ show_go_to_line(struct DrawState* state) { state->running = 0; STRLCPY(errors[ERR_CANT_ALLOC], state->error, - MAXBUFLINE); + LINE_DEFAULT); return 0; } } @@ -1970,7 +2071,7 @@ show_replace(struct DrawState* state) { state->running = 0; STRLCPY(errors[ERR_CANT_ALLOC], state->error, - MAXBUFLINE); + LINE_DEFAULT); return 0; } } @@ -1991,7 +2092,7 @@ show_search(struct DrawState* state) { state->running = 0; STRLCPY(errors[ERR_CANT_ALLOC], state->error, - MAXBUFLINE); + LINE_DEFAULT); return 0; } } @@ -2005,7 +2106,7 @@ show_search(struct DrawState* state) { state->running = 0; STRLCPY(errors[ERR_CANT_ALLOC], state->error, - MAXBUFLINE); + LINE_DEFAULT); return 0; } } @@ -2026,7 +2127,7 @@ show_search_replace(struct DrawState* state) { state->running = 0; STRLCPY(errors[ERR_CANT_ALLOC], state->error, - MAXBUFLINE); + LINE_DEFAULT); return 0; } } @@ -2040,7 +2141,7 @@ show_search_replace(struct DrawState* state) { state->running = 0; STRLCPY(errors[ERR_CANT_ALLOC], state->error, - MAXBUFLINE); + LINE_DEFAULT); return 0; } } @@ -2066,7 +2167,7 @@ simple_insert_line(struct DrawState* state) { struct BufferLine* newchunk = NULL; struct BufferLine* line = NULL; - uint32_t* newtext = NULL; + uint32_t* ptext = NULL; if (state->rows_count == state->buffer_size) { @@ -2077,7 +2178,7 @@ simple_insert_line(struct DrawState* state) { state->running = 0; STRLCPY(errors[ERR_CANT_ALLOC], state->error, - MAXBUFLINE); + LINE_DEFAULT); return; } state->buffer = newchunk; @@ -2086,52 +2187,69 @@ simple_insert_line(struct DrawState* state) { line = state->buffer + row; init_bufferline(line); - newtext = calloc(MAXBUFLINE, sizeof(uint32_t)); - if (!newtext) - { - state->running = 0; - STRLCPY(errors[ERR_CANT_ALLOC], state->error, - MAXBUFLINE); - return; - } - line->text = newtext; + ENSURE_SIZE(line->text, ptext, line->size, LINE_DEFAULT, + LINE_DEFAULT, simple_insert_line_error, + uint32_t); } } state->rows_count++; for (size_t i = state->rows_count - 1; i > state->coord.row + 1; i--) - state->buffer[i].length = u32_strncpy(state->buffer[i].text, - state->buffer[i - 1].text, MAXBUFLINE); + { + U32_SAFE_COPY(state->buffer[i].text, ptext, + state->buffer[i].size, state->buffer[i - 1].text, + state->buffer[i - 1].size, simple_insert_line_error); + state->buffer[i].length = u32_strlen(state->buffer[i].text); + } + U32_SAFE_COPY(state->buffer[state->coord.row + 1].text, ptext, + state->buffer[state->coord.row + 1].size, + state->buffer[state->coord.row].text + state->coord.col, + state->buffer[state->coord.row].size, simple_insert_line_error); state->buffer[state->coord.row + 1].length - = u32_strncpy(state->buffer[state->coord.row + 1].text, - state->buffer[state->coord.row].text + state->coord.col, - MAXBUFLINE); + = u32_strlen(state->buffer[state->coord.row + 1].text); state->buffer[state->coord.row].text[state->coord.col] = 0; state->buffer[state->coord.row].length = state->coord.col; state->dirty = 1; + return; + +simple_insert_line_error: + return; } void simple_join_lines(struct DrawState* state) { size_t first_line = state->coord.row; + uint32_t* ptext = NULL; if (first_line == state->rows_count - 1) return; + ENSURE_SIZE(state->buffer[first_line].text, ptext, + state->buffer[first_line].size, + state->buffer[first_line].length + + state->buffer[first_line + 1].length + 1, + state->buffer[first_line].length + + state->buffer[first_line + 1].length + 1, + simple_join_lines_error, uint32_t); u32_strncat(state->buffer[first_line].text, - state->buffer[first_line + 1].text, MAXBUFLINE); + state->buffer[first_line + 1].text, + state->buffer[first_line].size); state->buffer[first_line].length = u32_strlen(state->buffer[first_line].text); for (size_t i = first_line + 1; i < state->rows_count - 1; i++) { - u32_strncpy(state->buffer[i].text, state->buffer[i + 1].text, - MAXBUFLINE); + U32_SAFE_COPY(state->buffer[i].text, ptext, + state->buffer[i].size, state->buffer[i + 1].text, + state->buffer[i + 1].size, simple_join_lines_error); state->buffer[i].length = state->buffer[i + 1].length; } - *state->buffer[state->rows_count - 1].text = 0; - state->buffer[state->rows_count - 1].length = 0; + free_bufferline(&state->buffer[state->rows_count - 1], 1); state->rows_count--; state->dirty = 1; + return; + +simple_join_lines_error: + return; } void @@ -2145,6 +2263,7 @@ simple_yank(struct DrawState* state) int sec = 0; struct BufferLine* newbuf = NULL; size_t initialize_start; + uint32_t* ptext = NULL; if (!SEL_NOT_EMPTY(state)) { @@ -2179,35 +2298,46 @@ simple_yank(struct DrawState* state) newbuf = state->paste; for (size_t row = initialize_start; row < state->paste_size; row++) { - uint32_t* newtext = NULL; init_bufferline(newbuf + row); - newtext = calloc(MAXBUFLINE, sizeof(uint32_t)); - if (!newtext) - { - state->running = 0; - STRLCPY(errors[ERR_CANT_ALLOC], state->error, - MAXBUFLINE); - } - newbuf[row].text = newtext; - newbuf[row].length = 0; + ENSURE_SIZE(newbuf[row].text, ptext, newbuf[row].size, + LINE_DEFAULT, LINE_DEFAULT, simple_yank_alloc_error, + uint32_t); } state->paste = newbuf; if (state->paste_count == 1) - state->paste[0].length = u32_strncpy(state->paste[0].text, - state->buffer[ssr].text + ssc, sec - ssc + 1); + { + U32_SAFE_COPY(state->paste[0].text, ptext, state->paste[0].size, + state->buffer[ssr].text + ssc, sec - ssc + 1, + simple_yank_alloc_error); + state->paste[0].length = u32_strlen(state->paste[0].text); + } else { - state->paste[0].length = u32_strncpy(state->paste[0].text, - state->buffer[ssr].text + ssc, MAXBUFLINE); + U32_SAFE_COPY(state->paste[0].text, ptext, state->paste[0].size, + state->buffer[ssr].text + ssc, sec - ssc + 1, + simple_yank_alloc_error); + state->paste[0].length = u32_strlen(state->paste[0].text); for (size_t row = 1; row < state->paste_count - 1; row++) + { + U32_SAFE_COPY(state->paste[row].text, ptext, + state->paste[row].size, + state->buffer[ssr + row].text, + state->buffer[ssr + row].size, + simple_yank_alloc_error); state->paste[row].length - = u32_strncpy(state->paste[row].text, - state->buffer[ssr + row].text, - MAXBUFLINE); + = u32_strlen(state->paste[row].text); + } + U32_SAFE_COPY(state->paste[state->paste_count - 1].text, ptext, + state->paste[state->paste_count - 1].size, + state->buffer[ser].text, sec + 1, + simple_yank_alloc_error); state->paste[state->paste_count - 1].length - = u32_strncpy(state->paste[state->paste_count - 1].text, - state->buffer[ser].text, sec + 1); + = u32_strlen(state->paste[state->paste_count - 1].text); } + return; + +simple_yank_alloc_error: + return; } int @@ -2264,22 +2394,22 @@ write_file(struct DrawState* state) switch (result) { case SAVE_ERR_CANT_ALLOC: - STRLCPY(errors[ERR_CANT_ALLOC], state->error, MAXBUFLINE); + STRLCPY(errors[ERR_CANT_ALLOC], state->error, LINE_DEFAULT); state->running = 0; break; case SAVE_ERR_CANT_OPEN_FILE: - STRLCPY(errors[ERR_CANT_SAVE], state->error, MAXBUFLINE); + STRLCPY(errors[ERR_CANT_SAVE], state->error, LINE_DEFAULT); state->running = 0; break; case SAVE_ERR_CANT_COPY_FILE: - STRLCPY(errors[ERR_CANT_COPY], state->error, MAXBUFLINE); + STRLCPY(errors[ERR_CANT_COPY], state->error, LINE_DEFAULT); state->running = 0; break; case SAVE_ERR_CANT_READLINK: case SAVE_ERR_CANT_STAT_FILE: case SAVE_ERR_CANT_SYMLINK: case SAVE_ERR_CANT_UNLINK: - STRLCPY(strerror(errno), state->error, MAXBUFLINE); + STRLCPY(strerror(errno), state->error, LINE_DEFAULT); state->running = 0; break; case SAVE_ERR_NONE: @@ -2304,23 +2434,24 @@ int main(int argc, char** argv) { struct tb_event ev; - char error[MAXBUFLINE]; - char action[MAXBUFLINE]; - char prompt[MAXBUFLINE]; + char error[LINE_DEFAULT]; + char action[LINE_DEFAULT]; + char prompt[LINE_DEFAULT]; struct DrawState state; - char filename[MAXPATH]; + char* filename = NULL; + size_t filename_size = 0; struct stat st; long lineno = 1; long col = 1; int result; int dir_access; int dir_access_errno; - char* dir = NULL; + char* dir = NULL; + char* pdir = NULL; - *error = 0; - *action = 0; - *prompt = 0; - *filename = 0; + *error = 0; + *action = 0; + *prompt = 0; #ifdef __OpenBSD__ if (pledge("stdio tty unveil rpath wpath cpath fattr", NULL) < 0) @@ -2334,7 +2465,8 @@ main(int argc, char** argv) if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { - printf("Usage:\t%s -h | --help | -V | --full-version | -v |" + printf("Usage:\t%s -h | --help | -V | --full-version | " + "-v |" " --version\n" "\t%s filename\n", program_name, program_name); @@ -2368,12 +2500,40 @@ main(int argc, char** argv) printf(" WRAP_WIDTH:\t\t%d\n", WRAP_WIDTH); return 0; } - else + + /* + * Chicken and egg problem: to get fpathconf(dirfd, _PC_PATH_MAX) + * we need a dir, which needs to be allocated, and to allocate it we + * need to know the value returned by fpathconf(dirfd, _PC_PATH_MAX)... + */ + /* argv[1] is the filename; BTW, "filename" is a misnomer */ + filename_size = strlen(argv[1]) + 1; + if (!(dir = malloc(filename_size))) + return print_error(1, "Allocation failed (out of memory?)"); + + (void)dirname(dir, argv[1], filename_size); + if (init_limits(dir) < 0) { - strncpy(filename, argv[1], MAXPATH - 1); - filename[MAXPATH - 1] = 0; - if (!*filename) - return print_error(EINVAL, "No filename given"); + perror("sled: init_limits"); + free(dir); + exit(1); + } + + filename = calloc(limits[L_PATH_MAX], 1); + if (!filename) + { + free(dir); + return print_error(1, "Allocation failed (out of memory?)"); + } + + *filename = 0; + strncpy(filename, argv[1], limits[L_PATH_MAX]); + filename[limits[L_PATH_MAX] - 1] = 0; + if (!*filename) + { + free(filename); + free(dir); + return print_error(EINVAL, "No filename given"); } init_drawstate(&state, error, action, filename, prompt, 0); @@ -2382,15 +2542,18 @@ main(int argc, char** argv) if (unveil(state.filename, "rwc") < 0) { perror("sled: unveil"); + free(filename); + free(dir); exit(1); } #endif - dir = dirname(state.filename); #ifdef __OpenBSD__ if (unveil(dir, "rwc") < 0) { perror("sled: unveil"); + free(filename); + free(dir); exit(1); } #endif @@ -2400,13 +2563,18 @@ main(int argc, char** argv) { if (S_ISLNK(st.st_mode)) { - if (!(state.real_filename = calloc(MAXPATH, 1))) + if (!(state.real_filename + = calloc(limits[L_PATH_MAX], 1))) + { + free(filename); + free(dir); return print_error(1, "Allocation failed (out of " "memory?)"); + } state.rfn_len = readlink(state.filename, - state.real_filename, MAXPATH); + state.real_filename, limits[L_PATH_MAX]); if (state.rfn_len == -1) goto file_error; state.real_filename[state.rfn_len] = 0; @@ -2414,6 +2582,7 @@ main(int argc, char** argv) if (unveil(state.real_filename, "rwc") < 0) { perror("sled: unveil"); + free(filename); exit(1); } #endif @@ -2426,23 +2595,28 @@ main(int argc, char** argv) else goto file_error; - if (!(state.backup_filename = calloc(MAXPATH, 1))) + if (!(state.backup_filename = calloc(limits[L_PATH_MAX], 1))) { free(state.real_filename); + free(filename); + free(dir); return print_error(1, "Allocation failed (out of memory?)"); } - /* Backup filename is the target file + suffix if the file to write - * is not a new file and is a symlink, otherwise file + suffix */ + /* Backup filename is the target file + suffix if the file to + * write is not a new file and is a symlink, otherwise file + + * suffix */ STRLCPY(state.new_file ? state.filename : (state.rfn_len > 0 ? state.real_filename : state.filename), - state.backup_filename, MAXPATH); - STRLCAT(backup_suffix, state.backup_filename, MAXPATH); + state.backup_filename, limits[L_PATH_MAX]); + STRLCAT(backup_suffix, state.backup_filename, limits[L_PATH_MAX]); #ifdef __OpenBSD__ if (unveil(state.backup_filename, "rwc") < 0) { perror("sled: unveil"); + free(filename); + free(dir); exit(1); } #endif @@ -2450,11 +2624,21 @@ main(int argc, char** argv) if (state.rfn_len > 0) { - dir = dirname(state.real_filename); + if (!(pdir = realloc(dir, limits[L_PATH_MAX]))) + { + free(filename); + return print_error(1, + "Allocation failed (out of memory?)"); + } + dir = pdir; + *dir = 0; + (void)dirname(dir, state.real_filename, state.rfn_len + 1); #ifdef __OpenBSD__ if (unveil(dir, "rwc") < 0) { perror("sled: unveil"); + free(filename); + free(dir); exit(1); } #endif @@ -2500,6 +2684,8 @@ main(int argc, char** argv) if (unveil(NULL, NULL) < 0) { perror("sled: unveil"); + free(filename); + free(dir); exit(1); } #endif @@ -2533,6 +2719,8 @@ file_error: general_error: free(state.real_filename); free(state.backup_filename); + free(filename); + free(dir); return result; init_termbox: @@ -2550,7 +2738,8 @@ init_termbox: if (!draw(&state)) { state.running = 0; - STRLCPY(errors[ERR_CANT_ALLOC], state.error, MAXBUFLINE); + STRLCPY(errors[ERR_CANT_ALLOC], state.error, + LINE_DEFAULT); } tb_present(); result = tb_poll_event(&ev); @@ -2564,9 +2753,12 @@ init_termbox: { if (errno != 0) perror(program_name); + free(filename); return print_error(1, state.error); } free_drawstate(&state, 0); + free(filename); + free(dir); return 0; } diff --git a/tests/003-replace-text-100000-lines.test b/tests/003-replace-text-100000-lines.test @@ -0,0 +1,34 @@ +#!/bin/sh +# vim: set ft=bash: +exec 2>/dev/null +testfile=tests/testfile +text=$( for k in $(seq 0 9) + do + printf "$k" + done) +text=$( for j in $(seq 7) + do + printf "%s" "$text" + done) +text=$( for i in $(seq 100) + do + printf "%s\n" "$text" + done) + +for i in $(seq 500) +do + printf "%s\n" "$text" +done > "$testfile" + +set -x +cat "$testfile" "$testfile" > "${testfile}~" +mv "${testfile}~" "$testfile" + +expect tests/replace-ext.expect +if [ -n "$(tail -n 1 "$testfile" | grep "brown fox")" ]; then + result=0 +else + result=1 +fi +rm "$testfile" +exit "$result" diff --git a/tests/replace-ext.expect b/tests/replace-ext.expect @@ -0,0 +1,21 @@ +# vim: set ft=expect: +spawn ./sled tests/testfile +interact timeout 1 return +send_user "Entering command mode" +send "\034" +interact timeout 1 return +send_user "Pressing \\" +send "\\" +interact timeout 1 return +send_user "Entering text" +send "4567\r" +interact timeout 1 return +send_user "Entering replacement" +send "Quick brown fox\r" +interact timeout 3 return +send_user "Pressing Ctrl+S" +send "\023" +interact timeout 5 return +send_user "Pressing Ctrl+Q" +send "\021" +exit diff --git a/util.c b/util.c @@ -7,6 +7,7 @@ #include <stdlib.h> #include <string.h> +#include "limit.h" #include "termbox.h" #include "util.h" @@ -27,9 +28,9 @@ u8_string_to_unicode(uint32_t* us, const char* s, const size_t max) u8_len = tb_utf8_char_to_unicode(&uch, s); if (added + 1 > max) break; - *pus++ = uch; if (u8_len != TB_ERR) { + *pus++ = uch; s += u8_len; added++; } @@ -155,9 +156,7 @@ u32_strncpy(uint32_t* to, const uint32_t* from, const size_t max) { const uint32_t* pfrom = from; size_t copied = 0; - /* pfrom - from < max - 1 ... except we can't reliably subtract with - * size_t */ - while (*pfrom && pfrom + 1 < max + from) + while (*pfrom && copied < max) { *to++ = *pfrom++; copied++; @@ -175,12 +174,12 @@ u32_strncat(uint32_t* to, const uint32_t* from, const size_t max) { const uint32_t* pfrom = from; size_t copied = 0; - while (*to && copied + 1 < max) + while (*to && copied < max) { to++; copied++; } - while (*pfrom && copied + 1 < max) + while (*pfrom && copied < max) { *to++ = *pfrom++; copied++; @@ -192,9 +191,9 @@ u32_strncat(uint32_t* to, const uint32_t* from, const size_t max) size_t u32_u8_strncpy(uint32_t* to, const char* from, const size_t max) { - uint32_t* ufrom = calloc(max + 1, sizeof(uint32_t)); + uint32_t* ufrom = NULL; size_t len; - if (!ufrom) + if (!(ufrom = malloc(max * sizeof(uint32_t)))) return 0; u8_string_to_unicode(ufrom, from, max); len = u32_strncpy(to, ufrom, max); diff --git a/util.h b/util.h @@ -4,10 +4,21 @@ #include <stdint.h> #include <sys/types.h> +#include <unistd.h> + #define ABS(a) ((a < 0) ? (-a) : (a)) #define LEN(x) (sizeof(x) / sizeof(x[0])) #define MAX(a, b) ((a > b) ? a : b) #define MIN(a, b) ((a < b) ? a : b) +#define MEMCCPY(from, to, tolen, temp) \ + do \ + { \ + if (!(temp = memccpy(to, from, 0, tolen))) \ + { \ + print_error(1, "memccpy:%d: Overflow", __LINE__); \ + exit(1); \ + } \ + } while (0) #define STRLCAT(tail, head, totallen) \ do \ { \ @@ -27,6 +38,47 @@ } \ } while (0) +#define ENSURE_SIZE(to, temp, tosize, testsize, fromsize, label, type) \ + do \ + { \ + if (tosize < testsize) \ + { \ + tosize = fromsize; \ + temp = realloc(to, tosize * sizeof(type)); \ + if (!temp) \ + { \ + state->running = 0; \ + STRLCPY(errors[ERR_CANT_ALLOC], state->error, \ + LINE_DEFAULT); \ + goto label; \ + } \ + to = temp; \ + } \ + } while (0) +/* + * - Make sure the size of the destination string is large enough to copy, and + * copy + * - Sizes are in bytes + */ +#define U8_SAFE_COPY(to, temp, tosize, from, fromsize, label) \ + do \ + { \ + ENSURE_SIZE(to, temp, tosize, fromsize, fromsize, label, char); \ + u8_strncpy(to, from, tosize); \ + } while (0) +/* + * - Make sure the size of the destination string is large enough to copy, and + * copy + * - Sizes are in uint32_t units + */ +#define U32_SAFE_COPY(to, temp, tosize, from, fromsize, label) \ + do \ + { \ + ENSURE_SIZE(to, temp, tosize, fromsize, fromsize, label, \ + uint32_t); \ + u32_strncpy(to, from, tosize); \ + } while (0) + enum { UNKNOWN_MODE, COMMAND_MODE, @@ -53,14 +105,9 @@ enum { enum { ACT_WRITING, ACT_READING }; -#define ALLOC_DELTA 50 -#define MAXBUFLINE 4096 #define MAXCOPYBUF 8192 #define MAXDATEBUF 80 #define MAXFLAGSBUF (4 + 1) -#define MAXMSGLINE 1024 -#define MSGDELTA 1024 -#define MAXPATH 1024 /* maximum chars for UTF-8 representations of Unicode chars, per Unicode char */ #define UTF8REPMAX 6 #define TB_KEY_CTRL_SPACE 0x00