LibCore: Add File::read_link() :^)

This is a convenient wrapper around readlink() that hides away the details
of buffers and buffer sizes, and simply returns a String. The best part is it
doesn't rely on PATH_MAX :D

It comes in two versions, for Serenity, where we can pass non-null-terminated
strings to syscalls, and where sys$readlink() returns the total link size, and
for other systems, where we have to copy out the string, and always have to do
two syscalls.
This commit is contained in:
Sergey Bugaev 2020-06-16 21:56:00 +03:00 committed by Andreas Kling
parent 47d83800e1
commit d89843f96f
Notes: sideshowbarker 2024-07-19 05:36:53 +09:00
2 changed files with 74 additions and 0 deletions

View file

@ -24,6 +24,9 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef __serenity__
# include <Kernel/Syscall.h>
#endif
#include <LibCore/File.h>
#include <errno.h>
#include <fcntl.h>
@ -129,5 +132,75 @@ String File::real_path_for(const String& filename)
return real_path;
}
#ifdef __serenity__
String File::read_link(const StringView& link_path)
{
// First, try using a 64-byte buffer, that ought to be enough for anybody.
char small_buffer[64];
Syscall::SC_readlink_params small_params {
{ link_path.characters_without_null_termination(), link_path.length() },
{ small_buffer, sizeof(small_buffer) }
};
int rc = syscall(SC_readlink, &small_params);
if (rc < 0) {
errno = -rc;
return {};
}
size_t size = rc;
// If the call was successful, the syscall (unlike the LibC wrapper)
// returns the full size of the link. Let's see if our small buffer
// was enough to read the whole link.
if (size <= sizeof(small_buffer))
return { small_buffer, size };
// Nope, but at least now we know the right size.
char* large_buffer_ptr;
auto large_buffer = StringImpl::create_uninitialized(size, large_buffer_ptr);
Syscall::SC_readlink_params large_params {
{ link_path.characters_without_null_termination(), link_path.length() },
{ large_buffer_ptr, (size_t)size }
};
rc = syscall(SC_readlink, &large_params);
if (rc < 0) {
errno = -rc;
return {};
}
size_t new_size = rc;
if (new_size == size)
return { *large_buffer };
// If we're here, the symlink has changed while we were looking at it.
// If it became shorter, our buffer is valid, we just have to trim it a bit.
if (new_size < size)
return { large_buffer_ptr, new_size };
// Otherwise, here's not much we can do, unless we want to loop endlessly
// in this case. Let's leave it up to the caller whether to loop.
errno = -EAGAIN;
return {};
}
#else
// This is a sad version for other systems. It has to always make a copy of the
// link path, and to always make two syscalls to get the right size first.
String File::read_link(const StringView& link_path)
{
String link_path_str = link_path;
struct stat statbuf;
int rc = lstat(link_path_str.characters(), &statbuf);
if (rc < 0)
return {};
char* buffer_ptr;
auto buffer = StringImpl::create_uninitialized(statbuf.st_size, buffer_ptr);
rc = readlink(link_path_str.characters(), buffer_ptr, statbuf.st_size);
if (rc < 0)
return {};
// (See above.)
if (rc == statbuf.st_size)
return { *buffer };
return { buffer_ptr, (size_t)rc };
}
#endif
}

View file

@ -47,6 +47,7 @@ public:
static bool exists(const String& filename);
static String real_path_for(const String& filename);
static String read_link(const StringView& link_path);
virtual bool open(IODevice::OpenMode) override;