-
-
Notifications
You must be signed in to change notification settings - Fork 178
Enhancement/efi shell interface #1679
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enhancement/efi shell interface #1679
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this! As a general rule of thumb: High-level wrappers should be close to the API of std
and not replicate "ugly" or unusual details of the low-level API. See my suggestions for get_var
Some general guidance:
- Start with a PR that just updates
uefi-raw
. (As Philipp noted here, the definition of the shell protocol that matches the C spec should go in uefi-raw. See for exampleRngProtocol
. api_guidelines has some notes about how to implement stuff in uefi-raw.) - Once that's merged, do small PRs that add ergonomic wrappers to the
uefi
crate. Small PRs will help get review done faster. - Regarding
Vec
, it's generally OK to use, but does need to be gated by thealloc
feature. (I haven't looked at the code yet though, I'm just commenting generally.)
Thanks for the feedback! I will start with making the PR to update uefi-raw
as Nicholas suggested, then come back to address the other comments.
Hi all. Now that #1680 is merged, I was wondering if it would be alright to use this PR to work on the 4 ergonomic wrappers I've implemented (GetEnv()
, SetEnv()
, GetCurDir()
, SetCurDir()
). Is this sufficiently small enough? Or should I split this into two PRs- one for the Env
functions and the other for CurDir
?
If it is okay to continue to use this PR, then I plan to rebase this branch onto the current main, squash the commits, and then continue from there.
That sounds good, start with doing that in this PR. Once the changes are in this PR it's possible I might ask for it to be split up into more than one PR for review, but we can start with the assumption that one PR is OK.
b9a3be8
to
0f30078
Compare
I've rebased this branch onto the current main and updated it to disclude the ShellProtocol
definition moved to uefi-raw
. I was wondering if there is any guidance for writing tests. So far, I have done my best to look at other files and imitate the format such as uefi-test-runner/src/proto/media.rs
and uefi-test-runner/src/proto/rng.rs
, but if there is anything more specific I can revise what I have.
910e291
to
42cad4a
Compare
Thanks for the feedback! I have updated get_envs()
to return a Vars
struct that implements the Iterator trait. However, I do have a question about my implementation. When checking if a given Char16
is NULL
, I use
Char16::from_u16_unchecked(0)
to represent the NULL
value for the comparison. I saw that one of the suggestions requested to use
Char16::from_u16(0).unwrap()
instead, but I don't see this function implemented for Char16
. Is it alright to use the unchecked variant?
I'll start working on unit tests next! However, for Miri I will need to take some time to read its docs since I'm not familiar.
5661358
to
6b35e5a
Compare
I've updated the PR to use the cstr16!
macro instead of the buffers and also added a unit test for the Vars
struct. Thank you for the tip on mocking the inner value with the Vec
, it was very helpful. Please let me know if the unit test looks alright.
LGTM! Please squash your commits a little to smaller but sensible units, then I think we are good to go
c0b817e
to
a21932f
Compare
Commits have been squashed :)
uefi/src/proto/shell/mod.rs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In #1679 (comment), it was suggested to use the same name as std
: var
. I think we should do that here, for several reasons:
- Consistency in APIs is good; anyone who is familiar with
std::env
will instinctively know how to use this API - Per https://rust-lang.github.io/api-guidelines/naming.html#getter-names-follow-rust-convention-c-getter, Rust APIs generally should not use a
get_
prefix - It's clearer to say you are getting the variable (noun) rather than the environment (which can be a noun, but in "environment variable" it's an adjective).
uefi/src/proto/shell/mod.rs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of date, it returns Vars
now. You can probably drop the Returns
section here though, it's redundent with the top-level description.
uefi/src/proto/shell/mod.rs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Gets an iterator over the names of all environment variables."
uefi/src/proto/shell/mod.rs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think var_names
would be a better name for this function (see comment on get_env
for details). However, even better might be to make it match std
and have Vars
return an iterator over both names and values (and then the method can be named simply vars
). Is that doable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, sounds reasonable. High level abstractions should be close to known interfaces (such as std) and not replicate low-level details
uefi/src/proto/shell/mod.rs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of core::ptr::from_ref
, you can use name.as_ptr()
(and ditto for value below).
uefi/src/proto/shell/mod.rs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: parens not needed around x.as_ptr()
. Might be clearer to write as map_or(ptr::null(), CStr16::as_ptr)
.
uefi/src/proto/shell/mod.rs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
set_current_dir
to match std
uefi/src/proto/shell/mod.rs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here and on other methods, return Result
(https://docs.rs/uefi/latest/uefi/type.Result.html) instead of a raw Status
. This is more convenient for users since it works with ?
.
uefi/src/proto/shell/mod.rs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
set_var
to match std
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the implementation could be simplified, something like this:
fn next(&mut self) -> Option<Self::Item> { let s = unsafe { CStr16::from_ptr(self.inner) }; if s.is_empty() { None } else { self.inner = unsafe { self.inner.add(s.num_chars() + 1) }; Some(s) } }
8e808c2
to
3e9bff8
Compare
Thank you for the feedback. I'm starting to understand the goal of abstracting the lower level implementation in ways that are useful for the user and will do my best to keep this in mind for future pull requests! I've addressed the function call names and return values.
For returning Result
as opposed to Status
, is it alright to simply call the to_result()
method and return?
As for changing Vars
to return both the name and value of the environment variables, is it fine to use something from the alloc
crate like Vec
or Box
for this? I don't think I see a way to handle this without dynamically allocating memory to, at the very least, hold the pointers to the values. Please let me know if you have any suggested approaches.
Thank you for the feedback. I'm starting to understand the goal of abstracting the lower level implementation in ways that are useful for the user
Perfect! Thanks for your patience and valuable contributions :)
For returning Result as opposed to Status, is it alright to simply call the to_result() method and return?
Yes, perfectly fine, and also done in other parts of the code base
As for changing Vars to return both the name and value of the environment variables, is it fine to use something from the alloc crate like Vec or Box for this
Yes, that's fine. So far, we didn't have a clear guidline. IMHO, uefi
in the early days tried to avoid alloc
whenever possible, but over time, alloc
was mandatory for more and more parts of the crate.
Now, IMHO, the guideline is: prevent alloc
when it is easily doable. Otherwise, go with the convenience of alloc
Now, IMHO, the guideline is: prevent
alloc
when it is easily doable. Otherwise, go with the convenience ofalloc
In this case, would be it be okay to simply store the vector of values as one of the fields of Var
? I was thinking of something like:
pub struct Vars<'a> { /// Char16 containing names of environment variables names: *const Char16, /// Vec containing values of environment variables values: Vec::<Option<&'a CStr16>>, /// Current position in vec cur_pos: usize, /// Placeholder to attach a lifetime to `Vars` placeholder: PhantomData<&'a CStr16>, }
In this case, would be it be okay to simply store the vector of values as one of the fields of Var?
Ideally we wouldn't store a Vec
of all env values (this somewhat defeats the purpose of it being an iterator, since it requires extra allocations). I'm a little unclear on the behavior of EFI_SHELL_PROTOCOL.GetEnv()
when null
is passed in -- does it return just the names of variables, or names and values?
I see your point on storing Vec
in the iterator. As for EFI_SHELL_PROTOCOL.GetEnv()
in the case where null
is passed in, only the names of the variables are returned. So for returning the values alongside the variables in the Vars
iterator, it seems that a little extra work has to be done to call and store the results of either get_env()
or var()
for each variable name.
Could Vars store a reference to the protocol, and call get_env in the next function to get each variable value one at a time?
That's quite a clever approach! I've written an initial implementation and did not find any issues. I will push it shortly so we can review it.
fd136bb
to
7542291
Compare
Hi, just getting back to reviewing this. Could you create a new PR with the current_dir/set_current_dir changes, so that this one just has var/vars/set_var? Getting the per-PR changes smaller will help me review it more quickly.
Sure thing. I'll need a bit to read through and separate the changes, but as soon as I do I'll push the changes and open the new PR.
7542291
to
6e70d4f
Compare
I've separated the CurDir
functions into #1740 and removed their respective changes from this PR. Please let me know if there's anything I've missed!
This commit implements wrappers for the following EFI Shell Protocol functions: set_env() and get_env()
This commit includes tests for the following EFI Shell Protocol functions: get_env() and set_env()
The UEFI get_env() implementation is used for getting individual environment variable values and also environment variable names. This is not intuitive so this commit splits the function into two dedicated ones: one for accessing values and the other for accessing names. Co-authored-by: Philipp Schuster <phip1611@gmail.com>
Co-authored-by: Philipp Schuster <phip1611@gmail.com>
...h standard convention
6e70d4f
to
116701e
Compare
This PR will implement the wrappers for EFI Shell Protocol functions to address #448 . This PR does branch off of the changes made in #596 so big thanks to @timrobertsdev for laying the groundwork!
Currently, I've implemented wrappers and wrote tests for
GetEnv()
,SetEnv()
,GetCurDir()
andSetCurDir()
. My plan is to first write tests for and finish implementingExecute()
,CloseFile()
,CreateFile()
,FindFiles()
,FindFilesInDir()
, andGetFileSize()
since they have already been started. Then I can implement the rest in either this PR or subsequent follow up PRs (whichever is preferable).I do have some questions about my
get_env()
implementation. UEFI'sGetEnv()
returns the value of a variable if its name is specified. However if its name is not specified, it returns all known variable names in a*const Char16
where the names are delimited by aNULL
and the end of the list is terminated by a doubleNULL
.My initial approach was to parse the
*const Char16
into aVec
in this case. I wrapped the return value in an Enum that can be unpacked into a single&CStr16
or into a vector of&CStr16
s depending on which is expected. Is this approach okay? I was concerned that if I simply returned a&CStr16
in the "name list" case that the true size of the string wouldn't be visible since the names are separated byNULL
. Also, is it okay for me to usealloc::vec::Vec
? I saw that a lot of other protocols don't use anyVec
at all so I'm not clear on if there are some restrictions for doing so.Checklist