diff --git a/Libraries/LibCore/File.cpp b/Libraries/LibCore/File.cpp index 90c556a063..d90b82d0f0 100644 --- a/Libraries/LibCore/File.cpp +++ b/Libraries/LibCore/File.cpp @@ -24,6 +24,9 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#ifdef __serenity__ +# include +#endif #include #include #include @@ -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 } diff --git a/Libraries/LibCore/File.h b/Libraries/LibCore/File.h index e02f602ee4..d098fb41fe 100644 --- a/Libraries/LibCore/File.h +++ b/Libraries/LibCore/File.h @@ -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;