|
23 | 23 | #include "xfs_trans_space.h"
|
24 | 24 | #include "xfs_parent.h"
|
25 | 25 | #include "xfs_ag.h"
|
| 26 | +#include "xfs_ialloc.h" |
26 | 27 |
|
27 | 28 | const struct xfs_name xfs_name_dotdot = {
|
28 | 29 | .name = (const unsigned char *)"..",
|
@@ -1080,3 +1081,229 @@ xfs_dir_exchange_children(
|
1080 | 1081 |
|
1081 | 1082 | return 0;
|
1082 | 1083 | }
|
| 1084 | + |
| 1085 | +/* |
| 1086 | + * Given an entry (@src_name, @src_ip) in directory @src_dp, make the entry |
| 1087 | + * @target_name in directory @target_dp point to @src_ip and remove the |
| 1088 | + * original entry, cleaning up everything left behind. |
| 1089 | + * |
| 1090 | + * Cleanup involves dropping a link count on @target_ip, and either removing |
| 1091 | + * the (@src_name, @src_ip) entry from @src_dp or simply replacing the entry |
| 1092 | + * with (@src_name, @wip) if a whiteout inode @wip is supplied. |
| 1093 | + * |
| 1094 | + * All inodes must have the ILOCK held. We assume that if @src_ip is a |
| 1095 | + * directory then its '..' doesn't already point to @target_dp, and that @wip |
| 1096 | + * is a freshly allocated whiteout. |
| 1097 | + */ |
| 1098 | +int |
| 1099 | +xfs_dir_rename_children( |
| 1100 | + struct xfs_trans *tp, |
| 1101 | + struct xfs_dir_update *du_src, |
| 1102 | + struct xfs_dir_update *du_tgt, |
| 1103 | + unsigned int spaceres, |
| 1104 | + struct xfs_dir_update *du_wip) |
| 1105 | +{ |
| 1106 | + struct xfs_mount *mp = tp->t_mountp; |
| 1107 | + struct xfs_inode *src_dp = du_src->dp; |
| 1108 | + const struct xfs_name *src_name = du_src->name; |
| 1109 | + struct xfs_inode *src_ip = du_src->ip; |
| 1110 | + struct xfs_inode *target_dp = du_tgt->dp; |
| 1111 | + const struct xfs_name *target_name = du_tgt->name; |
| 1112 | + struct xfs_inode *target_ip = du_tgt->ip; |
| 1113 | + bool new_parent = (src_dp != target_dp); |
| 1114 | + bool src_is_directory; |
| 1115 | + int error; |
| 1116 | + |
| 1117 | + src_is_directory = S_ISDIR(VFS_I(src_ip)->i_mode); |
| 1118 | + |
| 1119 | + /* |
| 1120 | + * Check for expected errors before we dirty the transaction |
| 1121 | + * so we can return an error without a transaction abort. |
| 1122 | + */ |
| 1123 | + if (target_ip == NULL) { |
| 1124 | + /* |
| 1125 | + * If there's no space reservation, check the entry will |
| 1126 | + * fit before actually inserting it. |
| 1127 | + */ |
| 1128 | + if (!spaceres) { |
| 1129 | + error = xfs_dir_canenter(tp, target_dp, target_name); |
| 1130 | + if (error) |
| 1131 | + return error; |
| 1132 | + } |
| 1133 | + } else { |
| 1134 | + /* |
| 1135 | + * If target exists and it's a directory, check that whether |
| 1136 | + * it can be destroyed. |
| 1137 | + */ |
| 1138 | + if (S_ISDIR(VFS_I(target_ip)->i_mode) && |
| 1139 | + (!xfs_dir_isempty(target_ip) || |
| 1140 | + (VFS_I(target_ip)->i_nlink > 2))) |
| 1141 | + return -EEXIST; |
| 1142 | + } |
| 1143 | + |
| 1144 | + /* |
| 1145 | + * Directory entry creation below may acquire the AGF. Remove |
| 1146 | + * the whiteout from the unlinked list first to preserve correct |
| 1147 | + * AGI/AGF locking order. This dirties the transaction so failures |
| 1148 | + * after this point will abort and log recovery will clean up the |
| 1149 | + * mess. |
| 1150 | + * |
| 1151 | + * For whiteouts, we need to bump the link count on the whiteout |
| 1152 | + * inode. After this point, we have a real link, clear the tmpfile |
| 1153 | + * state flag from the inode so it doesn't accidentally get misused |
| 1154 | + * in future. |
| 1155 | + */ |
| 1156 | + if (du_wip->ip) { |
| 1157 | + struct xfs_perag *pag; |
| 1158 | + |
| 1159 | + ASSERT(VFS_I(du_wip->ip)->i_nlink == 0); |
| 1160 | + |
| 1161 | + pag = xfs_perag_get(mp, XFS_INO_TO_AGNO(mp, du_wip->ip->i_ino)); |
| 1162 | + error = xfs_iunlink_remove(tp, pag, du_wip->ip); |
| 1163 | + xfs_perag_put(pag); |
| 1164 | + if (error) |
| 1165 | + return error; |
| 1166 | + |
| 1167 | + xfs_bumplink(tp, du_wip->ip); |
| 1168 | + } |
| 1169 | + |
| 1170 | + /* |
| 1171 | + * Set up the target. |
| 1172 | + */ |
| 1173 | + if (target_ip == NULL) { |
| 1174 | + /* |
| 1175 | + * If target does not exist and the rename crosses |
| 1176 | + * directories, adjust the target directory link count |
| 1177 | + * to account for the ".." reference from the new entry. |
| 1178 | + */ |
| 1179 | + error = xfs_dir_createname(tp, target_dp, target_name, |
| 1180 | + src_ip->i_ino, spaceres); |
| 1181 | + if (error) |
| 1182 | + return error; |
| 1183 | + |
| 1184 | + xfs_trans_ichgtime(tp, target_dp, |
| 1185 | + XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG); |
| 1186 | + |
| 1187 | + if (new_parent && src_is_directory) { |
| 1188 | + xfs_bumplink(tp, target_dp); |
| 1189 | + } |
| 1190 | + } else { /* target_ip != NULL */ |
| 1191 | + /* |
| 1192 | + * Link the source inode under the target name. |
| 1193 | + * If the source inode is a directory and we are moving |
| 1194 | + * it across directories, its ".." entry will be |
| 1195 | + * inconsistent until we replace that down below. |
| 1196 | + * |
| 1197 | + * In case there is already an entry with the same |
| 1198 | + * name at the destination directory, remove it first. |
| 1199 | + */ |
| 1200 | + error = xfs_dir_replace(tp, target_dp, target_name, |
| 1201 | + src_ip->i_ino, spaceres); |
| 1202 | + if (error) |
| 1203 | + return error; |
| 1204 | + |
| 1205 | + xfs_trans_ichgtime(tp, target_dp, |
| 1206 | + XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG); |
| 1207 | + |
| 1208 | + /* |
| 1209 | + * Decrement the link count on the target since the target |
| 1210 | + * dir no longer points to it. |
| 1211 | + */ |
| 1212 | + error = xfs_droplink(tp, target_ip); |
| 1213 | + if (error) |
| 1214 | + return error; |
| 1215 | + |
| 1216 | + if (src_is_directory) { |
| 1217 | + /* |
| 1218 | + * Drop the link from the old "." entry. |
| 1219 | + */ |
| 1220 | + error = xfs_droplink(tp, target_ip); |
| 1221 | + if (error) |
| 1222 | + return error; |
| 1223 | + } |
| 1224 | + } /* target_ip != NULL */ |
| 1225 | + |
| 1226 | + /* |
| 1227 | + * Remove the source. |
| 1228 | + */ |
| 1229 | + if (new_parent && src_is_directory) { |
| 1230 | + /* |
| 1231 | + * Rewrite the ".." entry to point to the new |
| 1232 | + * directory. |
| 1233 | + */ |
| 1234 | + error = xfs_dir_replace(tp, src_ip, &xfs_name_dotdot, |
| 1235 | + target_dp->i_ino, spaceres); |
| 1236 | + ASSERT(error != -EEXIST); |
| 1237 | + if (error) |
| 1238 | + return error; |
| 1239 | + } |
| 1240 | + |
| 1241 | + /* |
| 1242 | + * We always want to hit the ctime on the source inode. |
| 1243 | + * |
| 1244 | + * This isn't strictly required by the standards since the source |
| 1245 | + * inode isn't really being changed, but old unix file systems did |
| 1246 | + * it and some incremental backup programs won't work without it. |
| 1247 | + */ |
| 1248 | + xfs_trans_ichgtime(tp, src_ip, XFS_ICHGTIME_CHG); |
| 1249 | + xfs_trans_log_inode(tp, src_ip, XFS_ILOG_CORE); |
| 1250 | + |
| 1251 | + /* |
| 1252 | + * Adjust the link count on src_dp. This is necessary when |
| 1253 | + * renaming a directory, either within one parent when |
| 1254 | + * the target existed, or across two parent directories. |
| 1255 | + */ |
| 1256 | + if (src_is_directory && (new_parent || target_ip != NULL)) { |
| 1257 | + |
| 1258 | + /* |
| 1259 | + * Decrement link count on src_directory since the |
| 1260 | + * entry that's moved no longer points to it. |
| 1261 | + */ |
| 1262 | + error = xfs_droplink(tp, src_dp); |
| 1263 | + if (error) |
| 1264 | + return error; |
| 1265 | + } |
| 1266 | + |
| 1267 | + /* |
| 1268 | + * For whiteouts, we only need to update the source dirent with the |
| 1269 | + * inode number of the whiteout inode rather than removing it |
| 1270 | + * altogether. |
| 1271 | + */ |
| 1272 | + if (du_wip->ip) |
| 1273 | + error = xfs_dir_replace(tp, src_dp, src_name, du_wip->ip->i_ino, |
| 1274 | + spaceres); |
| 1275 | + else |
| 1276 | + error = xfs_dir_removename(tp, src_dp, src_name, src_ip->i_ino, |
| 1277 | + spaceres); |
| 1278 | + if (error) |
| 1279 | + return error; |
| 1280 | + |
| 1281 | + xfs_trans_ichgtime(tp, src_dp, XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG); |
| 1282 | + xfs_trans_log_inode(tp, src_dp, XFS_ILOG_CORE); |
| 1283 | + if (new_parent) |
| 1284 | + xfs_trans_log_inode(tp, target_dp, XFS_ILOG_CORE); |
| 1285 | + |
| 1286 | + /* Schedule parent pointer updates. */ |
| 1287 | + if (du_wip->ppargs) { |
| 1288 | + error = xfs_parent_addname(tp, du_wip->ppargs, src_dp, |
| 1289 | + src_name, du_wip->ip); |
| 1290 | + if (error) |
| 1291 | + return error; |
| 1292 | + } |
| 1293 | + |
| 1294 | + if (du_src->ppargs) { |
| 1295 | + error = xfs_parent_replacename(tp, du_src->ppargs, src_dp, |
| 1296 | + src_name, target_dp, target_name, src_ip); |
| 1297 | + if (error) |
| 1298 | + return error; |
| 1299 | + } |
| 1300 | + |
| 1301 | + if (du_tgt->ppargs) { |
| 1302 | + error = xfs_parent_removename(tp, du_tgt->ppargs, target_dp, |
| 1303 | + target_name, target_ip); |
| 1304 | + if (error) |
| 1305 | + return error; |
| 1306 | + } |
| 1307 | + |
| 1308 | + return 0; |
| 1309 | +} |
0 commit comments