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:
@@ -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()|.
|
||||||
|
|
||||||
|
|
||||||
|
40
src/blob.c
40
src/blob.c
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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},
|
||||||
|
@@ -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);
|
||||||
|
@@ -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);
|
||||||
|
@@ -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
|
||||||
|
@@ -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,
|
||||||
/**/
|
/**/
|
||||||
|
Reference in New Issue
Block a user