diff --git a/Makefile b/Makefile index af733ae6..f88859bc 100644 --- a/Makefile +++ b/Makefile @@ -113,7 +113,7 @@ sysroot-source: sysroot-fsh cp Makefile -t "$(SYSROOT)/src" cp README -t "$(SYSROOT)/src" cp -RT build-aux "$(SYSROOT)/src/build-aux" - (for D in $(MODULES); do (cp -LR $$D -t "$(SYSROOT)/src" && $(MAKE) -C "$(SYSROOT)/src/$$D" clean) || exit $$?; done) + (for D in $(MODULES); do (cp -R $$D -t "$(SYSROOT)/src" && $(MAKE) -C "$(SYSROOT)/src/$$D" clean) || exit $$?; done) .PHONY: sysroot-ports sysroot-ports: sysroot-fsh sysroot-base-headers sysroot-system sysroot-source diff --git a/ext/extfs.cpp b/ext/extfs.cpp index 287fe5df..67e681dc 100644 --- a/ext/extfs.cpp +++ b/ext/extfs.cpp @@ -202,6 +202,14 @@ bool RespondRead(int svr, int chl, const uint8_t* buf, size_t count) RespondData(svr, chl, buf, count); } +bool RespondReadlink(int svr, int chl, const uint8_t* buf, size_t count) +{ + struct fsm_resp_readlink body; + body.targetlen = count; + return RespondMessage(svr, chl, FSM_RESP_READLINK, &body, sizeof(body)) && + RespondData(svr, chl, buf, count); +} + bool RespondWrite(int svr, int chl, size_t count) { struct fsm_resp_write body; @@ -577,6 +585,59 @@ void HandleLink(int svr, int chl, struct fsm_req_link* msg, Filesystem* fs) inode->Unref(); } +void HandleSymlink(int svr, int chl, struct fsm_req_symlink* msg, Filesystem* fs) +{ + if ( fs->num_inodes <= msg->dirino ) { RespondError(svr, chl, EBADF); return; } + Inode* inode = fs->GetInode((uint32_t) msg->dirino); + if ( !inode ) { RespondError(svr, chl, errno); return; } + + char* dest_raw = (char*) &(msg[1]); + char* dest = (char*) malloc(msg->targetlen + 1); + if ( !dest ) + { + RespondError(svr, chl, errno); + inode->Unref(); + return; + } + memcpy(dest, dest_raw, msg->namelen); + dest[msg->targetlen] = '\0'; + + char* path_raw = (char*) dest_raw + msg->targetlen; + char* path = (char*) malloc(msg->namelen + 1); + if ( !path ) + { + RespondError(svr, chl, errno); + inode->Unref(); + return; + } + memcpy(path, path_raw, msg->namelen); + path[msg->namelen] = '\0'; + + if ( inode->Symlink(path, dest) ) + RespondSuccess(svr, chl); + else + RespondError(svr, chl, errno); + + free(path); + free(dest); + inode->Unref(); +} + +void HandleReadlink(int svr, int chl, struct fsm_req_readlink* msg, Filesystem* fs) +{ + if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } + Inode* inode = fs->GetInode((uint32_t) msg->ino); + if ( !inode ) { RespondError(svr, chl, errno); return; } + if ( !EXT2_S_ISLNK(inode->Mode()) ) { RespondError(svr, chl, EINVAL); return; } + size_t count = inode->Size(); + uint8_t* buf = (uint8_t*) malloc(count); + if ( !buf ) { inode->Unref(); RespondError(svr, chl, errno); return; } + ssize_t amount = inode->ReadAt(buf, count, 0); + RespondReadlink(svr, chl, buf, amount); + inode->Unref(); + free(buf); +} + void HandleRename(int svr, int chl, struct fsm_req_rename* msg, Filesystem* fs) { if ( fs->num_inodes <= msg->olddirino ) { RespondError(svr, chl, EBADF); return; } @@ -629,6 +690,8 @@ void HandleIncomingMessage(int svr, int chl, struct fsm_msg_header* hdr, handlers[FSM_REQ_RMDIR] = (handler_t) HandleRemoveDir; handlers[FSM_REQ_UNLINK] = (handler_t) HandleUnlink; handlers[FSM_REQ_LINK] = (handler_t) HandleLink; + handlers[FSM_REQ_SYMLINK] = (handler_t) HandleSymlink; + handlers[FSM_REQ_READLINK] = (handler_t) HandleReadlink; handlers[FSM_REQ_RENAME] = (handler_t) HandleRename; // TODO: symlink // TODO: readlink diff --git a/ext/inode.cpp b/ext/inode.cpp index 07b3a061..9f4fd43a 100644 --- a/ext/inode.cpp +++ b/ext/inode.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -289,6 +290,12 @@ bool Inode::FreeIndirect(uint64_t from, uint64_t offset, uint32_t block_id, void Inode::Truncate(uint64_t new_size) { + if ( 0 < Size() && Size() <= 60 && !data->i_blocks ) + { + fprintf(stderr, "extfs: Truncating optimized symlinks is not implemented!\n"); + return; + } + // TODO: Enforce a filesize limit! uint64_t old_size = Size(); SetSize(new_size); @@ -638,7 +645,7 @@ Inode* Inode::Unlink(const char* elem, bool directories, bool force) ssize_t Inode::ReadAt(uint8_t* buf, size_t s_count, off_t o_offset) { - if ( !EXT2_S_ISREG(Mode()) ) + if ( !EXT2_S_ISREG(Mode()) && !EXT2_S_ISLNK(Mode()) ) return errno = EISDIR, -1; if ( o_offset < 0 ) return errno = EINVAL, -1; @@ -652,6 +659,15 @@ ssize_t Inode::ReadAt(uint8_t* buf, size_t s_count, off_t o_offset) return 0; if ( file_size - offset < count ) count = file_size - offset; + // TODO: This case also needs to be handled in SetSize, Truncate, WriteAt, + // and so on. + if ( 0 < file_size && file_size <= 60 && !data->i_blocks ) + { + assert(count <= 60); + unsigned char* block_data = (unsigned char*) &(data->i_block[0]); + memcpy(buf, block_data + offset, count); + return (ssize_t) count; + } while ( sofar < count ) { uint64_t block_id = offset / filesystem->block_size; @@ -671,7 +687,7 @@ ssize_t Inode::ReadAt(uint8_t* buf, size_t s_count, off_t o_offset) ssize_t Inode::WriteAt(const uint8_t* buf, size_t s_count, off_t o_offset) { - if ( !EXT2_S_ISREG(Mode()) ) + if ( !EXT2_S_ISREG(Mode()) && !EXT2_S_ISLNK(Mode()) ) return errno = EISDIR, -1; if ( o_offset < 0 ) return errno = EINVAL, -1; @@ -684,6 +700,11 @@ ssize_t Inode::WriteAt(const uint8_t* buf, size_t s_count, off_t o_offset) uint64_t end_at = offset + count; if ( offset < end_at ) /* TODO: Overflow! off_t overflow? */{}; + if ( 0 < file_size && file_size <= 60 && !data->i_blocks ) + { + fprintf(stderr, "extfs: Writing to optimized symlinks is not implemented!\n"); + return errno = EROFS, -1; + } if ( file_size < end_at ) Truncate(end_at); while ( sofar < count ) @@ -744,6 +765,44 @@ bool Inode::Rename(Inode* olddir, const char* oldname, const char* newname) return true; } +bool Inode::Symlink(const char* elem, const char* dest) +{ + // TODO: Preferred block group! + uint32_t result_inode_id = filesystem->AllocateInode(); + if ( !result_inode_id ) + return NULL; + + Inode* result = filesystem->GetInode(result_inode_id); + memset(result->data, 0, sizeof(*result->data)); + result->SetMode((0777 & S_SETABLE) | EXT2_S_IFLNK); + + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + result->data->i_atime = now.tv_sec; + result->data->i_ctime = now.tv_sec; + result->data->i_mtime = now.tv_sec; + // TODO: Set all the other inode properties! + + if ( result->WriteAt((const uint8_t*) dest, strlen(dest), 0) < 0 ) + { + error: + memset(result->data, 0, sizeof(*result->data)); + // TODO: dtime + result->Unref(); + filesystem->FreeInode(result_inode_id); + return false; + } + + if ( !Link(elem, result, false) ) + { + result->Truncate(0); + goto error; + } + + result->Unref(); + return true; +} + Inode* Inode::CreateDirectory(const char* path, mode_t mode) { // TODO: Preferred block group! @@ -753,7 +812,7 @@ Inode* Inode::CreateDirectory(const char* path, mode_t mode) Inode* result = filesystem->GetInode(result_inode_id); memset(result->data, 0, sizeof(*result->data)); - result->SetMode((mode & S_SETABLE) | S_IFDIR); + result->SetMode((mode & S_SETABLE) | EXT2_S_IFDIR); // Increase the directory count statistics. uint32_t group_id = (result->inode_id - 1) / filesystem->sb->s_inodes_per_group; diff --git a/ext/inode.h b/ext/inode.h index 283570b7..879bf3da 100644 --- a/ext/inode.h +++ b/ext/inode.h @@ -59,6 +59,7 @@ public: Block* GetBlockFromTable(Block* table, uint32_t index); Inode* Open(const char* elem, int flags, mode_t mode); bool Link(const char* elem, Inode* dest, bool directories); + bool Symlink(const char* elem, const char* dest); Inode* Unlink(const char* elem, bool directories, bool force=false); ssize_t ReadAt(uint8_t* buffer, size_t count, off_t offset); ssize_t WriteAt(const uint8_t* buffer, size_t count, off_t offset); diff --git a/kernel/descriptor.cpp b/kernel/descriptor.cpp index 3a2c0dd0..25eb5124 100644 --- a/kernel/descriptor.cpp +++ b/kernel/descriptor.cpp @@ -385,6 +385,11 @@ Ref Descriptor::open(ioctx_t* ctx, const char* filename, int flags, if ( !IsSaneFlagModeCombination(flags, mode) ) return errno = EINVAL, Ref(); + char* filename_mine = NULL; + + size_t symlink_iteration = 0; + const size_t MAX_SYMLINK_ITERATION = 20; + Ref desc(this); while ( filename[0] ) { @@ -393,7 +398,7 @@ Ref Descriptor::open(ioctx_t* ctx, const char* filename, int flags, if ( filename[0] == '/' ) { if ( !S_ISDIR(desc->type) ) - return errno = ENOTDIR, Ref(); + return delete[] filename_mine, errno = ENOTDIR, Ref(); filename++; continue; } @@ -402,7 +407,7 @@ Ref Descriptor::open(ioctx_t* ctx, const char* filename, int flags, size_t slashpos = strcspn(filename, "/"); char* elem = String::Substring(filename, 0, slashpos); if ( !elem ) - return Ref(); + return delete[] filename_mine, Ref(); // Decide how to open the next element in the path. bool lastelem = IsLastPathElement(filename); @@ -414,12 +419,69 @@ Ref Descriptor::open(ioctx_t* ctx, const char* filename, int flags, delete[] elem; if ( !next ) - return Ref(); + return delete[] filename_mine, Ref(); + + filename += slashpos; + + bool want_the_symlink_itself = lastelem && (flags & O_SYMLINK_NOFOLLOW); + if ( S_ISLNK(next->type) && !want_the_symlink_itself ) + { + if ( (flags & O_NOFOLLOW) && lastelem ) + return delete[] filename_mine, errno = ELOOP, Ref(); + + if ( symlink_iteration++ == MAX_SYMLINK_ITERATION ) + return delete[] filename_mine, errno = ELOOP, Ref(); + + ioctx_t kctx; + SetupKernelIOCtx(&kctx); + + struct stat st; + if ( next->stat(&kctx, &st) < 0 ) + return delete[] filename_mine, Ref(); + assert(0 <= st.st_size); + + if ( (uintmax_t) SIZE_MAX <= (uintmax_t) st.st_size ) + return delete[] filename_mine, Ref(); + + size_t linkpath_length = (size_t) st.st_size; + char* linkpath = new char[linkpath_length + 1]; + if ( !linkpath ) + return delete[] filename_mine, Ref(); + + ssize_t linkpath_ret = next->readlink(&kctx, linkpath, linkpath_length); + if ( linkpath_ret < 0 ) + return delete[] linkpath, delete[] filename_mine, Ref(); + linkpath[linkpath_length] = '\0'; + + linkpath_length = strlen(linkpath); + if ( linkpath_length == 0 ) + return delete[] linkpath, delete[] filename_mine, + errno = ENOENT, Ref(); + bool link_from_root = linkpath[0] == '/'; + + // Either filename is the empty string or starts with a slash. + size_t filename_length = strlen(filename); + // TODO: Avoid overflow here. + size_t new_filename_length = linkpath_length + filename_length; + char* new_filename = new char[new_filename_length + 1]; + if ( !new_filename ) + return delete[] linkpath, delete[] filename_mine, + errno = ENOENT, Ref(); + stpcpy(stpcpy(new_filename, linkpath), filename); + delete[] filename_mine; + filename = filename_mine = new_filename; + + if ( link_from_root ) + desc = CurrentProcess()->GetRoot(); + + continue; + } desc = next; - filename += slashpos; } + delete[] filename_mine; + // Abort the open if the user wanted a directory but this wasn't. if ( flags & O_DIRECTORY && !S_ISDIR(desc->type) ) return errno = ENOTDIR, Ref(); diff --git a/kernel/fs/kram.cpp b/kernel/fs/kram.cpp index f558695b..e4f13c41 100644 --- a/kernel/fs/kram.cpp +++ b/kernel/fs/kram.cpp @@ -51,14 +51,15 @@ namespace Sortix { namespace KRAMFS { -File::File(dev_t dev, ino_t ino, uid_t owner, gid_t group, mode_t mode) +File::File(InodeType inode_type, mode_t type, dev_t dev, ino_t ino, uid_t owner, + gid_t group, mode_t mode) { - inode_type = INODE_TYPE_FILE; + this->inode_type = inode_type; if ( !dev ) dev = (dev_t) this; if ( !ino ) ino = (ino_t) this; - this->type = S_IFREG; + this->type = type; this->stat_uid = owner; this->stat_gid = group; this->stat_mode = (mode & S_SETABLE) | this->type; @@ -106,6 +107,15 @@ ssize_t File::pwrite(ioctx_t* ctx, const uint8_t* src, size_t count, off_t off) return ret; } +ssize_t File::readlink(ioctx_t* ctx, char* buf, size_t bufsize) +{ + if ( !S_ISLNK(type) ) + return errno = EINVAL, -1; + if ( (size_t) SSIZE_MAX < bufsize ) + bufsize = SSIZE_MAX; + return fcache.pread(ctx, (uint8_t*) buf, bufsize, 0); +} + Dir::Dir(dev_t dev, ino_t ino, uid_t owner, gid_t group, mode_t mode) { inode_type = INODE_TYPE_DIR; @@ -234,7 +244,8 @@ Ref Dir::open(ioctx_t* ctx, const char* filename, int flags, mode_t mode) } if ( !(flags & O_CREATE) ) return errno = ENOENT, Ref(NULL); - Ref file(new File(dev, 0, ctx->uid, ctx->gid, mode)); + Ref file(new File(INODE_TYPE_FILE, S_IFREG, dev, 0, ctx->uid, + ctx->gid, mode)); if ( !file ) return Ref(NULL); if ( !AddChild(filename, file) ) @@ -373,15 +384,36 @@ int Dir::unlink_raw(ioctx_t* /*ctx*/, const char* filename) return 0; } -int Dir::symlink(ioctx_t* /*ctx*/, const char* oldname, const char* filename) +int Dir::symlink(ioctx_t* ctx, const char* oldname, const char* filename) { ScopedLock lock(&dir_lock); if ( shut_down ) return errno = ENOENT, -1; - (void) oldname; - (void) filename; - errno = ENOSYS; - return -1; + if ( FindChild(filename) != SIZE_MAX ) + return errno = EEXIST, -1; + Ref file(new File(INODE_TYPE_SYMLINK, S_IFLNK, dev, 0, ctx->uid, + ctx->gid, 0777)); + if ( !file ) + return Ref(NULL); + ioctx_t kctx; + SetupKernelIOCtx(&kctx); + size_t oldname_length = strlen(oldname); + size_t so_far = 0; + while ( so_far < oldname_length ) + { +#if OFF_MAX < SIZE_MAX + if ( (uintmax_t) OFF_MAX < (uintmax_t) so_far ) + return Ref(NULL); +#endif + ssize_t amount = file->pwrite(&kctx, (const uint8_t*) oldname + so_far, + oldname_length - so_far, (off_t) so_far); + if ( amount <= 0 ) + return Ref(NULL); + so_far += (size_t) amount; + } + if ( !AddChild(filename, file) ) + return Ref(NULL); + return 0; } int Dir::rename_here(ioctx_t* ctx, Ref from, const char* oldname, diff --git a/kernel/fs/kram.h b/kernel/fs/kram.h index a5be7cdf..aa25de47 100644 --- a/kernel/fs/kram.h +++ b/kernel/fs/kram.h @@ -41,7 +41,8 @@ struct DirEntry class File : public AbstractInode { public: - File(dev_t dev, ino_t ino, uid_t owner, gid_t group, mode_t mode); + File(InodeType inode_type, mode_t type, dev_t dev, ino_t ino, uid_t owner, + gid_t group, mode_t mode); virtual ~File(); virtual int truncate(ioctx_t* ctx, off_t length); virtual off_t lseek(ioctx_t* ctx, off_t offset, int whence); @@ -49,6 +50,7 @@ public: off_t off); virtual ssize_t pwrite(ioctx_t* ctx, const uint8_t* buf, size_t count, off_t off); + virtual ssize_t readlink(ioctx_t* ctx, char* buf, size_t bufsiz); private: FileCache fcache; diff --git a/kernel/include/sortix/kernel/inode.h b/kernel/include/sortix/kernel/inode.h index 7f6360b8..dfbf2535 100644 --- a/kernel/include/sortix/kernel/inode.h +++ b/kernel/include/sortix/kernel/inode.h @@ -122,6 +122,7 @@ enum InodeType INODE_TYPE_STREAM, INODE_TYPE_TTY, INODE_TYPE_DIR, + INODE_TYPE_SYMLINK, }; class AbstractInode : public Inode diff --git a/kernel/initrd.cpp b/kernel/initrd.cpp index b1399d9f..c4508ab6 100644 --- a/kernel/initrd.cpp +++ b/kernel/initrd.cpp @@ -260,38 +260,22 @@ static bool ExtractDir(struct initrd_context* ctx, initrd_inode_t* inode, Refmode) ) { size_t filesize; uint8_t* data = initrd_inode_get_data(ctx, child, &filesize); if ( !data ) return false; - char* dest_path = new char[filesize + 1]; - if ( !dest_path ) + char* oldname = new char[filesize + 1]; + memcpy(oldname, data, filesize); + oldname[filesize] = '\0'; + int ret = dir->symlink(&ctx->ioctx, oldname, name); + delete[] oldname; + if ( ret < 0 ) return false; - memcpy(dest_path, data, filesize); - dest_path[filesize] = '\0'; - // TODO: Currently only symbolic links to files inside the same - // directory are supported when converted to hardlinks. - if ( !strchr(dest_path, '/') ) - { - if ( Ref dest = dir->open(&ctx->ioctx, dest_path, O_READ, 0) ) - dir->link(&ctx->ioctx, name, dest); - } - delete[] dest_path; + Ref desc = dir->open(&ctx->ioctx, name, O_READ | O_SYMLINK_NOFOLLOW, 0); + if ( desc ) + ExtractNode(ctx, child, desc); ctx->amount_extracted += child->size; initrd_progress(ctx); } diff --git a/kernel/io.cpp b/kernel/io.cpp index bf9364f1..0bf45189 100644 --- a/kernel/io.cpp +++ b/kernel/io.cpp @@ -182,7 +182,7 @@ static int sys_openat(int dirfd, const char* path, int flags, mode_t mode) // TODO: This is a hack! Stat the file in some manner and check permissions. static int sys_faccessat(int dirfd, const char* path, int /*mode*/, int flags) { - if ( flags & (AT_SYMLINK_NOFOLLOW) ) + if ( flags & ~(AT_SYMLINK_NOFOLLOW) ) return errno = EINVAL, -1; char* pathcopy = GetStringFromUser(path); if ( !pathcopy ) @@ -567,32 +567,26 @@ static int sys_symlinkat(const char* oldpath, int newdirfd, const char* newpath) { ioctx_t ctx; SetupUserIOCtx(&ctx); - char* newpathcopy = GetStringFromUser(newpath); - if ( !newpathcopy ) + char* newpath_copy = GetStringFromUser(newpath); + if ( !newpath_copy ) return -1; - const char* newrelpath = newpathcopy; - Ref newfrom(PrepareLookup(&newrelpath, newdirfd)); - if ( !newfrom ) { delete[] newpathcopy; return -1; } + char* oldpath_copy = GetStringFromUser(oldpath); + if ( !oldpath_copy ) + return delete[] newpath_copy, -1; - char* final_elem; - Ref dir = OpenDirContainingPath(&ctx, newfrom, newpathcopy, - &final_elem); - delete[] newpathcopy; - if ( !dir ) - return -1; + const char* newrel_path = newpath_copy; + Ref newfrom(PrepareLookup(&newrel_path, newdirfd)); + if ( !newfrom ) + return delete[] newpath_copy, -1; - char* oldpathcopy = GetStringFromUser(oldpath); - if ( !oldpathcopy ) { delete[] final_elem; return -1; } + int ret = newfrom->symlink(&ctx, oldpath, newrel_path); - int ret = (errno = EPERM, -1); - - delete[] oldpathcopy; - delete[] final_elem; + delete[] oldpath_copy; + delete[] newpath_copy; return ret; } - static int sys_settermmode(int fd, unsigned mode) { Ref desc = CurrentProcess()->GetDescriptor(fd); @@ -698,8 +692,7 @@ static ssize_t sys_readlinkat(int dirfd, const char* path, char* buf, size_t siz const char* relpath = pathcopy; Ref from = PrepareLookup(&relpath, dirfd); if ( !from ) { delete[] pathcopy; return -1; } - // TODO: Open the symbolic link, instead of what it points to! - Ref desc = from->open(&ctx, relpath, O_READ); + Ref desc = from->open(&ctx, relpath, O_READ | O_SYMLINK_NOFOLLOW); delete[] pathcopy; if ( !desc ) return -1; diff --git a/utils/ln.cpp b/utils/ln.cpp index f5f76e58..49374bce 100644 --- a/utils/ln.cpp +++ b/utils/ln.cpp @@ -110,11 +110,7 @@ int main(int argc, char* argv[]) if ( force ) unlink(newname); - if ( symbolic ) - fprintf(stderr, "%s: symbolic links are not supported, creating hard " - "link instead\n", argv0); - - int ret = link(oldname, newname); + int ret = (symbolic ? symlink : link)(oldname, newname); if ( ret == 0 ) { if ( verbose )