fs: ext4: Add support for the creation of symbolic links

Re-use the functions used to write/create a file, to support creation of a
symbolic link.
The difference with a regular file are small:
- The inode mode is flagged with S_IFLNK instead of S_IFREG
- The ext2_dirent's filetype is FILETYPE_SYMLINK instead of FILETYPE_REG
- Instead of storing the content of a file in allocated blocks, the path
to the target is stored. And if the target's path is short enough, no block
is allocated and the target's path is stored in ext2_inode.b.symlink

As with regulars files, if a file/symlink with the same name exits, it is
unlinked first and then re-created.

Signed-off-by: Jean-Jacques Hiblot <jjhiblot@ti.com>
Reviewed-by: Tom Rini <trini@konsulko.com>
[trini: Fix ext4 env code]
Signed-off-by: Tom Rini <trini@konsulko.com>
diff --git a/fs/ext4/ext4_common.c b/fs/ext4/ext4_common.c
index e912378..59ad6c8 100644
--- a/fs/ext4/ext4_common.c
+++ b/fs/ext4/ext4_common.c
@@ -608,7 +608,7 @@
 		dir->direntlen = cpu_to_le16(fs->blksz - totalbytes);
 
 	dir->namelen = strlen(filename);
-	dir->filetype = FILETYPE_REG;	/* regular file */
+	dir->filetype = file_type;
 	temp_dir = (char *)dir;
 	temp_dir = temp_dir + sizeof(struct ext2_dirent);
 	memcpy(temp_dir, filename, strlen(filename));
diff --git a/fs/ext4/ext4_write.c b/fs/ext4/ext4_write.c
index b5b7ee8..504d23a 100644
--- a/fs/ext4/ext4_write.c
+++ b/fs/ext4/ext4_write.c
@@ -465,6 +465,15 @@
 	if (le32_to_cpu(inode.size) % fs->blksz)
 		no_blocks++;
 
+	/*
+	 * special case for symlinks whose target are small enough that
+	 *it fits in struct ext2_inode.b.symlink: no block had been allocated
+	 */
+	if ((le16_to_cpu(inode.mode) & S_IFLNK) &&
+	    le32_to_cpu(inode.size) <= sizeof(inode.b.symlink)) {
+		no_blocks = 0;
+	}
+
 	if (le32_to_cpu(inode.flags) & EXT4_EXTENTS_FL) {
 		/* FIXME delete extent index blocks, i.e. eh_depth >= 1 */
 		struct ext4_extent_header *eh =
@@ -830,7 +839,7 @@
 }
 
 int ext4fs_write(const char *fname, const char *buffer,
-		 unsigned long sizebytes)
+		 unsigned long sizebytes, int type)
 {
 	int ret = 0;
 	struct ext2_inode *file_inode = NULL;
@@ -853,8 +862,12 @@
 	struct ext2_block_group *bgd = NULL;
 	struct ext_filesystem *fs = get_fs();
 	ALLOC_CACHE_ALIGN_BUFFER(char, filename, 256);
+	bool store_link_in_inode = false;
 	memset(filename, 0x00, 256);
 
+	if (type != FILETYPE_REG && type != FILETYPE_SYMLINK)
+		return -1;
+
 	g_parent_inode = zalloc(fs->inodesz);
 	if (!g_parent_inode)
 		goto fail;
@@ -892,8 +905,16 @@
 		if (ret)
 			goto fail;
 	}
-	/* calucalate how many blocks required */
-	bytes_reqd_for_file = sizebytes;
+
+	/* calculate how many blocks required */
+	if (type == FILETYPE_SYMLINK &&
+	    sizebytes <= sizeof(file_inode->b.symlink)) {
+		store_link_in_inode = true;
+		bytes_reqd_for_file = 0;
+	} else {
+		bytes_reqd_for_file = sizebytes;
+	}
+
 	blks_reqd_for_file = lldiv(bytes_reqd_for_file, fs->blksz);
 	if (do_div(bytes_reqd_for_file, fs->blksz) != 0) {
 		blks_reqd_for_file++;
@@ -906,7 +927,7 @@
 		goto fail;
 	}
 
-	inodeno = ext4fs_update_parent_dentry(filename, FILETYPE_REG);
+	inodeno = ext4fs_update_parent_dentry(filename, type);
 	if (inodeno == -1)
 		goto fail;
 	/* prepare file inode */
@@ -914,14 +935,23 @@
 	if (!inode_buffer)
 		goto fail;
 	file_inode = (struct ext2_inode *)inode_buffer;
-	file_inode->mode = cpu_to_le16(S_IFREG | S_IRWXU |
-	    S_IRGRP | S_IROTH | S_IXGRP | S_IXOTH);
+	file_inode->size = cpu_to_le32(sizebytes);
+	if (type == FILETYPE_SYMLINK) {
+		file_inode->mode = cpu_to_le16(S_IFLNK | S_IRWXU | S_IRWXG |
+					       S_IRWXO);
+		if (store_link_in_inode) {
+			strncpy(file_inode->b.symlink, buffer, sizebytes);
+			sizebytes = 0;
+		}
+	} else {
+		file_inode->mode = cpu_to_le16(S_IFREG | S_IRWXU | S_IRGRP |
+					       S_IROTH | S_IXGRP | S_IXOTH);
+	}
 	/* ToDo: Update correct time */
 	file_inode->mtime = cpu_to_le32(timestamp);
 	file_inode->atime = cpu_to_le32(timestamp);
 	file_inode->ctime = cpu_to_le32(timestamp);
 	file_inode->nlinks = cpu_to_le16(1);
-	file_inode->size = cpu_to_le32(sizebytes);
 
 	/* Allocate data blocks */
 	ext4fs_allocate_blocks(file_inode, blocks_remaining,
@@ -1013,7 +1043,7 @@
 		return -1;
 	}
 
-	ret = ext4fs_write(filename, buf, len);
+	ret = ext4fs_write(filename, buf, len, FILETYPE_REG);
 	if (ret) {
 		printf("** Error ext4fs_write() **\n");
 		goto fail;
@@ -1028,3 +1058,8 @@
 
 	return -1;
 }
+
+int ext4fs_create_link(const char *target, const char *fname)
+{
+	return ext4fs_write(fname, target, strlen(target), FILETYPE_SYMLINK);
+}