diff options
Diffstat (limited to 'fs/dcache.c')
-rw-r--r-- | fs/dcache.c | 164 |
1 files changed, 150 insertions, 14 deletions
diff --git a/fs/dcache.c b/fs/dcache.c index 1b4a3a34ec57..17b392a2049e 100644 --- a/fs/dcache.c +++ b/fs/dcache.c | |||
@@ -828,17 +828,19 @@ void d_instantiate(struct dentry *entry, struct inode * inode) | |||
828 | * (or otherwise set) by the caller to indicate that it is now | 828 | * (or otherwise set) by the caller to indicate that it is now |
829 | * in use by the dcache. | 829 | * in use by the dcache. |
830 | */ | 830 | */ |
831 | struct dentry *d_instantiate_unique(struct dentry *entry, struct inode *inode) | 831 | static struct dentry *__d_instantiate_unique(struct dentry *entry, |
832 | struct inode *inode) | ||
832 | { | 833 | { |
833 | struct dentry *alias; | 834 | struct dentry *alias; |
834 | int len = entry->d_name.len; | 835 | int len = entry->d_name.len; |
835 | const char *name = entry->d_name.name; | 836 | const char *name = entry->d_name.name; |
836 | unsigned int hash = entry->d_name.hash; | 837 | unsigned int hash = entry->d_name.hash; |
837 | 838 | ||
838 | BUG_ON(!list_empty(&entry->d_alias)); | 839 | if (!inode) { |
839 | spin_lock(&dcache_lock); | 840 | entry->d_inode = NULL; |
840 | if (!inode) | 841 | return NULL; |
841 | goto do_negative; | 842 | } |
843 | |||
842 | list_for_each_entry(alias, &inode->i_dentry, d_alias) { | 844 | list_for_each_entry(alias, &inode->i_dentry, d_alias) { |
843 | struct qstr *qstr = &alias->d_name; | 845 | struct qstr *qstr = &alias->d_name; |
844 | 846 | ||
@@ -851,19 +853,35 @@ struct dentry *d_instantiate_unique(struct dentry *entry, struct inode *inode) | |||
851 | if (memcmp(qstr->name, name, len)) | 853 | if (memcmp(qstr->name, name, len)) |
852 | continue; | 854 | continue; |
853 | dget_locked(alias); | 855 | dget_locked(alias); |
854 | spin_unlock(&dcache_lock); | ||
855 | BUG_ON(!d_unhashed(alias)); | ||
856 | iput(inode); | ||
857 | return alias; | 856 | return alias; |
858 | } | 857 | } |
858 | |||
859 | list_add(&entry->d_alias, &inode->i_dentry); | 859 | list_add(&entry->d_alias, &inode->i_dentry); |
860 | do_negative: | ||
861 | entry->d_inode = inode; | 860 | entry->d_inode = inode; |
862 | fsnotify_d_instantiate(entry, inode); | 861 | fsnotify_d_instantiate(entry, inode); |
863 | spin_unlock(&dcache_lock); | ||
864 | security_d_instantiate(entry, inode); | ||
865 | return NULL; | 862 | return NULL; |
866 | } | 863 | } |
864 | |||
865 | struct dentry *d_instantiate_unique(struct dentry *entry, struct inode *inode) | ||
866 | { | ||
867 | struct dentry *result; | ||
868 | |||
869 | BUG_ON(!list_empty(&entry->d_alias)); | ||
870 | |||
871 | spin_lock(&dcache_lock); | ||
872 | result = __d_instantiate_unique(entry, inode); | ||
873 | spin_unlock(&dcache_lock); | ||
874 | |||
875 | if (!result) { | ||
876 | security_d_instantiate(entry, inode); | ||
877 | return NULL; | ||
878 | } | ||
879 | |||
880 | BUG_ON(!d_unhashed(result)); | ||
881 | iput(inode); | ||
882 | return result; | ||
883 | } | ||
884 | |||
867 | EXPORT_SYMBOL(d_instantiate_unique); | 885 | EXPORT_SYMBOL(d_instantiate_unique); |
868 | 886 | ||
869 | /** | 887 | /** |
@@ -1235,6 +1253,11 @@ static void __d_rehash(struct dentry * entry, struct hlist_head *list) | |||
1235 | hlist_add_head_rcu(&entry->d_hash, list); | 1253 | hlist_add_head_rcu(&entry->d_hash, list); |
1236 | } | 1254 | } |
1237 | 1255 | ||
1256 | static void _d_rehash(struct dentry * entry) | ||
1257 | { | ||
1258 | __d_rehash(entry, d_hash(entry->d_parent, entry->d_name.hash)); | ||
1259 | } | ||
1260 | |||
1238 | /** | 1261 | /** |
1239 | * d_rehash - add an entry back to the hash | 1262 | * d_rehash - add an entry back to the hash |
1240 | * @entry: dentry to add to the hash | 1263 | * @entry: dentry to add to the hash |
@@ -1244,11 +1267,9 @@ static void __d_rehash(struct dentry * entry, struct hlist_head *list) | |||
1244 | 1267 | ||
1245 | void d_rehash(struct dentry * entry) | 1268 | void d_rehash(struct dentry * entry) |
1246 | { | 1269 | { |
1247 | struct hlist_head *list = d_hash(entry->d_parent, entry->d_name.hash); | ||
1248 | |||
1249 | spin_lock(&dcache_lock); | 1270 | spin_lock(&dcache_lock); |
1250 | spin_lock(&entry->d_lock); | 1271 | spin_lock(&entry->d_lock); |
1251 | __d_rehash(entry, list); | 1272 | _d_rehash(entry); |
1252 | spin_unlock(&entry->d_lock); | 1273 | spin_unlock(&entry->d_lock); |
1253 | spin_unlock(&dcache_lock); | 1274 | spin_unlock(&dcache_lock); |
1254 | } | 1275 | } |
@@ -1386,6 +1407,120 @@ already_unhashed: | |||
1386 | spin_unlock(&dcache_lock); | 1407 | spin_unlock(&dcache_lock); |
1387 | } | 1408 | } |
1388 | 1409 | ||
1410 | /* | ||
1411 | * Prepare an anonymous dentry for life in the superblock's dentry tree as a | ||
1412 | * named dentry in place of the dentry to be replaced. | ||
1413 | */ | ||
1414 | static void __d_materialise_dentry(struct dentry *dentry, struct dentry *anon) | ||
1415 | { | ||
1416 | struct dentry *dparent, *aparent; | ||
1417 | |||
1418 | switch_names(dentry, anon); | ||
1419 | do_switch(dentry->d_name.len, anon->d_name.len); | ||
1420 | do_switch(dentry->d_name.hash, anon->d_name.hash); | ||
1421 | |||
1422 | dparent = dentry->d_parent; | ||
1423 | aparent = anon->d_parent; | ||
1424 | |||
1425 | dentry->d_parent = (aparent == anon) ? dentry : aparent; | ||
1426 | list_del(&dentry->d_u.d_child); | ||
1427 | if (!IS_ROOT(dentry)) | ||
1428 | list_add(&dentry->d_u.d_child, &dentry->d_parent->d_subdirs); | ||
1429 | else | ||
1430 | INIT_LIST_HEAD(&dentry->d_u.d_child); | ||
1431 | |||
1432 | anon->d_parent = (dparent == dentry) ? anon : dparent; | ||
1433 | list_del(&anon->d_u.d_child); | ||
1434 | if (!IS_ROOT(anon)) | ||
1435 | list_add(&anon->d_u.d_child, &anon->d_parent->d_subdirs); | ||
1436 | else | ||
1437 | INIT_LIST_HEAD(&anon->d_u.d_child); | ||
1438 | |||
1439 | anon->d_flags &= ~DCACHE_DISCONNECTED; | ||
1440 | } | ||
1441 | |||
1442 | /** | ||
1443 | * d_materialise_unique - introduce an inode into the tree | ||
1444 | * @dentry: candidate dentry | ||
1445 | * @inode: inode to bind to the dentry, to which aliases may be attached | ||
1446 | * | ||
1447 | * Introduces an dentry into the tree, substituting an extant disconnected | ||
1448 | * root directory alias in its place if there is one | ||
1449 | */ | ||
1450 | struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode) | ||
1451 | { | ||
1452 | struct dentry *alias, *actual; | ||
1453 | |||
1454 | BUG_ON(!d_unhashed(dentry)); | ||
1455 | |||
1456 | spin_lock(&dcache_lock); | ||
1457 | |||
1458 | if (!inode) { | ||
1459 | actual = dentry; | ||
1460 | dentry->d_inode = NULL; | ||
1461 | goto found_lock; | ||
1462 | } | ||
1463 | |||
1464 | /* See if a disconnected directory already exists as an anonymous root | ||
1465 | * that we should splice into the tree instead */ | ||
1466 | if (S_ISDIR(inode->i_mode) && (alias = __d_find_alias(inode, 1))) { | ||
1467 | spin_lock(&alias->d_lock); | ||
1468 | |||
1469 | /* Is this a mountpoint that we could splice into our tree? */ | ||
1470 | if (IS_ROOT(alias)) | ||
1471 | goto connect_mountpoint; | ||
1472 | |||
1473 | if (alias->d_name.len == dentry->d_name.len && | ||
1474 | alias->d_parent == dentry->d_parent && | ||
1475 | memcmp(alias->d_name.name, | ||
1476 | dentry->d_name.name, | ||
1477 | dentry->d_name.len) == 0) | ||
1478 | goto replace_with_alias; | ||
1479 | |||
1480 | spin_unlock(&alias->d_lock); | ||
1481 | |||
1482 | /* Doh! Seem to be aliasing directories for some reason... */ | ||
1483 | dput(alias); | ||
1484 | } | ||
1485 | |||
1486 | /* Add a unique reference */ | ||
1487 | actual = __d_instantiate_unique(dentry, inode); | ||
1488 | if (!actual) | ||
1489 | actual = dentry; | ||
1490 | else if (unlikely(!d_unhashed(actual))) | ||
1491 | goto shouldnt_be_hashed; | ||
1492 | |||
1493 | found_lock: | ||
1494 | spin_lock(&actual->d_lock); | ||
1495 | found: | ||
1496 | _d_rehash(actual); | ||
1497 | spin_unlock(&actual->d_lock); | ||
1498 | spin_unlock(&dcache_lock); | ||
1499 | |||
1500 | if (actual == dentry) { | ||
1501 | security_d_instantiate(dentry, inode); | ||
1502 | return NULL; | ||
1503 | } | ||
1504 | |||
1505 | iput(inode); | ||
1506 | return actual; | ||
1507 | |||
1508 | /* Convert the anonymous/root alias into an ordinary dentry */ | ||
1509 | connect_mountpoint: | ||
1510 | __d_materialise_dentry(dentry, alias); | ||
1511 | |||
1512 | /* Replace the candidate dentry with the alias in the tree */ | ||
1513 | replace_with_alias: | ||
1514 | __d_drop(alias); | ||
1515 | actual = alias; | ||
1516 | goto found; | ||
1517 | |||
1518 | shouldnt_be_hashed: | ||
1519 | spin_unlock(&dcache_lock); | ||
1520 | BUG(); | ||
1521 | goto shouldnt_be_hashed; | ||
1522 | } | ||
1523 | |||
1389 | /** | 1524 | /** |
1390 | * d_path - return the path of a dentry | 1525 | * d_path - return the path of a dentry |
1391 | * @dentry: dentry to report | 1526 | * @dentry: dentry to report |
@@ -1784,6 +1919,7 @@ EXPORT_SYMBOL(d_instantiate); | |||
1784 | EXPORT_SYMBOL(d_invalidate); | 1919 | EXPORT_SYMBOL(d_invalidate); |
1785 | EXPORT_SYMBOL(d_lookup); | 1920 | EXPORT_SYMBOL(d_lookup); |
1786 | EXPORT_SYMBOL(d_move); | 1921 | EXPORT_SYMBOL(d_move); |
1922 | EXPORT_SYMBOL_GPL(d_materialise_unique); | ||
1787 | EXPORT_SYMBOL(d_path); | 1923 | EXPORT_SYMBOL(d_path); |
1788 | EXPORT_SYMBOL(d_prune_aliases); | 1924 | EXPORT_SYMBOL(d_prune_aliases); |
1789 | EXPORT_SYMBOL(d_rehash); | 1925 | EXPORT_SYMBOL(d_rehash); |