/src/e2fsprogs/lib/ext2fs/symlink.c
Line | Count | Source |
1 | | /* |
2 | | * symlink.c --- make a symlink in the filesystem, based on mkdir.c |
3 | | * |
4 | | * Copyright (c) 2012, Intel Corporation. |
5 | | * All Rights Reserved. |
6 | | * |
7 | | * %Begin-Header% |
8 | | * This file may be redistributed under the terms of the GNU Library |
9 | | * General Public License, version 2. |
10 | | * %End-Header% |
11 | | */ |
12 | | |
13 | | #include "config.h" |
14 | | #include <stdio.h> |
15 | | #include <string.h> |
16 | | #if HAVE_UNISTD_H |
17 | | #include <unistd.h> |
18 | | #endif |
19 | | #include <fcntl.h> |
20 | | #include <time.h> |
21 | | #if HAVE_SYS_STAT_H |
22 | | #include <sys/stat.h> |
23 | | #endif |
24 | | #if HAVE_SYS_TYPES_H |
25 | | #include <sys/types.h> |
26 | | #endif |
27 | | |
28 | | #include "ext2_fs.h" |
29 | | #include "ext2fs.h" |
30 | | |
31 | | #ifndef HAVE_STRNLEN |
32 | | /* |
33 | | * Incredibly, libc5 doesn't appear to have strnlen. So we have to |
34 | | * provide our own. |
35 | | */ |
36 | | static int my_strnlen(const char * s, int count) |
37 | | { |
38 | | const char *cp = s; |
39 | | |
40 | | while (count-- && *cp) |
41 | | cp++; |
42 | | return cp - s; |
43 | | } |
44 | | #define strnlen(str, x) my_strnlen((str),(x)) |
45 | | #endif |
46 | | |
47 | | errcode_t ext2fs_symlink(ext2_filsys fs, ext2_ino_t parent, ext2_ino_t ino, |
48 | | const char *name, const char *target) |
49 | 0 | { |
50 | 0 | errcode_t retval; |
51 | 0 | struct ext2_inode inode; |
52 | 0 | ext2_ino_t scratch_ino; |
53 | 0 | blk64_t blk; |
54 | 0 | int fastlink, inlinelink; |
55 | 0 | unsigned int target_len; |
56 | 0 | char *block_buf = 0; |
57 | 0 | int drop_refcount = 0; |
58 | |
|
59 | 0 | EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); |
60 | | |
61 | | /* |
62 | | * The Linux kernel doesn't allow for links longer than a block |
63 | | * (counting the NUL terminator) |
64 | | */ |
65 | 0 | target_len = strnlen(target, fs->blocksize + 1); |
66 | 0 | if (target_len >= fs->blocksize) { |
67 | 0 | retval = EXT2_ET_INVALID_ARGUMENT; |
68 | 0 | goto cleanup; |
69 | 0 | } |
70 | | |
71 | | /* |
72 | | * Allocate a data block for slow links |
73 | | */ |
74 | 0 | retval = ext2fs_get_mem(fs->blocksize, &block_buf); |
75 | 0 | if (retval) |
76 | 0 | goto cleanup; |
77 | 0 | memset(block_buf, 0, fs->blocksize); |
78 | 0 | strncpy(block_buf, target, fs->blocksize); |
79 | |
|
80 | 0 | memset(&inode, 0, sizeof(struct ext2_inode)); |
81 | 0 | fastlink = (target_len < sizeof(inode.i_block)); |
82 | 0 | if (!fastlink) { |
83 | 0 | retval = ext2fs_new_block2(fs, ext2fs_find_inode_goal(fs, ino, |
84 | 0 | &inode, |
85 | 0 | 0), |
86 | 0 | NULL, &blk); |
87 | 0 | if (retval) |
88 | 0 | goto cleanup; |
89 | 0 | } |
90 | | |
91 | | /* |
92 | | * Allocate an inode, if necessary |
93 | | */ |
94 | 0 | if (!ino) { |
95 | 0 | retval = ext2fs_new_inode(fs, parent, LINUX_S_IFLNK | 0755, |
96 | 0 | 0, &ino); |
97 | 0 | if (retval) |
98 | 0 | goto cleanup; |
99 | 0 | } |
100 | | |
101 | | /* |
102 | | * Create the inode structure.... |
103 | | */ |
104 | 0 | inode.i_mode = LINUX_S_IFLNK | 0777; |
105 | 0 | inode.i_uid = inode.i_gid = 0; |
106 | 0 | inode.i_links_count = 1; |
107 | 0 | ext2fs_inode_size_set(fs, &inode, target_len); |
108 | | /* The time fields are set by ext2fs_write_new_inode() */ |
109 | |
|
110 | 0 | inlinelink = !fastlink && ext2fs_has_feature_inline_data(fs->super); |
111 | 0 | if (fastlink) { |
112 | | /* Fast symlinks, target stored in inode */ |
113 | 0 | strcpy((char *)&inode.i_block, target); |
114 | 0 | } else if (inlinelink) { |
115 | | /* Try inserting an inline data symlink */ |
116 | 0 | inode.i_flags |= EXT4_INLINE_DATA_FL; |
117 | 0 | retval = ext2fs_write_new_inode(fs, ino, &inode); |
118 | 0 | if (retval) |
119 | 0 | goto cleanup; |
120 | 0 | retval = ext2fs_inline_data_set(fs, ino, &inode, block_buf, |
121 | 0 | target_len); |
122 | 0 | if (retval) { |
123 | 0 | inode.i_flags &= ~EXT4_INLINE_DATA_FL; |
124 | 0 | inlinelink = 0; |
125 | 0 | goto need_block; |
126 | 0 | } |
127 | 0 | retval = ext2fs_read_inode(fs, ino, &inode); |
128 | 0 | if (retval) |
129 | 0 | goto cleanup; |
130 | 0 | } else { |
131 | 0 | need_block: |
132 | | /* Slow symlinks, target stored in the first block */ |
133 | 0 | ext2fs_iblk_set(fs, &inode, 1); |
134 | 0 | if (ext2fs_has_feature_extents(fs->super)) { |
135 | | /* |
136 | | * The extent bmap is setup after the inode and block |
137 | | * have been written out below. |
138 | | */ |
139 | 0 | inode.i_flags |= EXT4_EXTENTS_FL; |
140 | 0 | } |
141 | 0 | } |
142 | | |
143 | | /* |
144 | | * Write out the inode and inode data block. The inode generation |
145 | | * number is assigned by write_new_inode, which means that the |
146 | | * operations using ino must come after it. |
147 | | */ |
148 | 0 | if (inlinelink) |
149 | 0 | retval = ext2fs_write_inode(fs, ino, &inode); |
150 | 0 | else |
151 | 0 | retval = ext2fs_write_new_inode(fs, ino, &inode); |
152 | 0 | if (retval) |
153 | 0 | goto cleanup; |
154 | | |
155 | 0 | if (!fastlink && !inlinelink) { |
156 | 0 | retval = ext2fs_bmap2(fs, ino, &inode, NULL, BMAP_SET, 0, NULL, |
157 | 0 | &blk); |
158 | 0 | if (retval) |
159 | 0 | goto cleanup; |
160 | | |
161 | 0 | retval = io_channel_write_blk64(fs->io, blk, 1, block_buf); |
162 | 0 | if (retval) |
163 | 0 | goto cleanup; |
164 | 0 | } |
165 | | |
166 | | /* |
167 | | * Update accounting.... |
168 | | */ |
169 | 0 | if (!fastlink && !inlinelink) |
170 | 0 | ext2fs_block_alloc_stats2(fs, blk, +1); |
171 | 0 | ext2fs_inode_alloc_stats2(fs, ino, +1, 0); |
172 | 0 | drop_refcount = 1; |
173 | | |
174 | | /* |
175 | | * Link the symlink into the filesystem hierarchy |
176 | | */ |
177 | 0 | if (name) { |
178 | 0 | retval = ext2fs_lookup(fs, parent, name, strlen(name), 0, |
179 | 0 | &scratch_ino); |
180 | 0 | if (!retval) { |
181 | 0 | retval = EXT2_ET_FILE_EXISTS; |
182 | 0 | goto cleanup; |
183 | 0 | } |
184 | 0 | if (retval != EXT2_ET_FILE_NOT_FOUND) |
185 | 0 | goto cleanup; |
186 | 0 | retval = ext2fs_link(fs, parent, name, ino, EXT2_FT_SYMLINK); |
187 | 0 | if (retval) |
188 | 0 | goto cleanup; |
189 | 0 | } |
190 | 0 | drop_refcount = 0; |
191 | |
|
192 | 0 | cleanup: |
193 | 0 | if (block_buf) |
194 | 0 | ext2fs_free_mem(&block_buf); |
195 | 0 | if (drop_refcount) { |
196 | 0 | if (!fastlink && !inlinelink) |
197 | 0 | ext2fs_block_alloc_stats2(fs, blk, -1); |
198 | 0 | ext2fs_inode_alloc_stats2(fs, ino, -1, 0); |
199 | 0 | } |
200 | 0 | return retval; |
201 | 0 | } |
202 | | |
203 | | /* |
204 | | * Test whether an inode is a fast symlink. |
205 | | * |
206 | | * A fast symlink has its symlink data stored in inode->i_block. |
207 | | */ |
208 | | int ext2fs_is_fast_symlink(struct ext2_inode *inode) |
209 | 0 | { |
210 | 0 | return LINUX_S_ISLNK(inode->i_mode) && EXT2_I_SIZE(inode) && |
211 | 0 | EXT2_I_SIZE(inode) < sizeof(inode->i_block); |
212 | 0 | } |