diff --git a/config.h b/config.h index 5e0ec03..63dc731 100644 --- a/config.h +++ b/config.h @@ -27,6 +27,7 @@ #define FS_MAX_OPEN_FD 32 #define FS_MAX_FNAME_LEN 11 #define FS_MAX_DIRECTORY_DEPTH 512 +#define FS_MAX_SYMLINK_FOLLOWING_DEPTH 1024 #endif diff --git a/inc/fs.h b/inc/fs.h index 6331f50..22e690a 100644 --- a/inc/fs.h +++ b/inc/fs.h @@ -4,7 +4,8 @@ enum fs_filetype { REGULAR, - DIRECTORY + DIRECTORY, + SYMLINK }; __attribute__((packed)) @@ -73,6 +74,7 @@ int fs_close(void *d); int fs_mkdir(void *d); int fs_rmdir(void *d); int fs_cd(void *d); +int fs_symlink(void *d); int fs_truncate(void *d); int fs_allow_write(void *d); int fs_prohibit_write(void *d); diff --git a/src/cli.c b/src/cli.c index 1e8ca3b..b518448 100644 --- a/src/cli.c +++ b/src/cli.c @@ -29,6 +29,7 @@ static const struct CliCommandEntry cmd[] = { {"mkdir", 1, (enum CliArgType[]) {STR}, fs_mkdir}, {"rmdir", 1, (enum CliArgType[]) {STR}, fs_rmdir}, {"cd", 1, (enum CliArgType[]) {STR}, fs_cd}, + {"symlink", 2, (enum CliArgType[]) {STR, STR}, fs_symlink}, // custom commands {"use", 1, (enum CliArgType[]) {STR}, fs_use}, diff --git a/src/fs.c b/src/fs.c index e540d02..2e8b92c 100644 --- a/src/fs.c +++ b/src/fs.c @@ -190,6 +190,16 @@ static unsigned int find_free_block(void) return 0; } +static unsigned int claim_free_block(void) +{ + int free_block = find_free_block(); + + if (free_block) + mark_used(free_block); + + return free_block; +} + static unsigned int find_free_inode_ptr(void) { unsigned int i = 1; // inode0 always points to root dir, so can't be free @@ -1584,6 +1594,12 @@ int fs_ln(void *d) } } + /* + * TODO: add support for directory as target, e. g.: + * $ create file1 + * $ mkdir dir2 + * $ ln file1 dir2 -> creates hardlink 'dir2/file1' + */ resolve_path(&new_rp, new_path, 1); if (new_rp.parent_inode_ptr < 0) { pr_err("failed to create hard link: no such directory: '%s'\n", new_path); @@ -1615,6 +1631,156 @@ int fs_ln(void *d) return 0; } +int fs_symlink(void *d) +{ + char *target_path = ((char **) d)[0]; + char *link_path = ((char **)d)[1]; + + int target_path_len = strlen(target_path); + + struct resolved_path rp; + resolve_path(&rp, link_path, FOLLOW_LAST_SYMLINK); + + if (rp.parent_inode_ptr < 0) { + pr_err("failed to create symlink: no such directory: '%s'\n", link_path); + return 0; + } + + char link_fname[FS_MAX_FNAME_LEN+1]; + unsigned int dir_inode_ptr; + + if (rp.target_inode_ptr >= 0) { + { + struct fs_inode i; + read_block(read_inode_ptr(rp.target_inode_ptr), &i); + + if (i.ftype == REGULAR) { + pr_err("file already exists: '%s'\n", rp.target_fname); + return 0; + } else { + int link_fname_len = extract_basename(target_path, link_fname); + if (link_fname_len > FS_MAX_FNAME_LEN) { + pr_err("new filename too long (%d > %d)\n", link_fname_len, FS_MAX_FNAME_LEN); + return 0; + } + + dir_inode_ptr = rp.target_inode_ptr; + } + } + } else { + int link_fname_len = extract_basename(link_path, link_fname); + if (link_fname_len > FS_MAX_FNAME_LEN) { + pr_err("new filename too long (%d > %d)\n", link_fname_len, FS_MAX_FNAME_LEN); + return 0; + } + + dir_inode_ptr = rp.parent_inode_ptr; + } + + int inode_ptr = find_free_inode_ptr(); + if (!inode_ptr) { + pr_err("no free inode_ptr found\n"); + return 0; + } + + int inode_addr = claim_free_block(); + if (!inode_addr) { + pr_err("no space left on device\n"); + return 0; + } + + write_inode_ptr(inode_ptr, inode_addr); + + int required_block_amount = target_path_len % FS_BLOCK_SIZE + ? target_path_len / FS_BLOCK_SIZE + 1 + : target_path_len / FS_BLOCK_SIZE; + + int inode_blocks[required_block_amount]; + + for (int i = 0; i < required_block_amount; i++) { + inode_blocks[i] = claim_free_block(); + if (!inode_blocks[i]) { + pr_err("no space left on device\n"); + return 0; + } + } + + int bytes_written = 0; + int blocks_written = 0; + + struct fs_inode i; + memset(&i, 0, sizeof(i)); + + i.ftype = SYMLINK; + i.ref_count = 1; + i.size = target_path_len; + + for (int j = 0; j < BLOCK_ADDRESSES_PER_INODE; j++, blocks_written++) { + i.blocks[j] = inode_blocks[blocks_written]; + write_block(inode_addr, &i); + + if (target_path_len - bytes_written < FS_BLOCK_SIZE) { + char data_block[FS_BLOCK_SIZE]; + memset(data_block, 0, sizeof(data_block)); + memcpy(data_block, &(target_path[bytes_written]), target_path_len - bytes_written); + write_block(inode_blocks[blocks_written], data_block); + bytes_written = target_path_len; + } else { + write_block(inode_blocks[blocks_written], &(target_path[bytes_written])); + bytes_written += FS_BLOCK_SIZE; + } + + if (target_path_len == bytes_written) + goto finish_writing; + } + + i.next_extension = claim_free_block(); + if (!i.next_extension) { + pr_err("no space left on device\n"); + return 0; + } + write_block(inode_addr, &i); + + for (unsigned int next_ext = i.next_extension; ; ) { + struct fs_inode_extension ext; + memset(&ext, 0, sizeof(ext)); + + for (int j = 0; j < BLOCK_ADDRESSES_PER_INODE_EXTENSION; j++, blocks_written++) { + ext.blocks[j] = inode_blocks[blocks_written]; + write_block(next_ext, &ext); + + if (target_path_len - bytes_written < FS_BLOCK_SIZE) { + char data_block[FS_BLOCK_SIZE]; + memset(data_block, 0, sizeof(data_block)); + memcpy(data_block, &(target_path[bytes_written]), target_path_len - bytes_written); + write_block(inode_blocks[blocks_written], data_block); + bytes_written = target_path_len; + } else { + write_block(inode_blocks[blocks_written], &(target_path[bytes_written])); + bytes_written += FS_BLOCK_SIZE; + } + + if (target_path_len == bytes_written) + goto finish_writing; + } + + ext.next_extension = claim_free_block(); + if (!ext.next_extension) { + pr_err("no space left on device\n"); + return 0; + } + write_block(next_ext, &ext); + + next_ext = ext.next_extension; + } + +finish_writing: + pr("Symlink '%s' written successfully (%d/%d bytes)\n", link_fname, bytes_written, target_path_len); + + fs_add_fname_to_directory(dir_inode_ptr, inode_ptr, link_fname); + return 0; +} + static int last_significant_slash_position(char *path, int path_len) { @@ -1637,12 +1803,9 @@ static int extract_basename(char *path, char *name) if (fname_len > FS_MAX_FNAME_LEN) { pr_err("filename too long (%d > %d)", fname_len, FS_MAX_FNAME_LEN); - return -1; - } - - // allow the usage of NULL ptr to discard basename string - if (name) + } else if (name) { strcpy(name, &(path[lsp+1])); + } return fname_len; }