Merge pull request #22453 from zeertzjq/vim-9.0.0795

vim-patch:9.0.{0795,0803,0810}: readblob() offset and size
This commit is contained in:
zeertzjq 2023-02-28 20:12:50 +08:00 committed by GitHub
commit 3f381f4d04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 136 additions and 43 deletions

View File

@ -359,7 +359,8 @@ pyxeval({expr}) any evaluate |python_x| expression
rand([{expr}]) Number get pseudo-random number
range({expr} [, {max} [, {stride}]])
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}]) List file names in {dir} selected by {expr}
readfile({fname} [, {type} [, {max}]])
List get list of lines from file {fname}
@ -6110,6 +6111,25 @@ pyxeval({expr}) *pyxeval()*
Can also be used as a |method|: >
GetExpr()->pyxeval()
<
rand([{expr}]) *rand()*
Return a pseudo-random Number generated with an xoshiro128**
algorithm using seed {expr}. The returned number is 32 bits,
also on 64 bits systems, for consistency.
{expr} can be initialized by |srand()| and will be updated by
rand(). If {expr} is omitted, an internal seed value is used
and updated.
Returns -1 if {expr} is invalid.
Examples: >
:echo rand()
:let seed = srand()
:echo rand(seed)
:echo rand(seed) % 16 " random number 0 - 15
<
Can also be used as a |method|: >
seed->rand()
<
*E726* *E727*
range({expr} [, {max} [, {stride}]]) *range()*
Returns a |List| with Numbers:
@ -6132,29 +6152,29 @@ range({expr} [, {max} [, {stride}]]) *range()*
Can also be used as a |method|: >
GetExpr()->range()
<
rand([{expr}]) *rand()*
Return a pseudo-random Number generated with an xoshiro128**
algorithm using seed {expr}. The returned number is 32 bits,
also on 64 bits systems, for consistency.
{expr} can be initialized by |srand()| and will be updated by
rand(). If {expr} is omitted, an internal seed value is used
and updated.
Returns -1 if {expr} is invalid.
Examples: >
:echo rand()
:let seed = srand()
:echo rand(seed)
:echo rand(seed) % 16 " random number 0 - 15
<
Can also be used as a |method|: >
seed->rand()
<
readblob({fname}) *readblob()*
readblob({fname} [, {offset} [, {size}]]) *readblob()*
Read file {fname} in binary mode and return a |Blob|.
When the file can't be opened an error message is given and
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.
This can be also used to read the data from a character device
on Unix when {size} is explicitly set. Only if the device
supports seeking {offset} can be used. Otherwise it should be
zero. E.g. to read 10 bytes from a serial console: >
readblob('/dev/ttyS0', 0, 10)
< When the file can't be opened an error message is given and
the result is an empty |Blob|.
When the offset is beyond the end of the file the result is an
empty blob.
When trying to read more bytes than are available the result
is truncated.
Also see |readfile()| and |writefile()|.

View File

@ -5859,27 +5859,63 @@ write_blob_error:
return false;
}
/// Read a blob from a file `fd`.
/// Read blob from file "fd".
/// Caller has allocated a blob in "rettv".
///
/// @param[in] fd File to read from.
/// @param[in,out] blob Blob to write to.
/// @param[in,out] rettv Blob to write to.
/// @param[in] offset Read the file from the specified offset.
/// @param[in] size Read the specified size, or -1 if no limit.
///
/// @return true on success, or false on failure.
bool read_blob(FILE *const fd, blob_T *const blob)
/// @return OK on success, or FAIL on failure.
int read_blob(FILE *const fd, typval_T *rettv, off_T offset, off_T size_arg)
FUNC_ATTR_NONNULL_ALL
{
blob_T *const blob = rettv->vval.v_blob;
FileInfo file_info;
if (!os_fileinfo_fd(fileno(fd), &file_info)) {
return false;
return FAIL; // can't read the file, error
}
const int size = (int)os_fileinfo_size(&file_info);
ga_grow(&blob->bv_ga, size);
blob->bv_ga.ga_len = size;
int whence;
off_T size = size_arg;
const off_T file_size = (off_T)os_fileinfo_size(&file_info);
if (offset >= 0) {
// The size defaults to the whole file. If a size is given it is
// limited to not go past the end of the file.
if (size == -1 || (size > file_size - offset && !S_ISCHR(file_info.stat.st_mode))) {
// size may become negative, checked below
size = (off_T)os_fileinfo_size(&file_info) - offset;
}
whence = SEEK_SET;
} else {
// limit the offset to not go before the start of the file
if (-offset > file_size && !S_ISCHR(file_info.stat.st_mode)) {
offset = -file_size;
}
// Size defaults to reading until the end of the file.
if (size == -1 || size > -offset) {
size = -offset;
}
whence = SEEK_END;
}
if (size <= 0) {
return OK;
}
if (offset != 0 && vim_fseek(fd, offset, whence) != 0) {
return OK;
}
ga_grow(&blob->bv_ga, (int)size);
blob->bv_ga.ga_len = (int)size;
if (fread(blob->bv_ga.ga_data, 1, (size_t)blob->bv_ga.ga_len, fd)
< (size_t)blob->bv_ga.ga_len) {
return false;
// An empty blob is returned on error.
tv_blob_free(rettv->vval.v_blob);
rettv->vval.v_blob = NULL;
return FAIL;
}
return true;
return OK;
}
/// Saves a typval_T as a string.

View File

@ -296,7 +296,7 @@ return {
perleval={args=1, base=1},
rand={args={0, 1}, base=1},
range={args={1, 3}, base=1},
readblob={args=1, base=1},
readblob={args={1, 3}, base=1},
readdir={args={1, 2}, base=1},
readfile={args={1, 3}, base=1},
reduce={args={2, 3}, base=1},

View File

@ -5592,15 +5592,24 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl
ptrdiff_t prevlen = 0; // length of data in prev
ptrdiff_t prevsize = 0; // size of prev buffer
int64_t maxline = MAXLNUM;
off_T offset = 0;
off_T size = -1;
if (argvars[1].v_type != VAR_UNKNOWN) {
if (strcmp(tv_get_string(&argvars[1]), "b") == 0) {
binary = true;
} else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) {
blob = true;
}
if (argvars[2].v_type != VAR_UNKNOWN) {
maxline = tv_get_number(&argvars[2]);
if (always_blob) {
offset = (off_T)tv_get_number(&argvars[1]);
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;
} else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) {
blob = true;
}
if (argvars[2].v_type != VAR_UNKNOWN) {
maxline = tv_get_number(&argvars[2]);
}
}
}
@ -5619,11 +5628,8 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl
if (blob) {
tv_blob_alloc_ret(rettv);
if (!read_blob(fd, rettv->vval.v_blob)) {
if (read_blob(fd, rettv, offset, size) == FAIL) {
semsg(_(e_notread), fname);
// An empty blob is returned on error.
tv_blob_free(rettv->vval.v_blob);
rettv->vval.v_blob = NULL;
}
fclose(fd);
return;

View File

@ -439,10 +439,41 @@ func Test_blob_read_write()
call writefile(b, 'Xblob')
VAR br = readfile('Xblob', 'B')
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)
#" reading past end of file, empty result
VAR br1e = readblob('Xblob', 10000)
call assert_equal(0z, br1e)
#" reading too much, result is truncated
VAR blong = readblob('Xblob', -1000)
call assert_equal(b, blong)
LET blong = readblob('Xblob', -10, 8)
call assert_equal(b, blong)
LET blong = readblob('Xblob', 0, 10)
call assert_equal(b, blong)
call delete('Xblob')
END
call CheckLegacyAndVim9Success(lines)
if filereadable('/dev/random')
let b = readblob('/dev/random', 0, 10)
call assert_equal(10, len(b))
endif
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.
call assert_fails("call readfile('.', 'B')", 'E17: "." is a directory')
endfunc