diff options
author | zeertzjq <zeertzjq@outlook.com> | 2023-02-28 19:04:32 +0800 |
---|---|---|
committer | zeertzjq <zeertzjq@outlook.com> | 2023-02-28 19:52:45 +0800 |
commit | bfa0bc7df0ca527fcec49dbd2055f1bac438663e (patch) | |
tree | 9b654edacf1b0f91bbf79b70b3fceec8d4e911b5 | |
parent | 3b927762264c27aaeb31b83ba2e4924d5312ddcc (diff) | |
download | rneovim-bfa0bc7df0ca527fcec49dbd2055f1bac438663e.tar.gz rneovim-bfa0bc7df0ca527fcec49dbd2055f1bac438663e.tar.bz2 rneovim-bfa0bc7df0ca527fcec49dbd2055f1bac438663e.zip |
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 vim/vim#11402)
https://github.com/vim/vim/commit/11df3aeee548b959ccd4b9a4d3c44651eab6b3ce
Remove trailing whitespace in test as done in patch 9.0.1257.
Move the help for rand() before range().
Co-authored-by: K.Takata <kentkt@csc.jp>
-rw-r--r-- | runtime/doc/builtin.txt | 53 | ||||
-rw-r--r-- | src/nvim/eval.c | 50 | ||||
-rw-r--r-- | src/nvim/eval.lua | 2 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 28 | ||||
-rw-r--r-- | src/nvim/testdir/test_blob.vim | 19 |
5 files changed, 110 insertions, 42 deletions
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index ed8c78ef2f..113703680f 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -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,22 @@ 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|. + 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 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()|. diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 698172442e..c2f84735b2 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5859,27 +5859,57 @@ 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 + } + + int whence; + off_T size = size_arg; + if (offset >= 0) { + if (size == -1) { + // size may become negative, checked below + size = (off_T)os_fileinfo_size(&file_info) - 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 > (off_T)os_fileinfo_size(&file_info)) { + return OK; + } + if (vim_fseek(fd, offset, whence) != 0) { + return OK; } - const int size = (int)os_fileinfo_size(&file_info); - ga_grow(&blob->bv_ga, size); - blob->bv_ga.ga_len = size; + + 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. diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index a476e20339..24263d9aee 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -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}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index d3bac14754..5da6233e38 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -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; diff --git a/src/nvim/testdir/test_blob.vim b/src/nvim/testdir/test_blob.vim index b1859f9dfe..dd37fb2f11 100644 --- a/src/nvim/testdir/test_blob.vim +++ b/src/nvim/testdir/test_blob.vim @@ -439,10 +439,29 @@ 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) + + VAR br1e = readblob('Xblob', 10000) + call assert_equal(0z, br1e) + VAR br2e = readblob('Xblob', -10000) + call assert_equal(0z, br2e) + call delete('Xblob') END call 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. call assert_fails("call readfile('.', 'B')", 'E17: "." is a directory') endfunc |