1
0
forked from aniani/vim

patch 9.0.0795: readblob() always reads the whole file

Problem:    readblob() always reads the whole file.
Solution:   Add arguments to read part of the file. (Ken Takata,
            closes #11402)
This commit is contained in:
K.Takata
2022-10-19 14:02:40 +01:00
committed by Bram Moolenaar
parent 9f62ea01a0
commit 11df3aeee5
7 changed files with 98 additions and 23 deletions

View File

@@ -445,7 +445,8 @@ pyxeval({expr}) any evaluate |python_x| expression
rand([{expr}]) Number get pseudo-random number rand([{expr}]) Number get pseudo-random number
range({expr} [, {max} [, {stride}]]) range({expr} [, {max} [, {stride}]])
List items from {expr} to {max} List items from {expr} to {max}
readblob({fname}) Blob read a |Blob| from {fname} readblob({fname} [, {offset} [, {size}]])
Blob read a |Blob| from {fname}
readdir({dir} [, {expr} [, {dict}]]) readdir({dir} [, {expr} [, {dict}]])
List file names in {dir} selected by {expr} List file names in {dir} selected by {expr}
readdirex({dir} [, {expr} [, {dict}]]) readdirex({dir} [, {expr} [, {dict}]])
@@ -6847,10 +6848,21 @@ range({expr} [, {max} [, {stride}]]) *range()*
GetExpr()->range() GetExpr()->range()
< <
readblob({fname}) *readblob()* readblob({fname} [, {offset} [, {size}]]) *readblob()*
Read file {fname} in binary mode and return a |Blob|. Read file {fname} in binary mode and return a |Blob|.
If {offset} is specified, read the file from the specified
offset. If it is a negative value, it is used as an offset
from the end of the file. E.g., to read the last 12 bytes: >
readblob('file.bin', -12)
< If {size} is specified, only the specified size will be read.
E.g. to read the first 100 bytes of a file: >
readblob('file.bin', 0, 100)
< If {size} is -1 or omitted, the whole data starting from
{offset} will be read.
When the file can't be opened an error message is given and When the file can't be opened an error message is given and
the result is an empty |Blob|. the result is an empty |Blob|.
When trying to read bytes beyond the end of the file the
result is an empty blob.
Also see |readfile()| and |writefile()|. Also see |readfile()| and |writefile()|.

View File

@@ -182,22 +182,52 @@ blob_equal(
} }
/* /*
* Read "blob" from file "fd". * Read blob from file "fd".
* Caller has allocated a blob in "rettv".
* Return OK or FAIL. * Return OK or FAIL.
*/ */
int int
read_blob(FILE *fd, blob_T *blob) read_blob(FILE *fd, typval_T *rettv, off_T offset, off_T size_arg)
{ {
blob_T *blob = rettv->vval.v_blob;
struct stat st; struct stat st;
int whence;
off_T size = size_arg;
if (fstat(fileno(fd), &st) < 0) if (fstat(fileno(fd), &st) < 0)
return FAIL; // can't read the file, error
if (offset >= 0)
{
if (size == -1)
// size may become negative, checked below
size = st.st_size - offset;
whence = SEEK_SET;
}
else
{
if (size == -1)
size = -offset;
whence = SEEK_END;
}
// Trying to read bytes that aren't there results in an empty blob, not an
// error.
if (size < 0 || size > st.st_size)
return OK;
if (vim_fseek(fd, offset, whence) != 0)
return OK;
if (ga_grow(&blob->bv_ga, (int)size) == FAIL)
return FAIL; return FAIL;
if (ga_grow(&blob->bv_ga, st.st_size) == FAIL) blob->bv_ga.ga_len = (int)size;
return FAIL;
blob->bv_ga.ga_len = st.st_size;
if (fread(blob->bv_ga.ga_data, 1, blob->bv_ga.ga_len, fd) if (fread(blob->bv_ga.ga_data, 1, blob->bv_ga.ga_len, fd)
< (size_t)blob->bv_ga.ga_len) < (size_t)blob->bv_ga.ga_len)
{
// An empty blob is returned on error.
blob_free(rettv->vval.v_blob);
rettv->vval.v_blob = NULL;
return FAIL; return FAIL;
}
return OK; return OK;
} }

View File

@@ -1078,6 +1078,7 @@ static argcheck_T arg3_string_any_dict[] = {arg_string, NULL, arg_dict_any};
static argcheck_T arg3_string_any_string[] = {arg_string, NULL, arg_string}; static argcheck_T arg3_string_any_string[] = {arg_string, NULL, arg_string};
static argcheck_T arg3_string_bool_bool[] = {arg_string, arg_bool, arg_bool}; static argcheck_T arg3_string_bool_bool[] = {arg_string, arg_bool, arg_bool};
static argcheck_T arg3_string_number_bool[] = {arg_string, arg_number, arg_bool}; static argcheck_T arg3_string_number_bool[] = {arg_string, arg_number, arg_bool};
static argcheck_T arg3_string_number_number[] = {arg_string, arg_number, arg_number};
static argcheck_T arg3_string_or_dict_bool_dict[] = {arg_string_or_dict_any, arg_bool, arg_dict_any}; static argcheck_T arg3_string_or_dict_bool_dict[] = {arg_string_or_dict_any, arg_bool, arg_dict_any};
static argcheck_T arg3_string_string_bool[] = {arg_string, arg_string, arg_bool}; static argcheck_T arg3_string_string_bool[] = {arg_string, arg_string, arg_bool};
static argcheck_T arg3_string_string_dict[] = {arg_string, arg_string, arg_dict_any}; static argcheck_T arg3_string_string_dict[] = {arg_string, arg_string, arg_dict_any};
@@ -2339,7 +2340,7 @@ static funcentry_T global_functions[] =
ret_number, f_rand}, ret_number, f_rand},
{"range", 1, 3, FEARG_1, arg3_number, {"range", 1, 3, FEARG_1, arg3_number,
ret_list_number, f_range}, ret_list_number, f_range},
{"readblob", 1, 1, FEARG_1, arg1_string, {"readblob", 1, 3, FEARG_1, arg3_string_number_number,
ret_blob, f_readblob}, ret_blob, f_readblob},
{"readdir", 1, 3, FEARG_1, arg3_string_any_dict, {"readdir", 1, 3, FEARG_1, arg3_string_any_dict,
ret_list_string, f_readdir}, ret_list_string, f_readdir},

View File

@@ -1792,16 +1792,27 @@ read_file_or_blob(typval_T *argvars, typval_T *rettv, int always_blob)
long cnt = 0; long cnt = 0;
char_u *p; // position in buf char_u *p; // position in buf
char_u *start; // start of current line char_u *start; // start of current line
off_T offset = 0;
off_T size = -1;
if (argvars[1].v_type != VAR_UNKNOWN) if (argvars[1].v_type != VAR_UNKNOWN)
{ {
if (STRCMP(tv_get_string(&argvars[1]), "b") == 0) if (always_blob)
binary = TRUE; {
if (STRCMP(tv_get_string(&argvars[1]), "B") == 0) offset = (off_T)tv_get_number(&argvars[1]);
blob = TRUE; if (argvars[2].v_type != VAR_UNKNOWN)
size = (off_T)tv_get_number(&argvars[2]);
}
else
{
if (STRCMP(tv_get_string(&argvars[1]), "b") == 0)
binary = TRUE;
if (STRCMP(tv_get_string(&argvars[1]), "B") == 0)
blob = TRUE;
if (argvars[2].v_type != VAR_UNKNOWN) if (argvars[2].v_type != VAR_UNKNOWN)
maxline = (long)tv_get_number(&argvars[2]); maxline = (long)tv_get_number(&argvars[2]);
}
} }
if ((blob ? rettv_blob_alloc(rettv) : rettv_list_alloc(rettv)) == FAIL) if ((blob ? rettv_blob_alloc(rettv) : rettv_list_alloc(rettv)) == FAIL)
@@ -1818,19 +1829,15 @@ read_file_or_blob(typval_T *argvars, typval_T *rettv, int always_blob)
} }
if (*fname == NUL || (fd = mch_fopen((char *)fname, READBIN)) == NULL) if (*fname == NUL || (fd = mch_fopen((char *)fname, READBIN)) == NULL)
{ {
semsg(_(e_cant_open_file_str), *fname == NUL ? (char_u *)_("<empty>") : fname); semsg(_(e_cant_open_file_str),
*fname == NUL ? (char_u *)_("<empty>") : fname);
return; return;
} }
if (blob) if (blob)
{ {
if (read_blob(fd, rettv->vval.v_blob) == FAIL) if (read_blob(fd, rettv, offset, size) == FAIL)
{
semsg(_(e_cant_read_file_str), fname); semsg(_(e_cant_read_file_str), fname);
// An empty blob is returned on error.
blob_free(rettv->vval.v_blob);
rettv->vval.v_blob = NULL;
}
fclose(fd); fclose(fd);
return; return;
} }
@@ -2007,7 +2014,11 @@ read_file_or_blob(typval_T *argvars, typval_T *rettv, int always_blob)
void void
f_readblob(typval_T *argvars, typval_T *rettv) f_readblob(typval_T *argvars, typval_T *rettv)
{ {
if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL) if (in_vim9script()
&& (check_for_string_arg(argvars, 0) == FAIL
|| check_for_opt_number_arg(argvars, 1) == FAIL
|| (argvars[1].v_type != VAR_UNKNOWN
&& check_for_opt_number_arg(argvars, 2) == FAIL)))
return; return;
read_file_or_blob(argvars, rettv, TRUE); read_file_or_blob(argvars, rettv, TRUE);

View File

@@ -10,7 +10,7 @@ int blob_get(blob_T *b, int idx);
void blob_set(blob_T *blob, int idx, int byte); void blob_set(blob_T *blob, int idx, int byte);
void blob_set_append(blob_T *blob, int idx, int byte); void blob_set_append(blob_T *blob, int idx, int byte);
int blob_equal(blob_T *b1, blob_T *b2); int blob_equal(blob_T *b1, blob_T *b2);
int read_blob(FILE *fd, blob_T *blob); int read_blob(FILE *fd, typval_T *rettv, off_T offset, off_T size);
int write_blob(FILE *fd, blob_T *blob); int write_blob(FILE *fd, blob_T *blob);
char_u *blob2string(blob_T *blob, char_u **tofree, char_u *numbuf); char_u *blob2string(blob_T *blob, char_u **tofree, char_u *numbuf);
blob_T *string2blob(char_u *str); blob_T *string2blob(char_u *str);

View File

@@ -488,10 +488,29 @@ func Test_blob_read_write()
call writefile(b, 'Xblob') call writefile(b, 'Xblob')
VAR br = readfile('Xblob', 'B') VAR br = readfile('Xblob', 'B')
call assert_equal(b, br) call assert_equal(b, br)
VAR br2 = readblob('Xblob')
call assert_equal(b, br2)
VAR br3 = readblob('Xblob', 1)
call assert_equal(b[1 :], br3)
VAR br4 = readblob('Xblob', 1, 2)
call assert_equal(b[1 : 2], br4)
VAR br5 = readblob('Xblob', -3)
call assert_equal(b[-3 :], br5)
VAR br6 = readblob('Xblob', -3, 2)
call assert_equal(b[-3 : -2], br6)
VAR br1e = readblob('Xblob', 10000)
call assert_equal(0z, br1e)
VAR br2e = readblob('Xblob', -10000)
call assert_equal(0z, br2e)
call delete('Xblob') call delete('Xblob')
END END
call v9.CheckLegacyAndVim9Success(lines) call v9.CheckLegacyAndVim9Success(lines)
call assert_fails("call readblob('notexist')", 'E484:')
" TODO: How do we test for the E485 error?
" This was crashing when calling readfile() with a directory. " This was crashing when calling readfile() with a directory.
call assert_fails("call readfile('.', 'B')", 'E17: "." is a directory') call assert_fails("call readfile('.', 'B')", 'E17: "." is a directory')
endfunc endfunc

View File

@@ -695,6 +695,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
795,
/**/ /**/
794, 794,
/**/ /**/